什么是 AOP ?
在軟件業(yè),AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預(yù)編譯方式和運(yùn)行期間動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。
AOP是OOP的延續(xù),是軟件開發(fā)中的一個熱點(diǎn),也是Spring框架中的一個重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對業(yè)務(wù)邏輯的各個部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發(fā)的效率。

Spring AOP面向切面編程
接口調(diào)用耗時
現(xiàn)在我們有個接口要在日志中記錄接口耗時,我們會怎么做呢?一般我們會在接口開始和接口結(jié)束時獲取系統(tǒng)時間,然后二者一減就是接口耗時時間了。如下,在20行我們打印出接口耗時。
1@RestController2@Slf4j3publicclassLoginController?{4@Autowired5LoginService?loginService;6@RequestMapping("/login/{id}")7publicMap?login(@PathVariable("id")?Integer?id){8long?start?=?System.currentTimeMillis();9Map?result?=newHashMap<>();10result.put("status","0");11result.put("msg","失敗");12if(loginService.login(id))?{13result.put("status","1");14result.put("msg","成功");15}16long?end?=?System.currentTimeMillis();17log.info("耗時=>{}ms",end-start);18returnresult;19}20}
啟動類:
1@SpringBootApplication2publicclassSpringaopSbApplication{3publicstaticvoidmain(String[]?args){4SpringApplication.run(SpringaopSbApplication.class,args);5}6}
但是,如果所有接口都要記錄耗時時間呢?我們還按這種方式嗎?顯然不行,這種要在每個接口都加上同樣的代碼,而且如果后期你老板說去掉的話,你還有一個個的刪掉么?簡直是不可想象。。所以對于這種需求,其實是可以提煉出來的。我們想,統(tǒng)計接口的耗時時間,無非就是在接口的執(zhí)行前后記錄一下時然后相減打印出來即可,然后在這樣的地方去加入我們提煉出來的公共的代碼。這就好比在原來的業(yè)務(wù)代碼的基礎(chǔ)上,把原來的代碼橫切開來,在需要的地方加入公共的代碼,對原來的業(yè)務(wù)代碼起到功能增強(qiáng)的作用。這就是AOP的作用。
Spring AOP應(yīng)用場景 - 接口耗時記錄
下面我們來看看使用Spring AOP怎么滿足這個需求。
首先定義一個切面類TimeMoitor,其中pointCut()方法(修飾一組連接點(diǎn))是一個切點(diǎn),@Pointcut定義了一組連接點(diǎn)(使用表達(dá)式匹配)aroundTimeCounter()是要加入的功能,被@Around注解修飾,是一個環(huán)繞通知(Spring AOP通知的一種),其實就是上面說的在方法執(zhí)行前后記錄時間然后相減再打印出來耗時時間。
1@Aspect2@Component3@Slf4j4publicclassTimeMoitor{5@Pointcut(value?="execution(*?com.walking.springaopsb.controller.*.*(..))")6publicvoidpointCut(){}78@Around(value?="com.walking.springaopsb.aop.TimeMoitor.pointCut()")9publicObjectaroundTimeCounter(ProceedingJoinPoint?jpx){10longstart?=?System.currentTimeMillis();11Object?proceed?=null;12try{13proceed?=?jpx.proceed();14}catch(Throwable?throwable)?{15throwable.printStackTrace();16}17longend?=?System.currentTimeMillis();18log.info("耗時=>{}ms",end-start);19returnproceed;20}21}
然后在LoginController#login方法里我們就可以把日志打印耗時時間的代碼刪掉了。
1@RestController2@Slf4j3publicclassLoginController?{4@Autowired5LoginService?loginService;6@RequestMapping("/login/{id}")7publicMap?login(@PathVariable("id")?Integer?id){8Map?result?=newHashMap<>();9result.put("status","0");10result.put("msg","失敗");11if(loginService.login(id))?{12result.put("status","1");13result.put("msg","成功");14}15returnresult;16}17}
再比如,LoginController里若是還有別的方法,也一樣可以應(yīng)用到。使用Spring AOP的控制臺日志:

Spring AOP的原理
以上就是Spring AOP的一個應(yīng)用場景。那Spring AOP的原理是什么呢,用的什么技術(shù)呢?其實就是反射+動態(tài)代理。代理用的就是JDK動態(tài)代理或cglib,那么Spring AOP什么時候用JDK動態(tài)代理什么時候用cglib?默認(rèn)使用哪種?
源碼分析
那么我們就通過源碼來看一下吧。首先我們將啟動類改一下,方便我們對源碼debug。
啟動類:
1@ComponentScan("com.walking.springaopsb.*")2@EnableAspectJAutoProxy3publicclassSpringaopSbApplication{4publicstaticvoidmain(String[]?args){5AnnotationConfigApplicationContext?applicationContext?=newAnnotationConfigApplicationContext(SpringaopSbApplication.class);6LoginController?loginController?=?(LoginController)?applicationContext.getBean("loginController");7loginController.login(123);8}9}
我們修改了一下啟動類,把斷點(diǎn)打在第6行,啟動,往下走一步,看loginController這個變量。
我們發(fā)現(xiàn)是cglib方式產(chǎn)生的代理類,說明從IoC容器里拿到的是代理類,到底是初始化IoC容器時生成的還是獲取時產(chǎn)生的呢?我們也跟隨源碼來看一下吧。

要知道的是,我們現(xiàn)在要看的是第5行還是第6行生成的代理類。先看第6 行的getBean吧,進(jìn)入這個方法
org.springframework.context.support.AbstractApplicationContext#getBean(java.lang.String)。

然后我們只看有return的地方,在進(jìn)入這個getBean(
org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String))。

再看doGetBean(
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean)第120行sharedInstance已經(jīng)變成了代理類

所以我們進(jìn)入
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)方法看看,重新運(yùn)行,然后再加個斷點(diǎn),打到
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)里。

走過88行后,singletonObject變成了代理類,所以關(guān)鍵點(diǎn)就是在this.singletonObjects.get(beanName);我們可以看到singletonObjects 是一個ConcurrentHashMap。原來IoC的實例在這個ConcurrentHashMap里。private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);所以到這里我們就可以知道,這個代理類不是在getBean的時候生成的,即不是在啟動類的第6行生成的,那就是在第5行生成的,即在IoC容器初始化時產(chǎn)生的代理類。剛才那個ConcurrentHashMap是get的,那就肯定有put的時候。搜一下,還在這個類里,發(fā)現(xiàn)一個addSingleton方法,有倆地方調(diào)用,一個是在
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerSingleton調(diào)用的,一個是在
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String,?org.springframework.beans.factory.ObjectFactory)

那就把斷點(diǎn)打到這倆方法里,看會走到哪個,把別的斷點(diǎn)都去掉,當(dāng)然了,因為spring還有別的自己的實例要獲取,IoC容器里還有spring自己的實例,所以這個斷點(diǎn)要加上條件,當(dāng)beanName是loginController時進(jìn)去斷點(diǎn),這樣就方便多了。我們只保留第5行的代碼,因為getBean里面也會調(diào)getSingleton。

運(yùn)行啟動類,發(fā)現(xiàn)進(jìn)入了getSingleton方法,但Object singletonObject = this.singletonObjects.get(beanName);返回的為null,所以繼續(xù)往下走。發(fā)現(xiàn)在第127行返回了代理類,看這行的getObject方法又不知道是那個實現(xiàn)類,所以我們?nèi)プ笙陆强捶椒?,找一下這個方法的上一個方法,

就是左下角的第二個方法doGetBean,發(fā)現(xiàn)傳的是一個匿名內(nèi)部類,這個匿名內(nèi)部類里調(diào)的是
org.springframework.beans.factory.support.AbstractBeanFactory#createBean所以我們把斷點(diǎn)走完,進(jìn)到這個createBean里打斷點(diǎn),同樣加條件。斷點(diǎn)走過324行時變成代理類,即進(jìn)入
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean看看,打個斷點(diǎn)同樣加條件

斷點(diǎn)走過doCreateBean方法第380行后產(chǎn)生了代理類,所以把斷點(diǎn)打到這個
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object,?org.springframework.beans.factory.support.RootBeanDefinition)方法里,同樣加上條件,把別的斷點(diǎn)去掉,重新運(yùn)行。

當(dāng)走過1240行時已經(jīng)變成了代理類,所以把斷點(diǎn)打到這個
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization方法,同樣加上條件,把別的斷點(diǎn)去掉,重新運(yùn)行。

我們發(fā)現(xiàn),這里有個循環(huán),迭代的是
this.getBeanPostProcessors()的結(jié)果,我們看看這個是什么,是List,下圖是這個list的數(shù)據(jù)

? ??經(jīng)過幾次debug發(fā)現(xiàn)當(dāng)BeanPostProcessor為第四個元素時
AnnotationAwareAspectJAutoProxyCreator,result變成了代理類。關(guān)鍵就是在processor.postProcessAfterInitialization()這個方法,把斷點(diǎn)打進(jìn)去。

發(fā)現(xiàn)沒有
AnnotationAwareAspectJAutoProxyCreator這個實現(xiàn)類

那就看看這個
AnnotationAwareAspectJAutoProxyCreator的父類吧,Ctrl + Alt + Shift + U查看AnnotationAwareAspectJAutoProxyCreator的類圖依賴關(guān)系

發(fā)現(xiàn)AbstractAutoProxyCreator在上上個圖中,并且
AnnotationAwareAspectJAutoProxyCreator沒有重寫postProcessAfterInitialization方法,所以我們就看AbstractAutoProxyCreator的這個方法。

打斷點(diǎn)時發(fā)現(xiàn)Object bean不是代理類,那就看看
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary方法。在這個方法中調(diào)用了createProxy()創(chuàng)建代理類,進(jìn)去看下。

這個方法最后return proxyFactory.getProxy(getProxyClassLoader());進(jìn)入getProxy方法看看

所以createAopProxy()方法返回AopProxy類型的實例,有倆實現(xiàn)類可供創(chuàng)建CglibAopProxy和JdkDynamicAopProxy,及cglib和jdk動態(tài)代理兩種。

那么究竟創(chuàng)建哪一種,就是我們今天要看的關(guān)鍵之處,所以我們進(jìn)入createAopProxy()方法看看。

再進(jìn)去
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy方法看看。

config.isOptimize()和config.isProxyTargetClass()都默認(rèn)false這里創(chuàng)建logincontroller時config的數(shù)據(jù)如下

然后判斷targetClass是否為接口,這里我們的LoginController不是接口,就走了下面的return

所以Spring AOP使用JDK動態(tài)代理還是cglib取決于是否是接口,并沒有默認(rèn)的方式。我們改一下LoginController讓其實現(xiàn)接口

debug啟動,這時得到的代理類就是JDK動態(tài)代理。

為什么JDK動態(tài)代理必須是接口?
我們看一下這個問題,首先把LoginController改為實現(xiàn)ILoginBaseController接口,然后根據(jù)咱們上面的debug分析,在
org.springframework.aop.framework.ProxyFactory#getProxy(java.lang.ClassLoader)方法里createAopProxy().getProxy就是我們解決這個問題的入口,我們在getProxy里打上斷點(diǎn),
JdkDynamicAopProxy#getProxy(java.lang.ClassLoader)方法里斷點(diǎn)加到return語句上
returnProxy.newProxyInstance(classLoader, proxiedInterfaces,this);復(fù)制代碼
然后在Proxy.newProxyInstance進(jìn)來加斷點(diǎn),一步步往下走,在719行是關(guān)鍵

進(jìn)去

進(jìn)入proxyClassCache.get方法

然后第120行時關(guān)鍵,我們看這個apply方法是BiFunction接口的方法,有如下實現(xiàn)類,把鼠標(biāo)放到subKeyFactory上去發(fā)現(xiàn)是KeyFactory類型的,進(jìn)debug去看,沒有我們想要的
? ??

然后繼續(xù)往下走,有個while循環(huán),經(jīng)過幾次debug,發(fā)現(xiàn)這個循環(huán)是關(guān)鍵,具體看圖中標(biāo)注

我們需要進(jìn)這個get

進(jìn)來get之后發(fā)現(xiàn)有一行關(guān)鍵點(diǎn),就是下圖的230行,還是有個apply方法

剛才也說過了他有如下實現(xiàn)類

通過看valueFactory的類型知道他是ProxyClassFactory類型的,然后進(jìn)入這個類。他是Proxy類的一個靜態(tài)內(nèi)部類。
經(jīng)過多次debug發(fā)現(xiàn)639-643行是關(guān)鍵,其中第639行是獲取字節(jié)碼,然后第642行調(diào)用defineClass0(一個native方法)創(chuàng)建實例。

這里加個小插曲,為什么java的動態(tài)代理生成的代理類前面有個$Proxy呢,在這里可以得到答案。


回到剛才,字節(jié)碼我們看不懂,但是可以反編譯我們把639行拿出來寫個測試類
1publicclassTest{2publicstaticvoidmain(String[]?args)throwsException{3//獲取ILoginBaseController的字節(jié)碼4byte[]?bytes?=?ProxyGenerator.generateProxyClass("$Proxy#MyLoginController",newClass[]{ILoginBaseController.class});5//輸出到MyLoginController.class文件6FileOutputStream?fileOutputStream?=newFileOutputStream(newFile("MyLoginController.class"));7fileOutputStream.write(bytes);8fileOutputStream.flush();9fileOutputStream.close();10}11}復(fù)制代碼
我們會看到生成了指定的文件

看到這個文件你是不是就明白為啥JDK動態(tài)代理只能是接口了嗎?原因就是?java中是單繼承多實現(xiàn),$Proxy#MyLoginController類已經(jīng)繼承了Proxy類,所以不能在繼承別的類了只能實現(xiàn)接口,所以JDK動態(tài)代理只能是接口。
總結(jié)
通過以上的源碼分析我們弄清楚了,Spring AOP使用的代理機(jī)制了,并且是沒有默認(rèn)的代理,不是JDK動態(tài)代理就是cglib,以及為啥java的動態(tài)代理只能是接口。并且我們還看了一下spring的源碼,雖然看的不是非常的仔細(xì),但是通過這樣看源碼我們的理解更加的加深了,也鍛煉了看源碼的能力。
原文鏈接:https://www.toutiao.com/a6791061744048407052/?log_from=a0229b11b730e_1640604345622