Spring-AOP的專案設定

說明如何配置Spring AOP 以及啟用 AspectJ 代理模式

AOP 是學習整個 Spring 框架內的核心之一,基本上你用 Spring 的東西很難多多少少沒有接觸過一點AOP,最簡單能接觸到的大概就是 Transactional 了。

我們都知道上了 Transactional 這 annotation 之後可以保持我們在操作「業務邏輯」時,中間經過的「 DB 資料操作」能保證是強一致性,然後我們再深入了解基底機制一點,會再知道說這底層的操作其實就 Spring 對了我們的 class 外層再包一層強化類的proxy。

而這 proxy 呢,可能是藉由 CGlib 去實作,或者是 JDK Dynamic Proxy 來做處理,但怎麼配置基本上我們也沒在管,畢竟就像前面說的,這已經被Spring 做掉了,你就用就好。

Spring AOP 基本的專案依賴配置

就跟上頭所說的一樣,這都被 Spring 幫你做掉了,你裝上 Spring AOP 的 Starter 就能用 Spring AOP 寫切面了

AOP Starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

改用 AspectJ 的設定與配置

從這邊開始就是我們的重頭戲,這邊會跟技術架構的取捨有關,畢竟我們專案做到後期還是把 AspectJ 給拔掉了。但你要先會這套 lib 再做什麼才會有後面的取捨。

AspectJ 介紹

AspectJ 是一套強大的 AOP 函式庫。你一定很好奇,既然有了 Spring AOP 為何還要額外配置 AspectJ?

核心原因與 Proxy (代理模式) 的局限性有關:

  • 私有方法 (Private Methods):Proxy 無法攔截類別內部的私有方法。

  • 自我呼叫 (Self-invocation):當類別內部方法呼叫 this.methodB() 時,呼叫的是原始對象而非 Proxy,導致外層代理感知不到,這就是著名的 @Transactional 失效問題。

剩下什麼 Spring AOP 跟 AspectJ 差異我的就不提了,留幾個關鍵字就好,畢竟剩下的關鍵字其他人的文應該寫很多。

compile weaving 、 post compile weaving 、 編譯時期載入 、 proxy 、 Self-invocation

阿反正基於上述問題,所以這邊就會有需求要加這套了

將 Spring AOP 的Auto Proxy關閉

因為我們現在要將 Spring AOP 切換成 AspectJ 模式,所以我們會需要撰寫參數將 Spring AOP 的 proxy 模式關閉,這邊就直接在 application.properties 上改就好,如果你要整理的更乾淨的話就拆 properties 吧。

AOP Auto Proxy
spring.aop.auto=false

新增 AspectJ需要的 Lib

我們因為把 proxy 關掉的關係,所以 Transactional 的 annotation 會直接失效,這邊會需要將 aspectJ 版本的函式庫加進去,並且將指定版本的 AspectJ 相關 lib也加入 pom 檔當中。

maven依賴內容
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
</dependency>
<!--    AspectJ    -->
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjrt</artifactId>
	<version>1.9.21.2</version>
</dependency>
<!--    AspectJ Weaver    -->
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.21.2</version>
</dependency>
<!--    AspectJ tool    -->
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjtools</artifactId>
	<version>1.9.21.2</version>
</dependency>

下面列個清單說明:

  • spring-aspects:這是 Spring 提供的整合包,裡面包含了 Spring 預定義好的切面(比如 @Transactional 的 AspectJ 版實作)。

  • aspectjrt (Runtime):這是執行時需要的最小函式庫。它提供了 AOP 程式碼在跑的時候需要的註解與基礎類別(如 @Aspect, JoinPoint)。

  • aspectjweaver:最核心的關鍵。 它負責「織入」的動作。如果你是用 LTW (Load-Time Weaving),這個 jar 就是必須掛在 JVM -javaagent 後面的那個特務(Agent)。

  • aspectjtools:包含了 ajc 編譯器等工具。如果你打算用 CTW (Compile-Time Weaving),在編譯時期就完成基因改造,這個 lib 就是主角。

編譯 plugin 設定

將相關的lib放入專案當中並不會直接讓專案就使用 AspectJ 去編譯,我們會需要對應的 maven 插件 或者是 gradle 插件,這邊因為我們專案用 maven ,所以我們用 maven 去作為示範,而 gradle 的化設定方法比較簡單況且有人有寫教學就不多寫了。

maven編譯插鍵設定
<plugins>
	<!-- 設定 AspectJ 的編譯器 -->
	<plugin>
		<groupId>org.codehaus.mojo</groupId>
		<artifactId>aspectj-maven-plugin</artifactId>
		<version>1.15.0</version>
		<!-- 更新 aspectJ 的 版本到專案JDK所需要的版本  -->
		<dependencies>
			<dependency>
				<groupId>org.aspectj</groupId>
				<artifactId>aspectjrt</artifactId>
				<version>1.9.21.2</version>
			</dependency>
			<dependency>
				<groupId>org.aspectj</groupId>
				<artifactId>aspectjweaver</artifactId>
				<version>1.9.21.2</version>
			</dependency>
			<dependency>
				<groupId>org.aspectj</groupId>
				<artifactId>aspectjtools</artifactId>
				<version>1.9.21.2</version>
			</dependency>
		</dependencies>
		<configuration>
			<source>21</source>
			<!-- Explicitly set Java version -->
			<target>21</target>
			<complianceLevel>21</complianceLevel>
            <!-- 用 AspectJ 語言撰寫的 lib -->
			<aspectLibraries>
				<aspectLibrary>
					<groupId>org.springframework</groupId>
					<artifactId>spring-aspects</artifactId>
				</aspectLibrary>
			</aspectLibraries>
			<!-- 強制使用 AspectJ 編譯器 重新編譯 -->
			<forceAjcCompile>true</forceAjcCompile>
			<sources/>
			<!-- 重新編譯的目錄清單 -->
			<weaveDirectories>
				<weaveDirectory>${project.build.directory}/classes</weaveDirectory>
			</weaveDirectories>
			<Xlint>ignore</Xlint>
			<showWeaveInfo>true</showWeaveInfo>
		</configuration>
		<executions>
			<execution>
				<goals>
					<goal>compile</goal>
					<goal>test-compile</goal>
				</goals>
			</execution>
		</executions>
	</plugin>
	<!-- Finally, package with Spring Boot -->
	<plugin>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-maven-plugin</artifactId>
		<version>3.4.3</version>
	</plugin>
</plugins>

額外說明:

  • aspectj-maven-plugin:

    • 這款插件因為從2023還24年就沒更新了,依照作者文件的說法,看起來是只需要將依賴的 aspectJ lib 更新成對應 JDK 的版本就可以繼續使用了,所以我們這邊就將我們上面 pom 版本的 aspectJ 加入進去

    • 其實後續有人維護另一個版本,叫做dev.aspectj:aspectj-maven-plugin,這版本在今天(2026/5/4)我上去github看了一下,最近的 released 時間是 今年三月,可以考慮試試。

  • aspectLibraries:這一步至關重要。僅僅在 pom 引入 spring-aspects 是不夠的,我們必須明確告訴 ajc 編譯器,要把 Spring 預寫好的切面(如 AnnotationTransactionAspect)織入到我們的程式碼中。這就是為什麼要在插件內重複引用它的原因。

  • forceAjcCompile:設定為 true 是為了確保所有的類別都會經過 ajc 處理,而不是只處理變動過的檔案。這在處理複雜的 this 呼叫織入時比較保險。

  • showWeaveInfo:建議在開發階段開啟。編譯時你會看到類似 Join point 'method-execution(…​)'…​ woven into …​ 的日誌,這能讓你肉眼確認你的切面到底有沒有真的「織」進去。

拔掉的原因

理由跟 AspectJ 會把 exception 包在 AspectJ 包裝的 customer exception 中的這件事有關。因為 Spring Exception 原本其實就包了一層了,現在外面又在包一層,原始的 exception 包太深,根本沒辦法查錯誤 log 。