當(dāng)前位置:首頁 > IT技術(shù) > 編程語言 > 正文

spring框架-認(rèn)識AOP(三)
2021-12-13 17:45:11

??

AOP綜述

AOP是什么

Aspect-oriented programming 面向切面(方面)編程

AOP有什么用

可以把業(yè)務(wù)邏輯和系統(tǒng)級的服務(wù)進(jìn)行隔離

系統(tǒng)級的服務(wù)像系統(tǒng)的日志,事務(wù),權(quán)限驗(yàn)證等

AOP怎么用

動態(tài)代理

AOP優(yōu)點(diǎn)

1.降低了組件之間的耦合性 ,實(shí)現(xiàn)了軟件各層之間的解耦

2、低侵入式設(shè)計(jì),代碼的污染極低

3、利用它很容易實(shí)現(xiàn)如權(quán)限攔截,運(yùn)行期監(jiān)控和系統(tǒng)日志等功能


探索AOP

如果沒有接觸過AOP(面向切面編程)的,可能一時(shí)間沒辦法理解,因?yàn)橹岸际怯肑AVA的面向?qū)ο缶幊蹋∣OP),沒關(guān)系,一步步跟我來學(xué)習(xí)AOP。

當(dāng)然如果您是技術(shù)大牛,既然您抽出時(shí)間查看了虛竹的文章,歡迎糾正文章中的不足和缺陷。

我們先來看下來這段《精通Spring4.x 企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》提供的代碼片段

spring框架-認(rèn)識AOP(三)_連接點(diǎn)

圖上的業(yè)務(wù)代碼被事務(wù)管理代碼和性能監(jiān)控代碼所包圍,而且學(xué)過JAVA的都知道,出現(xiàn)重復(fù)代碼了。出現(xiàn)重復(fù)代碼那就需要重構(gòu)代碼,代碼的重構(gòu)步驟一般有這兩種:

1、抽成方法

2、抽成類

抽取成類的方式我們稱之為:縱向抽取

  • 通過繼承的方式實(shí)現(xiàn)縱向抽取

但是,虛竹發(fā)現(xiàn)這種抽取方式在圖上的業(yè)務(wù)中不適用了,因?yàn)槭聞?wù)管理代碼和性能監(jiān)控代碼是跟對應(yīng)的業(yè)務(wù)代碼是有邏輯關(guān)系的


spring框架-認(rèn)識AOP(三)_aop_02

現(xiàn)在縱向抽取的方式不行了,AOP希望將分散在各個(gè)業(yè)務(wù)邏輯代碼中相同的代碼通過橫向切割的方式抽取到一個(gè)獨(dú)立的模塊中!請看下圖

?spring框架-認(rèn)識AOP(三)_動態(tài)代理_03

圖上應(yīng)該很好理解,把 重復(fù)性的非業(yè)務(wù)代碼橫向抽取出來,這個(gè)簡單。但是如何把這些非業(yè)務(wù)代碼融合到業(yè)務(wù)代碼中,達(dá)到跟之前的代碼同樣的效果。這個(gè)才是難題,就是AOP要解決的難題。



AOP的動態(tài)代理:


  1. jdk動態(tài)代理
  2. cglib動態(tài)代理

Spring在選擇用JDK動態(tài)代理還是CGLiB動態(tài)代理的依據(jù):

?? (1)當(dāng)Bean實(shí)現(xiàn)接口時(shí),Spring就會用JDK的動態(tài)代理

?? (2)當(dāng)Bean沒有實(shí)現(xiàn)接口時(shí),Spring使用CGlib是實(shí)現(xiàn)

(3)可以強(qiáng)制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)

?JDK代理和CGLib代理我們該用哪個(gè)

在《精通Spring4.x 企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》給出了建議:


??? 如果是單例的我們最好使用CGLib代理,如果是多例的我們最好使用JDK代理


原因:

??? JDK在創(chuàng)建代理對象時(shí)的性能要高于CGLib代理,而生成代理對象的運(yùn)行性能卻比CGLib的低。

??? 如果是單例的代理,推薦使用CGLib

現(xiàn)在大家知道什么是AOP了吧:把非業(yè)務(wù)重復(fù)代碼橫向抽取出來,通過動態(tài)代理織入目標(biāo)業(yè)務(wù)對象函數(shù)中,實(shí)現(xiàn)跟之前一樣的代碼


AOP術(shù)語

aop術(shù)語不好理解,《精通Spring4.x 企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》書上有提到這些術(shù)語,我盡量解釋下這些術(shù)語

連接點(diǎn)(JointPoint)

一個(gè)類中的所有方法都可以被稱為連接點(diǎn)

切入點(diǎn)(PointCut)

上面也說了,每個(gè)方法都可以稱之為連接點(diǎn),我們具體定位到某一個(gè)方法就成為切點(diǎn)

用注解來說明,被@Log定義的方法就是切入點(diǎn)

spring框架-認(rèn)識AOP(三)_aop_04

增強(qiáng)/通知(Advice)?

表示添加到切點(diǎn)的一段邏輯代碼,并定位連接點(diǎn)的方位信息。

前置通知(MethodBeforeAdvice )?

前置通知是在目標(biāo)方法執(zhí)行前執(zhí)行

后置通知 (AfterReturningAdvice )

后置通知是在目標(biāo)方法執(zhí)行后才執(zhí)行 ,可以得到目標(biāo)方法返回的值 ,但不能改變返回值

環(huán)繞通知(MethodInterceptor)

環(huán)繞通知有在目標(biāo)方法執(zhí)行前的代碼,也有在目標(biāo)方法執(zhí)行后的代碼,可以得到目標(biāo)方法的值,可以改變這個(gè)返回值!

擴(kuò)展知識點(diǎn):

spring中的攔截器分兩種:

1、HandlerInterceptor

2、MethodInterceptor

HandlerInterceptor是springMVC項(xiàng)目中的攔截器,它攔截的目標(biāo)是請求的地址,比MethodInterceptor先執(zhí)行

MethodInterceptor是AOP項(xiàng)目中的攔截器,它攔截的目標(biāo)是方法,即使不是controller中的方法。

異常通知(ThrowsAdvice)

最適合的場景就是事務(wù)管理

引介通知(IntroductionInterceptor)

引介通知比較特殊,上面的四種通知都是在目標(biāo)方法上織入通知,引介通知是在目標(biāo)類添加新方法或?qū)傩?/p>


  • 簡單來說就定義了是干什么的,具體是在哪干
  • Spring AOP提供了5種Advice類型給我們:前置通知、后置通知、返回通知、異常通知、環(huán)繞通知給我們使用!

切面(Aspect)

切面由切入點(diǎn)和增強(qiáng)/通知組成

交叉在各個(gè)業(yè)務(wù)邏輯中的系統(tǒng)服務(wù),類似于安全驗(yàn)證,事務(wù)處理,日志記錄都可以理解為切面

織入(weaving)

就是將切面代碼插入到目標(biāo)對象某個(gè)方法的過程,相當(dāng)于我們在jdk動態(tài)代理里面的 invocationHandler接口方法的內(nèi)容

用注解解釋:就是在目標(biāo)對象某個(gè)方法上,打上切面注解

目標(biāo)對象(target)

切入點(diǎn)和連接點(diǎn)所屬的類

顧問(Advisor)

就是通知的一個(gè)封裝和延伸,可以將通知以更為復(fù)雜的方式織入到某些方法中,是將通知包裝為更復(fù)雜切面的裝配器。


Spring對AOP的支持

Spring提供了3種類型的AOP支持:


  • 基于代理的經(jīng)典SpringAOP
    • 需要實(shí)現(xiàn)接口,手動創(chuàng)建代理
  • 純POJO切面
    • 使用XML配置,aop命名空間
  • ??@AspectJ??注解驅(qū)動的切面
    • 使用注解的方式,這是最簡潔和最方便的!


知識點(diǎn)

切面類型主要分成了三種


  • 一般切面
  • 切點(diǎn)切面
  • 引介/引入切面

spring框架-認(rèn)識AOP(三)_spring_05

一般切面,切點(diǎn)切面,引介/引入切面說明:

spring框架-認(rèn)識AOP(三)_spring_06


?一般切面一般不會直接使用,切點(diǎn)切面都是直接用就可以了。

這里重點(diǎn)說明下引介/引入切面:

  • 引介/引入切面是引介/引入增強(qiáng)的封裝器,通過引介/引入切面,可以更容易地為現(xiàn)有對象添加任何接口的實(shí)現(xiàn)!

繼承關(guān)系圖:


spring框架-認(rèn)識AOP(三)_動態(tài)代理_07


引介/引入切面有兩個(gè)實(shí)現(xiàn)類:


  • DefaultIntroductionAdvisor:常用的實(shí)現(xiàn)類
  • DeclareParentsAdvisor:用于實(shí)現(xiàn)AspectJ語言的DeclareParent注解表示的引介/引入切面

實(shí)際上,我們使用AOP往往是Spring內(nèi)部使用BeanPostProcessor幫我們創(chuàng)建代理

這些代理的創(chuàng)建器可以分成三類:


  • 基于Bean配置名規(guī)則的自動代理創(chuàng)建器:BeanNameAutoProxyCreator
  • 基于Advisor匹配機(jī)制的自動代理創(chuàng)建器:它會對容器所有的Advisor進(jìn)行掃描,實(shí)現(xiàn)類為DefaultAdvisorAutoProxyCreator
  • 基于Bean中的AspectJ注解標(biāo)簽的自動代理創(chuàng)建器:AnnotationAwareAspectJAutoProxyCreator

對應(yīng)的類繼承圖:


spring框架-認(rèn)識AOP(三)_aop_08



使用引介/引入功能實(shí)現(xiàn)為Bean引入新方法?

其實(shí)前置通知,后置通知,還是環(huán)繞通知,這些都很好理解。整個(gè)文章就引介/引入切面比較有趣,我們來實(shí)現(xiàn)試試吧

有個(gè)服務(wù)員的接口:

public interface Waiter {

// 向客人打招呼
void greetTo(String clientName);

// 服務(wù)
void serveTo(String clientName);
}

對應(yīng)的服務(wù)員接口實(shí)現(xiàn)類:

@Service("Waiter")
public class NaiveWaiter implements Waiter {
@Override
public void greetTo(String clientName) {
System.out.println("NaiveWaiter:greet to " + clientName + "...");
}

@Override
public void serveTo(String clientName) {
System.out.println("NaiveWaiter:serve to " + clientName + "...");
}
}

現(xiàn)在我想做的就是:想這個(gè)服務(wù)員可以充當(dāng)售貨員的角色,可以賣東西!當(dāng)然了,我肯定不會加一個(gè)賣東西的方法到Waiter接口上啦,因?yàn)檫@個(gè)是暫時(shí)的~

所以,我搞了一個(gè)售貨員接口:

public interface Seller {

int sell(String goods, String clientName);
}

售貨員接口實(shí)現(xiàn)類:

public class SmartSeller implements Seller {
@Override
public int sell(String goods, String clientName) {
System.out.println("SmartSeller: sell "+goods +" to "+clientName+"...");
return 100;
}
}

類圖是這樣子的:

spring框架-認(rèn)識AOP(三)_spring_09

現(xiàn)在我想干的就是:借助AOP的引入/引介切面,來讓我們的服務(wù)員也可以賣東西!

我們的引入/引介切面具體是這樣干的:

@Component
@Aspect
public class EnableSellerAspect {
@DeclareParents(value = "com.yiyu.kata.util.aop.NaiveWaiter", // 指定服務(wù)員具體的實(shí)現(xiàn)
defaultImpl = SmartSeller.class) // 售貨員具體的實(shí)現(xiàn)
public Seller seller; // 要實(shí)現(xiàn)的目標(biāo)接口
}

?寫了這個(gè)切面類后,大家猜猜有什么效果?

??????? 答案是:切面技術(shù)將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實(shí)現(xiàn)了Seller接口?。。?!

很神奇是不是,我們來測試下

public class TestAop {

@Test
public void testAop(){
ApplicationContext ac = new ClassPathXmlApplicationContext("dispatcher-servlet.xml","applicationContext-datasource.xml","applicationContext.xml");
Waiter waiter = ac.getBean("Waiter",Waiter.class);

// 調(diào)用服務(wù)員原有的方法
waiter.greetTo("虛竹");
waiter.serveTo("虛竹");

// 通過引介/引入切面已經(jīng)將waiter服務(wù)員實(shí)現(xiàn)了Seller接口,所以可以強(qiáng)制轉(zhuǎn)換
Seller seller = (Seller) waiter;
seller.sell("東西", "虛竹");

}
}

結(jié)果是: 神奇吧,哈哈哈

spring框架-認(rèn)識AOP(三)_aop_10

具體的調(diào)用過程是:

spring框架-認(rèn)識AOP(三)_連接點(diǎn)_11


總結(jié)


  1. AOP的底層實(shí)現(xiàn)是動態(tài)代理,動態(tài)代理包含JDK動態(tài)代理和CGLib代理。當(dāng)Bean實(shí)現(xiàn)接口時(shí),Spring就會用JDK的動態(tài)代理;當(dāng)Bean沒有實(shí)現(xiàn)接口時(shí),Spring使用CGlib是實(shí)現(xiàn);可以強(qiáng)制使用CGlib。
  2. 如果是單實(shí)例,推薦使用CGLib代理,如果是多例的我們最好使用JDK代理。因?yàn)镃GLib代理對象運(yùn)行速度要比JDK的代理對象要快
  3. AOP的層面是方法級別的,只能對方法進(jìn)入攔截
  4. 無論是XML方式還是注解方式,原理都一樣,我們用注解AOP就夠了,簡單好用
  5. 引介/引入切面是比較有趣的,具體實(shí)現(xiàn)上面舉例說明了,它可以達(dá)到用某個(gè)接口的方法,又不入侵代碼,低耦合的效果。具體妙處還待開發(fā),后面有發(fā)現(xiàn)再補(bǔ)充說明

?

本文摘自 :https://blog.51cto.com/u

開通會員,享受整站包年服務(wù)立即開通 >