AspectJ 使用介绍
文章目錄
- AspectJ 使用介紹
 - Compile-Time Weaving
 - Post-Compile Weaving
 - Load-Time Weaving
 - 小結(jié)
 
上一篇文章,我們介紹了 Spring AOP 的各種用法,包括隨著 Spring 的演進而發(fā)展出來的幾種配置方式。
但是我們始終沒有使用到 AspectJ,即使是在基于注解的 @AspectJ 的配置方式中,Spring 也僅僅是使用了 AspectJ 包中的一些注解而已,并沒有依賴于 AspectJ 實現(xiàn)具體的功能。
本文將介紹使用 AspectJ,介紹它的 3 種織入方式。
本文使用的測試源碼已上傳到 Github: hongjiev/aspectj-learning,如果你在使用過程中碰到麻煩,請在評論區(qū)留言。
目錄:
AspectJ 使用介紹
AspectJ 作為 AOP 編程的完全解決方案,提供了三種織入時機,分別為
本節(jié)中的內(nèi)容參考了《Intro to AspectJ》,Baeldung 真的是挺不錯的一個 Java 博客。
首先,先把下面兩個依賴加進來:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.13</version> </dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.13</version> </dependency>我們后面需要用到下面這個類,假設(shè)賬戶初始有 20 塊錢,之后會調(diào) account.pay(amount) 進行付款:
public class Account {int balance = 20;public boolean pay(int amount) {if (balance < amount) {return false;}balance -= amount;return true;} }下面,我們定義兩個 Aspect 來進行演示:
- AccountAspect:用 AspectJ 的語法來寫,對交易進行攔截,如此次交易超過余額,直接拒絕。
 - ProfilingAspect:用 Java 來寫,用于記錄方法的執(zhí)行時間
 
AccountAspect 需要以 .aj 結(jié)尾,如我們在 com.javadoop.aspectjlearning.aspectj 的 package 下新建文件 AccountAspect.aj,內(nèi)容如下:
package com.javadoop.aspectjlearning.aspect;import com.javadoop.aspectjlearning.model.Account;public aspect AccountAspect {pointcut callPay(int amount, Account account):call(boolean com.javadoop.aspectjlearning.model.Account.pay(int)) && args(amount) && target(account);before(int amount, Account account): callPay(amount, account) {System.out.println("[AccountAspect]付款前總金額: " + account.balance);System.out.println("[AccountAspect]需要付款: " + amount);}boolean around(int amount, Account account): callPay(amount, account) {if (account.balance < amount) {System.out.println("[AccountAspect]拒絕付款!");return false;}return proceed(amount, account);}after(int amount, Account balance): callPay(amount, balance) {System.out.println("[AccountAspect]付款后,剩余:" + balance.balance);}}上面 .aj 的語法我們可能不熟悉,但是看上去還是簡單的,分別處理了 before、around 和 after 的場景。
我們再來看用 Java 寫的 ProfilingAspect.java:
package com.javadoop.aspectjlearning.aspect;import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut;@Aspect public class ProfilingAspect {@Pointcut("execution(* com.javadoop.aspectjlearning.model.*.*(..))")public void modelLayer() {}@Around("modelLayer()")public Object logProfile(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();Object result = joinPoint.proceed();System.out.println("[ProfilingAspect]方法: 【" + joinPoint.getSignature() + "】結(jié)束,用時: " + (System.currentTimeMillis() - start));return result;} }接下來,我們討論怎么樣將定義好的兩個 Aspects 織入到我們的 Account 的付款方法 pay(amount) 中,也就是三種織入時機分別是怎么實現(xiàn)的。
Compile-Time Weaving
這是最簡單的使用方式,在編譯期的時候進行織入,這樣編譯出來的 .class 文件已經(jīng)織入了我們的代碼,在 JVM 運行的時候其實就是加載了一個普通的被織入了代碼的類。
如果你是采用 maven 進行管理,可以在 <build> 中加入以下的插件:
<!-- 編譯期織入 --> <plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.7</version><configuration><complianceLevel>1.8</complianceLevel><source>1.8</source><target>1.8</target><showWeaveInfo>true</showWeaveInfo><verbose>true</verbose><Xlint>ignore</Xlint><encoding>UTF-8</encoding></configuration><executions><execution><goals><goal>compile</goal><goal>test-compile</goal></goals></execution></executions> </plugin>AccountAspect.aj 文件 javac 是沒法編譯的,所以上面這個插件其實充當了編譯的功能。
然后,我們就可以運行了:
public class Application {public static void main(String[] args) {testCompileTime();}public static void testCompileTime() {Account account = new Account();System.out.println("==================");account.pay(10);account.pay(50);System.out.println("==================");} }輸出:
================== [AccountAspect]付款前總金額: 20 [AccountAspect]需要付款: 10 [ProfilingAspect]方法: 【boolean com.javadoop.aspectjlearning.model.Account.pay(int)】結(jié)束,用時: 1 [AccountAspect]付款后,剩余:10 [AccountAspect]付款前總金額: 10 [AccountAspect]需要付款: 50 [AccountAspect]拒絕付款! [AccountAspect]付款后,剩余:10 ==================結(jié)果看上去就很神奇(我們知道是 aop 搞的鬼當然會覺得不神奇),其實奧秘就在于 main 函數(shù)中的代碼被改變了,不再是上面幾行簡單的代碼了,而是進行了織入:
我們的 Account 類也不再像原來定義的那樣了:
編譯期織入理解起來應(yīng)該還是比較簡單,就是在編譯的時候先修改了代碼再進行編譯。
Post-Compile Weaving
Post-Compile Weaving 和 Compile-Time Weaving 非常類似,我們也是直接用場景來說。
我們假設(shè)上面的 Account 類在 aspectj-learning-share.jar 包中,我們的工程 aspectj-learning 依賴了這個 jar 包。
由于 Account 這個類已經(jīng)被編譯出來了,我們要對它的方法進行織入,就需要用到編譯后織入。
為了方便大家測試,盡量讓前面的用例也能跑起來。我們定義一個新的類 User,代碼和 Account 一樣,但是在 aspectj-learning-share.jar 包中,這個包就這一個類。
同時也復(fù)制 AccountAspect 一份出來,命名為 UserAspect,稍微修改修改就可以用來處理 User 類了。
首先,我們注釋掉之前編譯期織入使用的插件配置,增加以下插件配置(其實還是同一個插件):
<!--編譯后織入--> <plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.11</version><configuration><complianceLevel>1.8</complianceLevel><weaveDependencies><weaveDependency><groupId>com.javadoop</groupId><artifactId>aspectj-learning-share</artifactId></weaveDependency></weaveDependencies></configuration><executions><execution><goals><goal>compile</goal></goals></execution></executions> </plugin>注意配置中的 <weaveDependency>,我們在 <dependencies> 中要配置好依賴,然后在這里進行配置。這樣就可以對其進行織入了。
接下來,大家可以手動用 mvn clean package 編譯一下,然后就會看到以下結(jié)果:
從上圖我們可以看到,上面的配置會把相應(yīng)的 jar 包中的類加到當前工程的編譯結(jié)果中(User 類原本是在 aspectj-learning-share.jar 中的)。
運行一下:
java -jar target/aspectj-learning-1.0-jar-with-dependencies.jar運行結(jié)果也會如預(yù)期的一樣,UserAspect 對 User 進行了織入,這里就不贅述了。感興趣的讀者自己去跑一下,注意一定要用 mvn 命令,不要用 IDE,不然很多時候發(fā)現(xiàn)不了問題。
Intellij 在 build 的時候會自己處理 AspectJ,而不是用我們配置的 maven 插件。
Load-Time Weaving
最后,我們要介紹的是 LTW 織入,正如 Load-Time 的名字所示,它是在 JVM 加載類的時候做的織入。AspectJ 允許我們在啟動的時候指定 agent 來實現(xiàn)這個功能。
首先,我們先注釋掉之前在 pom.xml 中用于編譯期和編譯后織入使用的插件,免得影響我們的測試。
我們要知道,一旦我們?nèi)サ袅?aspectj 的編譯插件,那么 .aj 的文件是不會被編譯的。
然后,我們需要在 JVM 的啟動參數(shù)中加上以下 agent(或在 IDE 中配置 VM options),如:
-javaagent:/Users/hongjie/.m2/repository/org/aspectj/aspectjweaver/1.8.13/aspectjweaver-1.8.13.jar之后,我們需要在 resources 中配置 aop.xml 文件,放置在 META-INF 目錄中(resource/META-INF/aop.xml):
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"><aspectj> <aspects> <aspect name="com.javadoop.aspectjlearning.aspect.ProfilingAspect"/> <weaver options="-verbose -showWeaveInfo"> <include within="com.javadoop.aspectjlearning..*"/> </weaver> </aspects></aspectj>aop.xml 文件中的配置非常容易理解,只需要配置 Aspects 和需要被織入的類即可。
我們用以下程序進行測試:
public class Application { public static void main(String[] args) { testLoadTime(); } public static void testLoadTime() { Account account = new Account(); System.out.println("=================="); account.pay(10); account.pay(50); System.out.println("=================="); }}萬事具備了,我們可以開始跑起來了。
第一步,編譯
mvn clean package第二步,檢查編譯結(jié)果
我們通過 IDE 查看編譯出來的代碼(IDE反編譯),可以看到,Application 類并未進行織入,Account 類也并未進行織入。
第三步,運行
從第二步我們可以看到,在運行之前,AspectJ 沒有做任何的事情。
那么可以肯定的就是,AspectJ 會在運行期利用 aop.xml 中的配置進行織入處理。
在命令行中執(zhí)行以下語句:
java -jar target/aspectj-learning-1.0-jar-with-dependencies.jar輸出為:
====================================可以看到?jīng)]有任何織入處理,然后執(zhí)行以下語句再試試:
java -javaagent:/Users/hongjie/.m2/repository/org/aspectj/aspectjweaver/1.8.13/aspectjweaver-1.8.13.jar -jar target/aspectj-learning-1.0-jar-with-dependencies.jar啟動的時候指定了 -javaagent:/.../aspectjweaver-1.8.13.jar,然后再看輸出結(jié)果:
==================[ProfilingAspect]方法: 【boolean com.javadoop.aspectjlearning.model.Account.pay(int)】結(jié)束,用時: 1[ProfilingAspect]方法: 【boolean com.javadoop.aspectjlearning.model.Account.pay(int)】結(jié)束,用時: 0==================我們可以看到 ProfilingAspect 已經(jīng)進行了織入處理,這就是 Load-time Weaving。
到這里,就要結(jié)束這一小節(jié)了,這里順便再介紹下如果用 maven 跑測試的話怎么搞。
首先,我們往 surefire 插件中加上 javaagent:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.10</version> <configuration> <argLine> -javaagent:/xxx/aspectjweaver-1.8.13.jar </argLine> <useSystemClassLoader>true</useSystemClassLoader> <forkMode>always</forkMode> </configuration></plugin>然后,我們就可以用 mvn test 看到織入效果了。還是那句話,不要用 IDE 進行測試,因為 IDE 太“智能”了。
小結(jié)
AspectJ 的三種織入方式中,個人覺得前面的兩種會比較實用一些,因為第三種需要修改啟動腳本,對于大型公司來說會比較不友好,需要專門找運維人員配置。
在實際生產(chǎn)中,我們用得最多的還是純 Spring AOP,通過本文的介紹,相信大家對于 AspectJ 的使用應(yīng)該也沒什么壓力了。
大家如果對于本文介紹的內(nèi)容有什么不清楚的,請直接在評論區(qū)留言,如果對于 Spring + AspectJ 感興趣的讀者,碰到問題也可以在評論區(qū)和大家互動討論。
(全文完)
總結(jié)
以上是生活随笔為你收集整理的AspectJ 使用介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 【SpringCloud】Gateway
 - 下一篇: 嵌入式系统设计的核心技术