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