Spring Aop 詳解一
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??夢想家haima
來源 |? urlify.cn/7nymqu
Aop 是一個編程思想,最初是一個理論,最后落地成了很多的技術(shù)實現(xiàn)。
我們寫一個系統(tǒng),都希望盡量少寫點兒重復的東西。而很多時候呢,又不得不寫一些重復的東西。比如訪問某些方法的權(quán)限,執(zhí)行某些方法性能的日志,數(shù)據(jù)庫操作的方法進行事務(wù)控制。以上提到的,權(quán)限的控制,事務(wù)控制,性能監(jiān)控的日志 可以叫一個切面。像一個橫切面穿過這一些列需要控制的方法。通過aop編程,實現(xiàn)了對切面業(yè)務(wù)的統(tǒng)一處理。
以上是我對aop的一個總體概括
aop的原始實現(xiàn)
通過動態(tài)代理和反射實現(xiàn),又稱之為JDK動態(tài)代理
MyInterceptor.java
package?demo.aop.jdkproxy;
import?java.lang.reflect.InvocationHandler;
import?java.lang.reflect.Method;
/**
?*?攔截器
?*????1、目標類導入進來
?*????2、事務(wù)導入進來
?*????3、invoke完成
?*????????1、開啟事務(wù)
?*????????2、調(diào)用目標對象的方法
?*????????3、事務(wù)的提交
?*?@author?zd
?*
?*/
public?class?MyInterceptor?implements?InvocationHandler{
????private?Object?target;//目標類
????private?Transaction?transaction;
????public?MyInterceptor(Object?target,?Transaction?transaction)?{
????????super();
????????this.target?=?target;
????????this.transaction?=?transaction;
????}
????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)
????????????throws?Throwable?{
????????String?methodName?=?method.getName();
????????if("savePerson".equals(methodName)||"updatePerson".equals(methodName)
????????????????||"deletePerson".equals(methodName)){
????????????this.transaction.beginTransaction();//開啟事務(wù)
????????????method.invoke(target);//調(diào)用目標方法
????????????this.transaction.commit();//事務(wù)的提交
????????}else{
????????????method.invoke(target);
????????}
????????return?null;
????}
}
PersonDao.java
package?demo.aop.jdkproxy;
public?interface?PersonDao?{
????public?void?savePerson();
????public?void?updatePerson();
}
PersonDaoImpl.java
package?demo.aop.jdkproxy;
public?class?PersonDaoImpl?implements?PersonDao{
????public?void?savePerson()?{
????????System.out.println("save?person");
????}
????public?void?updatePerson()?{
????????//?TODO?Auto-generated?method?stub
????????System.out.println("update?person");
????}
}
Transaction.java
package?demo.aop.jdkproxy;
public?class?Transaction?{
????public?void?beginTransaction(){
????????System.out.println("begin?transaction");
????}
????public?void?commit(){
????????System.out.println("commit");
????}
}
JDKProxyTest
package?demo.aop.jdkproxy;
import?org.junit.Test;
import?java.lang.reflect.Proxy;
/**
?*?1、攔截器的invoke方法是在時候執(zhí)行的?
?*?????當在客戶端,代理對象調(diào)用方法的時候,進入到了攔截器的invoke方法
?*?2、代理對象的方法體的內(nèi)容是什么?
?*?????攔截器的invoke方法的內(nèi)容就是代理對象的方法的內(nèi)容
?*?3、攔截器中的invoke方法中的參數(shù)method是誰在什么時候傳遞過來的?
?*?????代理對象調(diào)用方法的時候,進入了攔截器中的invoke方法,所以invoke方法中的參數(shù)method就是
?*???????代理對象調(diào)用的方法
?*?@author?zd
?*
?*/
public?class?JDKProxyTest?{
????@Test
????public?void?testJDKProxy(){
????????/**
?????????*?1、創(chuàng)建一個目標對象
?????????*?2、創(chuàng)建一個事務(wù)
?????????*?3、創(chuàng)建一個攔截器
?????????*?4、動態(tài)產(chǎn)生一個代理對象
?????????*/
????????Object?target?=?new?PersonDaoImpl();
????????Transaction?transaction?=?new?Transaction();
????????MyInterceptor?interceptor?=?new?MyInterceptor(target,?transaction);
????????/**
?????????*?1、目標類的類加載器
?????????*?2、目標類實現(xiàn)的所有的接口
?????????*?3、攔截器
?????????*/
????????PersonDao?personDao?=?(PersonDao)?Proxy.newProxyInstance(target.getClass().getClassLoader(),
????????????????target.getClass().getInterfaces(),?interceptor);
????????//personDao.savePerson();
????????personDao.updatePerson();
????}
}
運行結(jié)果
begin?transaction
update?person
commit
原始實現(xiàn)部分,想必現(xiàn)在很少會有人再這么寫了。但這個對于我們理解Aop的思想很有幫助。
我們可以看到 代理對象 personDao調(diào)用的方法
updatePerson中沒有模擬事務(wù)的代碼,但最終代理對象卻輸出了begin transaction和commit
Spring AOP概念核心詞
切面(Aspect):一個關(guān)注點的模塊化,這個關(guān)注點可能會橫切多個對象。事務(wù)管理是J2EE應(yīng)用中一個關(guān)于橫切關(guān)注點的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式來實現(xiàn)。
連接點(Joinpoint):在程序執(zhí)行過程中某個特定的點,比如某方法調(diào)用的時候或者處理異常的時候。在Spring AOP中,一個連接點總是表示一個方法的執(zhí)行。
通知(Advice):在切面的某個特定的連接點上執(zhí)行的動作。其中包括了“around”、“before”和“after”等不同類型的通知(通知的類型將在后面部分進行討論)。許多AOP框架(包括Spring)都是以攔截器做通知模型,并維護一個以連接點為中心的攔截器鏈。
切入點(Pointcut):匹配連接點的斷言。通知和一個切入點表達式關(guān)聯(lián),并在滿足這個切入點的連接點上運行(例如,當執(zhí)行某個特定名稱的方法時)。切入點表達式如何和連接點匹配是AOP的核心:Spring缺省使用AspectJ切入點語法。
引入(Introduction):用來給一個類型聲明額外的方法或?qū)傩裕ㄒ脖环Q為連接類型聲明(inter-type declaration))。Spring允許引入新的接口(以及一個對應(yīng)的實現(xiàn))到任何被代理的對象。例如,你可以使用引入來使一個bean實現(xiàn)IsModified接口,以便簡化緩存機制。
目標對象(Target Object):被一個或者多個切面所通知的對象。也被稱做被通知(advised)對象。既然Spring AOP是通過運行時代理實現(xiàn)的,這個對象永遠是一個被代理(proxied)對象。
AOP代理(AOP Proxy):AOP框架創(chuàng)建的對象,用來實現(xiàn)切面契約(例如通知方法執(zhí)行等等)。在Spring中,AOP代理可以是JDK動態(tài)代理或者CGLIB代理。
織入(Weaving):把切面連接到其它的應(yīng)用程序類型或者對象上,并創(chuàng)建一個被通知的對象。這些可以在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其他純Java AOP框架一樣,在運行時完成織入。
上面為官方文檔,有的地方還是很難讀懂,畢竟是純概念。下面我用自己的話來翻譯一下,如果有不對的地方,請指正
切面?統(tǒng)一處理的業(yè)務(wù),比如上文提到的 權(quán)限控制,事務(wù)處理
連接點?原本被執(zhí)行的方法,一個執(zhí)行的方法可能被多個切面橫切
通知?切面方法的執(zhí)行,比如權(quán)限控制的具體執(zhí)行過程(權(quán)限控制可以用前置通知@Before)
切入點?切入點的概念通常和連接點概念容易分不清,切入點其實是一個規(guī)則,也就是說什么樣的情況下(滿足什么規(guī)則),
就會去執(zhí)行鏈接點的那些方法,這個規(guī)則就是切入點,這種規(guī)則用切入點表達式去制定引入(Introduction)?被代理的對象可以引入新接口,通過默認的實現(xiàn)類,讓這個被代理的類增強
目標對象?就是被切面執(zhí)行了的對象
AOP代理?代理包括jdk代理和cglib代理,是aop底層實現(xiàn)過程
織入?就是切面中的方法完成加載執(zhí)行的過程
這里有8個概念,但真正要完成aop的理解,還不得不再引入兩個概念。
被代理對象?我們可以看到,上面說到目標對象
永遠是一個被代理的對象,也是被通知的對象。代理對象?代理對象呢, 就是最后通知后,生成的對象。
切入點表達式
execution
用于匹配指定類型內(nèi)的方法執(zhí)行,匹配的是方法,可以確切到方法
execution(modifiers-pattern??ret-type-pattern?declaring-type-pattern??name-pattern?(param-pattern)
??????????throws-pattern?)
??????????
modifiers-pattern?修飾符表達式???:public?protect?private?,可缺省,表示不限制
ret-type-pattern????返回值表達式???如?String代表返回值為String?,*代表任意返回值都可以???必填字段
declaring-type-pattern??類型,可以由完整包名加類名組成???可以只寫包名加.*限定包下的所有類???可缺省,表示不限制
name-pattern????方法名表達式,可以由*統(tǒng)配所有字符???必填字段
param-pattern???參數(shù)列表,可以用..來表示所有的方法??必填字段
execution(public?*?*?(..))???//所有public的方法
execution(*?set*(..))??//所有set開頭的方法
execution(?*?com.xyz.service.AccountService.*?(..)?)?//AccountService的所有方法,如果AccountService是接口,指實現(xiàn)了這個接口的所有方法
execution(*?com.xyz.service.*.*(..))?//com.xyz.service包下的所有類的所有方法
execution(*?com.xyz.service..*.*(..))?//com.xyz.service包及**子包**下的所有類的所有方法
within
用于匹配指定類型內(nèi)的方法執(zhí)行,匹配的是類型內(nèi)的方法,類型下的所有方法
within?(com.xyz.service.*)?//?com.xyz.service包下面的所有類的所有方法
within?(com.xyz.service..*)?//?com.xyz.service包及**子包**下面的所有類的所有方法
within?(com.xyz.service.impl.UserServiceImpl)?//?UserServiceImpl類下面所有方法
this
用于匹配當前AOP代理對象類型的執(zhí)行方法,在前文中引入(Introduction)的代理對象使用,可以注入代理對象bean
指定spring容器中特定名稱的bean的所有方法為連接點target
用于匹配當前目標對象類型的執(zhí)行方法,可以注入目標對象,被代理的對象args
用于匹配當前執(zhí)行的方法傳入的參數(shù)為指定類型的執(zhí)行方法,可以注入連接點(方法)的參數(shù)列表@target 暫未解讀
@args 暫未解讀
@within 暫未解讀
@annotation 暫未解讀
代碼實戰(zhàn)
5種通知的案例
DemoAspect.java
定義切面,及通知,這里為了測試更多的案例,表達式切到AdviceKindTestController.java
package?demo.aop.aspect;
import?lombok.extern.slf4j.Slf4j;
import?org.aspectj.lang.ProceedingJoinPoint;
import?org.aspectj.lang.annotation.*;
import?org.springframework.stereotype.Component;
@Aspect
@Slf4j
@Component?//必須是個bean
public?class?DemoAspect?{
????//前置通知
????@Before("execution?(*?demo.aop.controller.AdviceKindTestController.*(..))")
????public?void?auth()?{
????????log.info("前置通知,假裝校驗了個權(quán)限");
????}
????//后置通知
????@AfterReturning("execution?(*?demo.aop.controller.AdviceKindTestController.*(..))")
????public??Object??afterSomething(){
????????log.info("后置通知,不太清楚運用場景");
????????return?"ok";
????}
????//環(huán)繞通知
????//如果環(huán)繞通知?不返回執(zhí)行結(jié)果??方法不會返回任何結(jié)果,導致接口拿不到任何數(shù)據(jù)
????//所以一定把proceed?返回
????//ProceedingJoinPoint?是?JoinPoint的子類,僅當環(huán)繞通知的時候,可以注入ProceedingJoinPoint的連接點
????@Around("execution?(*?demo.aop.controller.AdviceKindTestController.*(..))")
????public??Object??getMethodTime(ProceedingJoinPoint?point)?throws?Throwable?{
????????log.info("環(huán)繞通知,統(tǒng)計方法耗時,方法執(zhí)行前");
????????Long?beforeMillis?=?System.currentTimeMillis();
????????Object?proceed?=?point.proceed();
????????Long?taketimes=?System.currentTimeMillis()-beforeMillis;
????????log.info(String.format("該方法用時%s毫秒",taketimes));
????????return?proceed;
????}
????//異常通知
????@AfterThrowing("execution?(*?demo.aop.controller.AdviceKindTestController.*(..))")
????public?void?throwSomething()?{
????????log.info("異常通知,只有異常了才會通知。具體場景,不是特別了解");
????}
????//最終通知
????@After("execution?(*?demo.aop.controller.AdviceKindTestController.*(..))")
????public?void?closeSomething()?{
????????log.info("最終通知,官網(wǎng)說,可以用來回收某些資源。無論發(fā)不發(fā)生異常,都會被執(zhí)行");
????}
}
AdviceKindTestController.java
測試用的接口類
package?demo.aop.controller;
import?org.springframework.web.bind.annotation.GetMapping;
import?org.springframework.web.bind.annotation.RestController;
@RestController
public?class?AdviceKindTestController?{
????@GetMapping("/advice")?//http://localhost:8080/advice
????public?String?test()?throws?InterruptedException?{
????????Thread.sleep(4);
????????return?"ok";
????}
????@GetMapping("/advice/throwing")?//http://localhost:8080/advice/throwing
????public?String?test2(){
????????int?i=1/0;
????????return?"ok";
????}
}
訪問?http://localhost:8080/advice?后臺輸出為
2020-10-18?11:24:01.201?????????????????:?環(huán)繞通知,統(tǒng)計方法耗時,方法執(zhí)行前
2020-10-18?11:24:01.201?????????????????:?前置通知,假裝校驗了個權(quán)限
2020-10-18?11:24:01.201?????????????????:?該方法用時6毫秒
2020-10-18?11:24:01.202?????????????????:?最終通知,官網(wǎng)說,可以用來回收某些資源。無論發(fā)不發(fā)生異常,都會被執(zhí)行
2020-10-18?11:24:01.202?????????????????:?后置通知,不太清楚運用場景
執(zhí)行順序
-?環(huán)繞通知的前面部分
-?前置通知
-?環(huán)繞通知的后面部分
-?最終通知
-?后置通知
訪問?http://localhost:8080/advice/throwing?后臺輸出為
2020-10-18?11:30:34.935?????????????????:?環(huán)繞通知,統(tǒng)計方法耗時,方法執(zhí)行前
2020-10-18?11:30:34.935?????????????????:?前置通知,假裝校驗了個權(quán)限
2020-10-18?11:30:34.936?????????????????:?最終通知,官網(wǎng)說,可以用來回收某些資源。無論發(fā)不發(fā)生異常,都會被執(zhí)行
2020-10-18?11:30:34.936?????????????????:?異常通知,只有異常了才會通知。具體場景,不是特別了解
java.lang.ArithmeticException:?/?by?zero????//test2()方法拋出了異常
執(zhí)行順序如下,我們可以看到因為接口出現(xiàn)了異常,所以后置通知并沒執(zhí)行,環(huán)繞通知的后面部分也沒執(zhí)行,但最終通知和異常通知被執(zhí)行
-?環(huán)繞通知的前面部分
-?前置通知
-?最終通知
-?異常通知
粉絲福利:108本java從入門到大神精選電子書領(lǐng)取
???
?長按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料以及 可以進入java1234官方微信群
感謝點贊支持下哈?
