0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

聊聊在使用Spring AOP時一個非常常見的概念A(yù)spectJ

OSC開源社區(qū) ? 來源:江南一點雨 ? 2023-08-30 09:40 ? 次閱讀

1. 關(guān)于代理

小伙伴們知道,Java 23 種設(shè)計模式中有一種模式叫做代理模式,這種代理我們可以將之稱為靜態(tài)代理,Spring AOP 我們常說是一種動態(tài)代理,那么這兩種代理的區(qū)別在哪里呢?

1.1 靜態(tài)代理

這種代理在我們?nèi)粘I钪衅鋵嵎浅3R?,例如房屋中介就相?dāng)于是一個代理,當(dāng)房東需要出租房子的時候,需要發(fā)布廣告、尋找客戶、清理房間。。。由于比較麻煩,因此房東可以將租房子這件事情委托給中間代理去做。這就是一個靜態(tài)代理。

我通過一個簡單的代碼來演示一下,首先我們有一個租房的接口,如下:

publicinterfaceRent{
voidrent();
}

房東實現(xiàn)了該接口,表示想要出租房屋:

publicclassLandlordimplementsRent{
@Override
publicvoidrent(){
System.out.println("房屋出租");
}
}

中介作為中間代理,也實現(xiàn)了該接口,同時代理了房東,如下:

publicclassHouseAgentimplementsRent{
privateLandlordlandlord;

publicHouseAgent(Landlordlandlord){
this.landlord=landlord;
}

publicHouseAgent(){
}

@Override
publicvoidrent(){
publishAd();
landlord.rent();
agencyFee();
}

publicvoidpublishAd(){
System.out.println("發(fā)布招租廣告");
}

publicvoidagencyFee(){
System.out.println("收取中介費");
}
}

可以看到,中介的 rent 方法中,除了調(diào)用房東的 rent 方法之外,還調(diào)用了 publishAd 和 agencyFee 兩個方法。

接下來客戶租房,只需要和代理打交道就可以了,如下:

publicclassClient{
publicstaticvoidmain(String[]args){
Landlordlandlord=newLandlord();
HouseAgenthouseAgent=newHouseAgent(landlord);
houseAgent.rent();
}
}

這就是一個簡單的代理模式。無論大家是否有接觸過 Java 23 種設(shè)計模式,上面這段代碼應(yīng)該都很好理解。

這是靜態(tài)代理。

1.2 動態(tài)代理

動態(tài)代理講究在不改變原類原方法的情況下,增強目標(biāo)方法的功能,例如,大家平時使用的 Spring 事務(wù)功能,在不改變目標(biāo)方法的情況下,就可以通過動態(tài)代理為方法添加事務(wù)處理能力。再比如松哥在 TienChin 項目中所講的日志處理、接口冪等性處理、多數(shù)據(jù)源處理等,都是動態(tài)代理能力的體現(xiàn)

從實現(xiàn)原理上,我們又可以將動態(tài)代理劃分為兩大類:

編譯時增強。

運行時增強。

1.2.1 編譯時增強

編譯時增強,這種有點類似于 Lombok 的感覺,就是在編譯階段就直接生成了代理類,將來運行的時候,就直接運行這個編譯生成的代理類,AspectJ 就是這樣一種編譯時增強的工具。

AspectJ 全稱是 Eclipse AspectJ, 其官網(wǎng)地址是:http://www.eclipse.org/aspectj,截止到本文寫作時,目前最新版本為:1.9.7。

從官網(wǎng)我們可以看到 AspectJ 的定位:

基于 Java 語言的面向切面編程語言。

兼容 Java。

易學(xué)易用。

使用 AspectJ 時需要使用專門的編譯器 ajc。

1.2.2 運行時增強

運行時增強則是指借助于 JDK 動態(tài)代理或者 CGLIB 動態(tài)代理等,在內(nèi)存中臨時生成 AOP 動態(tài)代理類,我們在 Spring AOP 中常說的動態(tài)代理,一般是指這種運行時增強。

我們平日開發(fā)寫的 Spring AOP,基本上都是屬于這一類。

2. AspectJ 和 Spring AOP

經(jīng)過前面的介紹,相信大家已經(jīng)明白了 AspectJ 其實也是 AOP 的一種實現(xiàn),只不過它是編譯時增強。

接下來,松哥再通過三個具體的案例,來和小伙伴們演示編譯時增強和運行時增強。

2.1 AspectJ

首先,在 IDEA 中想要運行 AspectJ,需要先安裝 AspectJ 插件,就是下面這個:

55bc36ce-465d-11ee-a2ef-92fbcf53809c.png

安裝好之后,我們需要在 IDEA 中配置一下,使用 ajc 編譯器代替 javac(這個是針對當(dāng)前項目的設(shè)置,所以可以放心修改):

55c3e630-465d-11ee-a2ef-92fbcf53809c.png

有如下幾個需要修改的點:

首先修改編譯器為 ajc。

將使用的 Java 版本改為 8,這個一共有兩個地方需要修改。

設(shè)置 aspectjtools.jar 的位置,這個 jar 包需要自己提前準(zhǔn)備好,可以從 Maven 官網(wǎng)下載,然后在這里配置 jar 的路徑,配置完成之后,點擊 test 按鈕進(jìn)行測試,測試成功就會彈出來圖中的彈框。

對于第 3 步所需要的 jar,也可以在項目的 Maven 中添加如下依賴,自動下載,下載到本地倉庫之后,再刪除掉 pom.xml 中的配置即可:


org.aspectj
aspectjtools
1.9.7.M3

這樣,開發(fā)環(huán)境就準(zhǔn)備好了。

接下來,假設(shè)我有一個銀行轉(zhuǎn)帳的方法:

publicclassMoneyService{

publicvoidtransferMoney(){
System.out.println("轉(zhuǎn)賬操作");
}
}

我想給這個方法添加事務(wù),那么我就新建一個 Aspect,如下:

publicaspectTxAspect{
voidaround():call(voidMoneyService.transferMoney()){
System.out.println("開啟事務(wù)");
try{
proceed();
System.out.println("提交事務(wù)事務(wù)");
}catch(Exceptione){
System.out.println("回滾事務(wù)");
}
}
}

這就是 AspectJ 的語法,跟 Java 有點像,但是不太一樣。需要注意的是,這個 TxAspect 不是一個 Java 類,它的后綴是 .aj。

proceed 表示繼續(xù)執(zhí)行目標(biāo)方法,前后邏輯比較簡單,我就不多說了。

最后,我們?nèi)ミ\行轉(zhuǎn)賬服務(wù):

publicclassDemo01{
publicstaticvoidmain(String[]args){
MoneyServicemoneyService=newMoneyService();
moneyService.transferMoney();
}
}

運行結(jié)果如下:

55e67bbe-465d-11ee-a2ef-92fbcf53809c.png

這就是一個靜態(tài)代理。

為什么這么說呢?我們通過 IDEA 來查看一下 TxAspect 編譯之后的結(jié)果:

@Aspect
publicclassTxAspect{
static{
try{
ajc$postClinit();
}catch(Throwablevar1){
ajc$initFailureCause=var1;
}

}

publicTxAspect(){
}

@Around(
value="call(voidMoneyService.transferMoney())",
argNames="ajc$aroundClosure"
)
publicvoidajc$around$org_javaboy_demo_p2_TxAspect$1$3b99afea(AroundClosureajc$aroundClosure){
System.out.println("開啟事務(wù)");

try{
ajc$around$org_javaboy_demo_p2_TxAspect$1$3b99afeaproceed(ajc$aroundClosure);
System.out.println("提交事務(wù)事務(wù)");
}catch(Exceptionvar2){
System.out.println("回滾事務(wù)");
}

}

publicstaticTxAspectaspectOf(){
if(ajc$perSingletonInstance==null){
thrownewNoAspectBoundException("org_javaboy_demo_p2_TxAspect",ajc$initFailureCause);
}else{
returnajc$perSingletonInstance;
}
}

publicstaticbooleanhasAspect(){
returnajc$perSingletonInstance!=null;
}
}

再看一下編譯之后的啟動類:

publicclassDemo01{
publicDemo01(){
}

publicstaticvoidmain(String[]args){
MoneyServicemoneyService=newMoneyService();
transferMoney_aroundBody1$advice(moneyService,TxAspect.aspectOf(),(AroundClosure)null);
}
}

可以看到,都是修改后的內(nèi)容了。

所以說 AspectJ 的作用就有點類似于 Lombok,直接在編譯時期將我們的代碼改了,這就是編譯時增強。

2.2 Spring AOP

Spring AOP 在開發(fā)的時候,其實也使用了 AspectJ 中的注解,像我們平時使用的 @Aspect、@Around、@Pointcut 等,都是 AspectJ 里邊提供的,但是 Spring AOP 并未借鑒 AspectJ 的編譯時增強,Spring AOP 沒有使用 AspectJ 的編譯器和織入器,Spring AOP 還是使用了運行時增強。

運行時增強可以利用 JDK 動態(tài)代理或者 CGLIB 動態(tài)代理來實現(xiàn)。我分別來演示。

2.2.1 JDK 動態(tài)代理

JDK 動態(tài)代理有一個要求,就是被代理的對象需要有接口,沒有接口不行,CGLIB 動態(tài)代理則無此要求。

假設(shè)我現(xiàn)在有一個計算器接口:

publicinterfaceICalculator{
intadd(inta,intb);
}

這個接口有一個實現(xiàn)類:

publicclassCalculatorImplimplementsICalculator{
@Override
publicintadd(inta,intb){
System.out.println(a+"+"+b+"="+(a+b));
returna+b;
}
}

現(xiàn)在,我想通過動態(tài)代理實現(xiàn)統(tǒng)計該接口的執(zhí)行時間功能,JDK 動態(tài)代理如下:

publicclassDemo02{
publicstaticvoidmain(String[]args){

CalculatorImplcalculator=newCalculatorImpl();
ICalculatorproxyInstance=(ICalculator)Proxy.newProxyInstance(Demo02.class.getClassLoader(),newClass[]{ICalculator.class},newInvocationHandler(){
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
longstartTime=System.currentTimeMillis();
Objectinvoke=method.invoke(calculator,args);
longendTime=System.currentTimeMillis();
System.out.println(method.getName()+"方法執(zhí)行耗時"+(endTime-startTime)+"毫秒");
returninvoke;
}
});
proxyInstance.add(3,4);
}
}

不需要任何額外依賴,都是 JDK 自帶的能力:

Proxy.newProxyInstance 方法表示要生成一個動態(tài)代理對象。

newProxyInstance 方法有三個參數(shù),第一個是一個類加載器,第二個參數(shù)是一個被代理的對象所實現(xiàn)的接口,第三個則是具體的代理邏輯。

在 InvocationHandler 中,有一個 invoke 方法,該方法有三個參數(shù),分別表示當(dāng)前代理對象,被攔截下來的方法以及方法的參數(shù),我們在該方法中可以統(tǒng)計被攔截方法的執(zhí)行時間,通過方式執(zhí)行被攔截下來的目標(biāo)方法。

最終,第一步的方法返回了一個代理對象,執(zhí)行該代理對象,就有代理的效果了。

上面這個案例就是一個 JDK 動態(tài)代理。這是一種運行時增強,在編譯階段并未修改我們的代碼。

2.2.2 CGLIB 動態(tài)代理

從 SpringBoot2 開始,AOP 默認(rèn)使用的動態(tài)代理就是 CGLIB 動態(tài)代理了,相比于 JDK 動態(tài)代理,CGLIB 動態(tài)代理支持代理一個類。

使用 CGLIB 動態(tài)代理,需要首先添加依賴,如下:


cglib
cglib
3.3.0

假設(shè)我有一個計算器,如下:

publicclassCalculator{
publicintadd(inta,intb){
System.out.println(a+"+"+b+"="+(a+b));
returna+b;
}
}

大家注意,這個計算器就是一個實現(xiàn)類,沒有接口。

現(xiàn)在,我想統(tǒng)計這個計算器方法的執(zhí)行時間,首先,我添加一個方法執(zhí)行的攔截器:

publicclassCalculatorInterceptorimplementsMethodInterceptor{
@Override
publicObjectintercept(Objecto,Methodmethod,Object[]objects,MethodProxymethodProxy)throwsThrowable{
longstartTime=System.currentTimeMillis();
Objectresult=methodProxy.invokeSuper(o,objects);
longendTime=System.currentTimeMillis();
System.out.println(method.getName()+"方法執(zhí)行耗時"+(endTime-startTime)+"毫秒");
returnresult;
}
}

當(dāng)把代理方法攔截下來之后,額外要做的事情就在 intercept 方法中完成。通過執(zhí)行 methodProxy.invokeSuper 可以調(diào)用到代理方法。

最后,配置 CGLIB,為方法配置增強:

publicclassDemo03{
publicstaticvoidmain(String[]args){
Enhancerenhancer=newEnhancer();
enhancer.setSuperclass(Calculator.class);
enhancer.setCallback(newCalculatorInterceptor());
Calculatorcalculator=(Calculator)enhancer.create();
calculator.add(4,5);
}
}

這里其實就是創(chuàng)建了字節(jié)增強器,為生成的代理對象配置 superClass,然后設(shè)置攔截下來之后的回調(diào)函數(shù)就行了,最后通過 create 方法獲取到一個代理對象。

這就是 CGLIB 動態(tài)代理。

3. 小結(jié)

經(jīng)過上面的介紹,現(xiàn)在大家應(yīng)該搞明白了靜態(tài)代理、編譯時增強的動態(tài)代理和運行時增強的動態(tài)代理了吧~

那么我們在項目中到底該如何選擇呢?

先來說 AspectJ 的幾個優(yōu)勢吧。

Spring AOP 由于要生成動態(tài)代理類,因此,對于一些 static 或者 final 修飾的方法,是無法代理的,因為這些方法是無法被重寫的,final 修飾的類也無法被繼承。但是,AspectJ 由于不需要動態(tài)生成代理類,一切都是編譯時完成的,因此,這個問題在 AspectJ 中天然的就被解決了。

Spring AOP 有一個局限性,就是只能用到被 Spring 容器管理的 Bean 上,其他的類則無法使用,AspectJ 則無此限制(話說回來,Java 項目 Spring 基本上都是標(biāo)配了,所以這點其實到也不重要)。

Spring AOP 只能在運行時增強,而 AspectJ 則支持編譯時增強,編譯后增強以及運行時增強。

Spring AOP 支持方法的增強,然而 AspectJ 支持方法、屬性、構(gòu)造器、靜態(tài)對象、final 類/方法等的增強。

AspectJ 由于是編譯時增強,因此運行效率也要高于 Spring AOP。

。。。

雖然 AspectJ 有這么多優(yōu)勢,但是 Spring AOP 卻有另外一個制勝法寶,那就是簡單易用!

所以,我們?nèi)粘i_發(fā)中,還是 Spring AOP 使用更多。






審核編輯:劉清
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1602

    瀏覽量

    48896
  • 計算器
    +關(guān)注

    關(guān)注

    16

    文章

    434

    瀏覽量

    37031
  • JAVA語言
    +關(guān)注

    關(guān)注

    0

    文章

    138

    瀏覽量

    20026
  • AOP
    AOP
    +關(guān)注

    關(guān)注

    0

    文章

    40

    瀏覽量

    11070

原文標(biāo)題:似懂非懂的 AspectJ

文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    AOP知識詳解

    今天我們繼續(xù)看看AOP相關(guān)的知識,前面說到了Javassit,Spring AOP,通過該篇,讓你對AOP有更完整的認(rèn)識。 AOP 再看
    的頭像 發(fā)表于 09-25 11:14 ?814次閱讀
    <b class='flag-5'>AOP</b>知識詳解

    Spring AOP如何破解java應(yīng)用

    前面我們看過javaassit是如何破解java應(yīng)用,核心都是AOP相關(guān)的知識,今天我們看下Spring AOP是怎么回事! Spring-AOP
    的頭像 發(fā)表于 09-25 11:16 ?779次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>AOP</b>如何破解java應(yīng)用

    java spring教程

    Spring核心概念介紹控制反轉(zhuǎn)(IOC)依賴注入(DI)集合對象注入等Bean的管理BeanFactoryApplicationContextSpring web中的使用
    發(fā)表于 09-11 11:09

    什么是java spring

    或多個模塊聯(lián)合實現(xiàn)簡單來說,Spring輕量級的控制反轉(zhuǎn)(IoC)和面向切面(AOP)的容器框架?!?輕量——從大小與開銷兩方面而言Sprin
    發(fā)表于 09-11 11:16

    聊聊Dubbo - Dubbo可擴展機制源碼解析

    摘要: Dubbo可擴展機制實戰(zhàn)中,我們了解了Dubbo擴展機制的概念,初探了Dubbo中LoadBalance的實現(xiàn),并自己實現(xiàn)了
    發(fā)表于 06-05 18:43

    Spring工作原理

    核心就是AOP這個就是面向切面編程,可以為某類對象 進(jìn)行監(jiān)督和控制(也就是調(diào)用這類對象的具體方法的前后去調(diào)用你指定的 模塊)從而達(dá)到對
    發(fā)表于 07-10 07:41

    Spring筆記分享

    Spring實現(xiàn)了使用簡單的組件配置組合成復(fù)雜的應(yīng)用。 Spring 中可以使用XML和Java注解組合這些對象。6)
    發(fā)表于 11-04 07:51

    Spring AOP使用教程及代碼詳講

    和多態(tài)性等概念來建立種對象層次結(jié)構(gòu),用以模擬公共行為的集合。當(dāng)我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關(guān)系,但并不適合定
    發(fā)表于 12-14 01:19 ?2691次閱讀

    Spring認(rèn)證是什么?

    ,例如:配置、組件掃描、AOP、數(shù)據(jù)訪問和事務(wù)、REST、安全、自動配置、執(zhí)行器、 Spring boot測試等。
    的頭像 發(fā)表于 07-04 10:19 ?1206次閱讀
    <b class='flag-5'>Spring</b>認(rèn)證是什么?

    Spring框架的簡單介紹及快速入門教程

    開發(fā)者J2EE開發(fā)中遇到的許多常見的問題,提供了功能強大IOC、AOP及Web MVC等功能。Spring可以單獨應(yīng)用于構(gòu)筑應(yīng)用程序,也可以和Struts、Webwork、Tapes
    的頭像 發(fā)表于 07-15 14:47 ?1536次閱讀

    剖析Spring中最常用的擴展點(上)

    我們說到spring,可能第一個想到的是 `IOC`(控制反轉(zhuǎn)) 和 `AOP`(面向切面編程)。 沒錯,它們是spring的基石,
    的頭像 發(fā)表于 02-15 16:06 ?672次閱讀
    剖析<b class='flag-5'>Spring</b>中最常用的擴展點(上)

    剖析Spring中最常用的擴展點(中)

    我們說到spring,可能第一個想到的是 `IOC`(控制反轉(zhuǎn)) 和 `AOP`(面向切面編程)。 沒錯,它們是spring的基石,
    的頭像 發(fā)表于 02-15 16:06 ?435次閱讀
    剖析<b class='flag-5'>Spring</b>中最常用的擴展點(中)

    剖析Spring中最常用的擴展點(下)

    我們說到spring,可能第一個想到的是 `IOC`(控制反轉(zhuǎn)) 和 `AOP`(面向切面編程)。 沒錯,它們是spring的基石,
    的頭像 發(fā)表于 02-15 16:07 ?391次閱讀

    解讀Spring源碼中的IOC和AOP部分

    Spring Framework 是非常流行的開源框架,為 Java 應(yīng)用程序提供了廣泛的支持和功能。
    的頭像 發(fā)表于 06-06 15:49 ?667次閱讀

    AOP要怎么使用

    到,創(chuàng)建切面Advisor,并且將切點都綁定到自定義注解上面。 引入AOP的Starts: org .springframework
    的頭像 發(fā)表于 10-09 16:18 ?542次閱讀
    <b class='flag-5'>AOP</b>要怎么使用