??
AOP綜述
AOP是什么
Aspect-oriented programming 面向切面(方面)編程
AOP有什么用
可以把業(yè)務(wù)邏輯和系統(tǒng)級(jí)的服務(wù)進(jìn)行隔離
系統(tǒng)級(jí)的服務(wù)像系統(tǒng)的日志,事務(wù),權(quán)限驗(yàn)證等
AOP怎么用
動(dòng)態(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
如果沒(méi)有接觸過(guò)AOP(面向切面編程)的,可能一時(shí)間沒(méi)辦法理解,因?yàn)橹岸际怯肑AVA的面向?qū)ο缶幊蹋∣OP),沒(méi)關(guān)系,一步步跟我來(lái)學(xué)習(xí)AOP。
當(dāng)然如果您是技術(shù)大牛,既然您抽出時(shí)間查看了虛竹的文章,歡迎糾正文章中的不足和缺陷。
我們先來(lái)看下來(lái)這段《精通Spring4.x 企業(yè)應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)》提供的代碼片段
圖上的業(yè)務(wù)代碼被事務(wù)管理代碼和性能監(jiān)控代碼所包圍,而且學(xué)過(guò)JAVA的都知道,出現(xiàn)重復(fù)代碼了。出現(xiàn)重復(fù)代碼那就需要重構(gòu)代碼,代碼的重構(gòu)步驟一般有這兩種:
1、抽成方法
2、抽成類
抽取成類的方式我們稱之為:縱向抽取
- 通過(guò)繼承的方式實(shí)現(xiàn)縱向抽取
但是,虛竹發(fā)現(xiàn)這種抽取方式在圖上的業(yè)務(wù)中不適用了,因?yàn)槭聞?wù)管理代碼和性能監(jiān)控代碼是跟對(duì)應(yīng)的業(yè)務(wù)代碼是有邏輯關(guān)系的
現(xiàn)在縱向抽取的方式不行了,AOP希望將分散在各個(gè)業(yè)務(wù)邏輯代碼中相同的代碼通過(guò)橫向切割的方式抽取到一個(gè)獨(dú)立的模塊中!請(qǐng)看下圖
?
圖上應(yīng)該很好理解,把 重復(fù)性的非業(yè)務(wù)代碼橫向抽取出來(lái),這個(gè)簡(jiǎn)單。但是如何把這些非業(yè)務(wù)代碼融合到業(yè)務(wù)代碼中,達(dá)到跟之前的代碼同樣的效果。這個(gè)才是難題,就是AOP要解決的難題。
AOP的動(dòng)態(tài)代理:
- jdk動(dòng)態(tài)代理
- cglib動(dòng)態(tài)代理
Spring在選擇用JDK動(dòng)態(tài)代理還是CGLiB動(dòng)態(tài)代理的依據(jù):
?? (1)當(dāng)Bean實(shí)現(xiàn)接口時(shí),Spring就會(huì)用JDK的動(dòng)態(tài)代理
?? (2)當(dāng)Bean沒(méi)有實(shí)現(xiàn)接口時(shí),Spring使用CGlib是實(shí)現(xiàn)
(3)可以強(qiáng)制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)
?JDK代理和CGLib代理我們?cè)撚媚膫€(gè)
在《精通Spring4.x 企業(yè)應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)》給出了建議:
??? 如果是單例的我們最好使用CGLib代理,如果是多例的我們最好使用JDK代理
原因:
??? JDK在創(chuàng)建代理對(duì)象時(shí)的性能要高于CGLib代理,而生成代理對(duì)象的運(yùn)行性能卻比CGLib的低。
??? 如果是單例的代理,推薦使用CGLib
現(xiàn)在大家知道什么是AOP了吧:把非業(yè)務(wù)重復(fù)代碼橫向抽取出來(lái),通過(guò)動(dòng)態(tài)代理織入目標(biāo)業(yè)務(wù)對(duì)象函數(shù)中,實(shí)現(xiàn)跟之前一樣的代碼
AOP術(shù)語(yǔ)
aop術(shù)語(yǔ)不好理解,《精通Spring4.x 企業(yè)應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)》書(shū)上有提到這些術(shù)語(yǔ),我盡量解釋下這些術(shù)語(yǔ)
連接點(diǎn)(JointPoint)
一個(gè)類中的所有方法都可以被稱為連接點(diǎn)
切入點(diǎn)(PointCut)
上面也說(shuō)了,每個(gè)方法都可以稱之為連接點(diǎn),我們具體定位到某一個(gè)方法就成為切點(diǎn)。
用注解來(lái)說(shuō)明,被@Log定義的方法就是切入點(diǎn)
增強(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ò)展知識(shí)點(diǎn):
spring中的攔截器分兩種:
1、HandlerInterceptor
2、MethodInterceptor
HandlerInterceptor是springMVC項(xiàng)目中的攔截器,它攔截的目標(biāo)是請(qǐng)求的地址,比MethodInterceptor先執(zhí)行
MethodInterceptor是AOP項(xiàng)目中的攔截器,它攔截的目標(biāo)是方法,即使不是controller中的方法。
異常通知(ThrowsAdvice)
最適合的場(chǎng)景就是事務(wù)管理
引介通知(IntroductionInterceptor)
引介通知比較特殊,上面的四種通知都是在目標(biāo)方法上織入通知,引介通知是在目標(biāo)類添加新方法或?qū)傩?/p>
- 簡(jiǎn)單來(lái)說(shuō)就定義了是干什么的,具體是在哪干
- Spring AOP提供了5種Advice類型給我們:前置通知、后置通知、返回通知、異常通知、環(huán)繞通知給我們使用!
切面(Aspect)
切面由切入點(diǎn)和增強(qiáng)/通知組成
交叉在各個(gè)業(yè)務(wù)邏輯中的系統(tǒng)服務(wù),類似于安全驗(yàn)證,事務(wù)處理,日志記錄都可以理解為切面
織入(weaving)
就是將切面代碼插入到目標(biāo)對(duì)象某個(gè)方法的過(guò)程,相當(dāng)于我們?cè)趈dk動(dòng)態(tài)代理里面的 invocationHandler接口方法的內(nèi)容
用注解解釋:就是在目標(biāo)對(duì)象某個(gè)方法上,打上切面注解
目標(biāo)對(duì)象(target)
切入點(diǎn)和連接點(diǎn)所屬的類
顧問(wèn)(Advisor)
就是通知的一個(gè)封裝和延伸,可以將通知以更為復(fù)雜的方式織入到某些方法中,是將通知包裝為更復(fù)雜切面的裝配器。
Spring對(duì)AOP的支持
Spring提供了3種類型的AOP支持:
- 基于代理的經(jīng)典SpringAOP
- 需要實(shí)現(xiàn)接口,手動(dòng)創(chuàng)建代理
- 純POJO切面
- 使用XML配置,aop命名空間
- ?
?@AspectJ?
?注解驅(qū)動(dòng)的切面 - 使用注解的方式,這是最簡(jiǎn)潔和最方便的!
知識(shí)點(diǎn)
切面類型主要分成了三種
- 一般切面
- 切點(diǎn)切面
- 引介/引入切面
一般切面,切點(diǎn)切面,引介/引入切面說(shuō)明:
?一般切面一般不會(huì)直接使用,切點(diǎn)切面都是直接用就可以了。
這里重點(diǎn)說(shuō)明下引介/引入切面:
- 引介/引入切面是引介/引入增強(qiáng)的封裝器,通過(guò)引介/引入切面,可以更容易地為現(xiàn)有對(duì)象添加任何接口的實(shí)現(xiàn)!
繼承關(guān)系圖:
引介/引入切面有兩個(gè)實(shí)現(xiàn)類:
- DefaultIntroductionAdvisor:常用的實(shí)現(xiàn)類
- DeclareParentsAdvisor:用于實(shí)現(xiàn)AspectJ語(yǔ)言的DeclareParent注解表示的引介/引入切面
實(shí)際上,我們使用AOP往往是Spring內(nèi)部使用BeanPostProcessor幫我們創(chuàng)建代理。
這些代理的創(chuàng)建器可以分成三類:
- 基于Bean配置名規(guī)則的自動(dòng)代理創(chuàng)建器:BeanNameAutoProxyCreator
- 基于Advisor匹配機(jī)制的自動(dòng)代理創(chuàng)建器:它會(huì)對(duì)容器所有的Advisor進(jìn)行掃描,實(shí)現(xiàn)類為DefaultAdvisorAutoProxyCreator
- 基于Bean中的AspectJ注解標(biāo)簽的自動(dòng)代理創(chuàng)建器:AnnotationAwareAspectJAutoProxyCreator
對(duì)應(yīng)的類繼承圖:
使用引介/引入功能實(shí)現(xiàn)為Bean引入新方法?
其實(shí)前置通知,后置通知,還是環(huán)繞通知,這些都很好理解。整個(gè)文章就引介/引入切面比較有趣,我們來(lái)實(shí)現(xiàn)試試吧
有個(gè)服務(wù)員的接口:
public interface Waiter {
// 向客人打招呼
void greetTo(String clientName);
// 服務(wù)
void serveTo(String clientName);
}
對(duì)應(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)然了,我肯定不會(huì)加一個(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;
}
}
類圖是這樣子的:
現(xiàn)在我想干的就是:借助AOP的引入/引介切面,來(lái)讓我們的服務(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)接口
}
?寫(xiě)了這個(gè)切面類后,大家猜猜有什么效果?
??????? 答案是:切面技術(shù)將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實(shí)現(xiàn)了Seller接口?。。?!
很神奇是不是,我們來(lái)測(cè)試下
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("虛竹");
// 通過(guò)引介/引入切面已經(jīng)將waiter服務(wù)員實(shí)現(xiàn)了Seller接口,所以可以強(qiáng)制轉(zhuǎn)換
Seller seller = (Seller) waiter;
seller.sell("東西", "虛竹");
}
}
結(jié)果是: 神奇吧,哈哈哈
具體的調(diào)用過(guò)程是:
總結(jié)
- AOP的底層實(shí)現(xiàn)是動(dòng)態(tài)代理,動(dòng)態(tài)代理包含JDK動(dòng)態(tài)代理和CGLib代理。當(dāng)Bean實(shí)現(xiàn)接口時(shí),Spring就會(huì)用JDK的動(dòng)態(tài)代理;當(dāng)Bean沒(méi)有實(shí)現(xiàn)接口時(shí),Spring使用CGlib是實(shí)現(xiàn);可以強(qiáng)制使用CGlib。
- 如果是單實(shí)例,推薦使用CGLib代理,如果是多例的我們最好使用JDK代理。因?yàn)镃GLib代理對(duì)象運(yùn)行速度要比JDK的代理對(duì)象要快
- AOP的層面是方法級(jí)別的,只能對(duì)方法進(jìn)入攔截
- 無(wú)論是XML方式還是注解方式,原理都一樣,我們用注解AOP就夠了,簡(jiǎn)單好用
- 引介/引入切面是比較有趣的,具體實(shí)現(xiàn)上面舉例說(shuō)明了,它可以達(dá)到用某個(gè)接口的方法,又不入侵代碼,低耦合的效果。具體妙處還待開(kāi)發(fā),后面有發(fā)現(xiàn)再補(bǔ)充說(shuō)明
?
本文摘自 :https://blog.51cto.com/u