<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

聊聊在使用Spring AOP時一個非常常見的概念AspectJ

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

1. 關于代理

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

1.1 靜態代理

這種代理在我們日常生活中其實非常常見,例如房屋中介就相當于是一個代理,當房東需要出租房子的時候,需要發布廣告、尋找客戶、清理房間。。。由于比較麻煩,因此房東可以將租房子這件事情委托給中間代理去做。這就是一個靜態代理。

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

publicinterfaceRent{
voidrent();
}

房東實現了該接口,表示想要出租房屋:

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

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

publicclassHouseAgentimplementsRent{
privateLandlordlandlord;

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

publicHouseAgent(){
}

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

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

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

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

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

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

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

這是靜態代理。

1.2 動態代理

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

從實現原理上,我們又可以將動態代理劃分為兩大類:

編譯時增強。

運行時增強。

1.2.1 編譯時增強

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

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

從官網我們可以看到 AspectJ 的定位:

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

兼容 Java。

易學易用。

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

1.2.2 運行時增強

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

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

2. AspectJ 和 Spring AOP

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

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

2.1 AspectJ

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

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

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

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

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

首先修改編譯器為 ajc。

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

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

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


org.aspectj
aspectjtools
1.9.7.M3

這樣,開發環境就準備好了。

接下來,假設我有一個銀行轉帳的方法:

publicclassMoneyService{

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

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

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

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

proceed 表示繼續執行目標方法,前后邏輯比較簡單,我就不多說了。

最后,我們去運行轉賬服務:

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

運行結果如下:

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

這就是一個靜態代理。

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

@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("開啟事務");

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

}

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);
}
}

可以看到,都是修改后的內容了。

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

2.2 Spring AOP

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

運行時增強可以利用 JDK 動態代理或者 CGLIB 動態代理來實現。我分別來演示。

2.2.1 JDK 動態代理

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

假設我現在有一個計算器接口:

publicinterfaceICalculator{
intadd(inta,intb);
}

這個接口有一個實現類:

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

現在,我想通過動態代理實現統計該接口的執行時間功能,JDK 動態代理如下:

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()+"方法執行耗時"+(endTime-startTime)+"毫秒");
returninvoke;
}
});
proxyInstance.add(3,4);
}
}

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

Proxy.newProxyInstance 方法表示要生成一個動態代理對象。

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

在 InvocationHandler 中,有一個 invoke 方法,該方法有三個參數,分別表示當前代理對象,被攔截下來的方法以及方法的參數,我們在該方法中可以統計被攔截方法的執行時間,通過方式執行被攔截下來的目標方法。

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

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

2.2.2 CGLIB 動態代理

從 SpringBoot2 開始,AOP 默認使用的動態代理就是 CGLIB 動態代理了,相比于 JDK 動態代理,CGLIB 動態代理支持代理一個類。

使用 CGLIB 動態代理,需要首先添加依賴,如下:


cglib
cglib
3.3.0

假設我有一個計算器,如下:

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

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

現在,我想統計這個計算器方法的執行時間,首先,我添加一個方法執行的攔截器:

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()+"方法執行耗時"+(endTime-startTime)+"毫秒");
returnresult;
}
}

當把代理方法攔截下來之后,額外要做的事情就在 intercept 方法中完成。通過執行 methodProxy.invokeSuper 可以調用到代理方法。

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

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

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

這就是 CGLIB 動態代理。

3. 小結

經過上面的介紹,現在大家應該搞明白了靜態代理、編譯時增強的動態代理和運行時增強的動態代理了吧~

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

先來說 AspectJ 的幾個優勢吧。

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

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

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

Spring AOP 支持方法的增強,然而 AspectJ 支持方法、屬性、構造器、靜態對象、final 類/方法等的增強。

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

。。。

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

所以,我們日常開發中,還是 Spring AOP 使用更多。






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

    關注

    1

    文章

    1579

    瀏覽量

    48713
  • 計算器
    +關注

    關注

    16

    文章

    428

    瀏覽量

    36579
  • JAVA語言
    +關注

    關注

    0

    文章

    138

    瀏覽量

    19956
  • AOP
    AOP
    +關注

    關注

    0

    文章

    37

    瀏覽量

    11053

原文標題:似懂非懂的 AspectJ

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    AOP知識詳解

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

    Spring AOP如何破解java應用

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

    java spring教程

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

    什么是java spring

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

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

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

    Spring工作原理

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

    Spring筆記分享

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

    啟動Spring Boot項目應用的三種方法

    的讀取,開啟注解4)配置日志文件...配置完成之后部署tomcat 調試。但是如果使用spring boot呢?很簡單,我僅僅只需要非常少的幾個配置就可以迅速方便的搭建起來套web項目或者是構建
    發表于 01-14 17:33

    Spring認證」Spring Hello World 項目示例

    讓我們開始使用 Spring Framework 進行實際編程。開始使用 Spring 框架編寫第一個示例之前,您必須確保已按照Spring
    發表于 08-17 13:49

    Spring AOP使用教程及代碼詳講

    AOP(Aspect-OrientedProgramming,面向方面編程),可以說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承
    發表于 12-14 01:19 ?2627次閱讀

    Spring認證是什么?

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

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

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

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

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

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

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

    AOP要怎么使用

    到,創建一個切面Advisor,并且將切點都綁定到一個自定義注解上面。 引入AOP的Starts: org .springframework.boot spring -boot-starter-aop
    的頭像 發表于 10-09 16:18 ?381次閱讀
    <b class='flag-5'>AOP</b>要怎么使用
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>