1. 程式人生 > >Spring MVC系列-(5) AOP

Spring MVC系列-(5) AOP

![Spring.png](http://ww1.sinaimg.cn/large/a18449c6gy1gco2xb0bj9j20nn0cet8m.jpg) [toc] ## 5 AOP ### 5.1 什麼是AOP AOP(Aspect-Oriented Programming,面向切面程式設計),可以說是OOP(Object-Oriented Programing,面向物件程式設計)的補充和完善。 OOP引入封裝、繼承和多型性等概念來建立一種物件層次結構,用以模擬公共行為的一個集合。當我們需要為分散的物件引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌程式碼往往水平地散佈在所有物件層次中,而與它所散佈到的物件的核心功能毫無關係。對於其他型別的程式碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的程式碼被稱為橫切(cross-cutting)程式碼,在OOP設計中,它導致了大量程式碼的重複,而不利於各個模組的重用。 **而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術,剖解開封裝的物件內部,並將那些影響了多個類的公共行為封裝到一個可重用模組,並將其名為“Aspect”,即切面。所謂“切面”,簡單地說,就是將那些與業務無關,卻為業務模組所共同呼叫的邏輯或責任封裝起來,便於減少系統的重複程式碼,降低模組間的耦合度,並有利於未來的可操作性和可維護性。** 實現AOP的技術,主要分為兩大類:一是採用動態代理技術,利用攔截方法的方式,對該方法進行裝飾,以取代原有物件行為的執行;二是採用靜態織入的方 ### 5.2 AOP術語 **1. 連線點(Join point)** 連線點是在應用執行過程中能夠插入切面的一個點。這個點可以是類的某個方法呼叫前、呼叫後、方法丟擲異常後等。 **2. 通知(Advice)** 在特定的連線點,AOP框架執行的動作。 Spring AOP 提供了5種類型的通知: * 前置通知(Before):在目標方法被呼叫之前呼叫通知功能。 * 後置通知(After):在目標方法完成之後呼叫通知,無論該方法是否發生異常。 * 後置返回通知(After-returning):在目標方法成功執行之後呼叫通知。 * 後置異常通知(After-throwing):在目標方法丟擲異常後呼叫通知。 * 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為。 **3. 切點(Poincut)** 具體定位的連線點:上面也說了,每個方法都可以稱之為連線點,我們具體定位到某一個方法就成為切點。 >**切點與連線點**:切點和連線點不是一對一的關係,一個切點匹配多個連線點,切點通過 org.springframework.aop.Pointcut 介面進行描述,它使用類和方法作為連線點的查詢條件。每個類都擁有多個連線點,例如 ArithmethicCalculator類的所有方法實際上都是連線點。 **4. 切面(Aspect)** **切面由切點和通知組成**,它既包括了橫切邏輯的定義、也包括了連線點的定義。 **5. 織入(Weaving)** **織入描述的是把切面應用到目標物件來建立新的代理物件的過程。** Spring AOP 的切面是在執行時被織入,原理是使用了動態代理技術。Spring支援兩種方式生成代理物件:JDK動態代理和CGLib,預設的策略是如果目標類是介面,則使用JDK動態代理技術,否則使用Cglib來生成代理。 **6. 引入(Introduction)** 新增方法或欄位到被通知的類。 Spring允許引入新的介面到任何被通知的物件。例如,你可以使用一個引入使任何物件實現 IsModified介面,來簡化快取。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現的介面。 ### 5.3 AOP使用 首先新建業務邏輯類,該類實現了基本的除法操作: ``` public class Calculator { //業務邏輯方法 public int div(int i, int j) { System.out.println("--------"); return i/j; } } ``` 現在需要實現:在div()方法執行之前, 記錄一下日誌, 執行後也記錄一下,執行出異常,也列印一下。 因此可以使用AOP來完成日誌的功能,新建日誌切面類: 在定義切面類的時候,需要注意如下幾點: * 在類上加上`@Aspect`宣告為切面類。 * 可以使用PointCut將相同的切點進行統一定義,其他地方直接引用即可。 ``` @Aspect public class LogAspects { @Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))") public void pointCut(){}; //@before代表在目標方法執行前切入, 並指定在哪個方法前切入 @Before("pointCut()") public void logStart(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature().getName()+"除法執行....引數列表是:{"+Arrays.asList(joinPoint.getArgs())+"}"); } @After("pointCut()") public void logEnd(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature().getName()+"除法結束......"); } @AfterReturning(value="pointCut()",returning="result") public void logReturn(Object result){ System.out.println("除法正常返回......執行結果是:{"+result+"}"); } @AfterThrowing(value="pointCut()",throwing="exception") public void logException(Exception exception){ System.out.println("執行異常......異常資訊是:{"+exception+"}"); } /*@Around("pointCut()") public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("@Arount:執行目標方法之前..."); Object obj = proceedingJoinPoint.proceed();//相當於開始調div地 System.out.println("@Arount:執行目標方法之後..."); return obj; }*/ } ``` 有了以上操作, 我們還需要將切面類和被切面的類, 都加入到容器中,注意需要加上`@EnableAspectJAutoProxy`開啟AOP。 ``` /* * 日誌切面類的方法需要動態感知到div()方法執行, * 通知方法: * 前置通知:logStart(); 在我們執行div()除法之前執行(@Before) * 後置通知:logEnd();在我們目標方法div執行結束之後 ,不管有沒有異常(@After) * 返回通知:logReturn();在我們的目標方法div正常返回值後執行(@AfterReturning) * 異常通知:logException();在我們的目標方法div出現異常後執行(@AfterThrowing) * 環繞通知:動態代理, 需要手動執行joinPoint.procced()(其實就是執行我們的目標方法div,), 執行之前div()相當於前置通知, 執行之後就相當於我們後置通知(@Around) */ @Configuration @EnableAspectJAutoProxy public class Cap10MainConfig { @Bean public Calculator calculator(){ return new Calculator(); } @Bean public LogAspects logAspects(){ return new LogAspects(); } } ``` 使用JoinPoint可以拿到相關的內容, 比如方法名, 引數 ![Pictu222re1.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbpdlvhys8j20rw04sac1.jpg) 那麼方法正常返回, 怎麼拿方法的返回值呢? ![Pictu4444re1.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbpdmieawqj20l2032aaw.jpg) 那麼如果是異常呢?定義 ![Pictur55551.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbpdmulonyj20l0030dhc.jpg) 下面是測試程式,注意需要使用從IOC容器中取出Bean,否則直接new物件進行操作,AOP、無法生效。 ![Pict111111ure1.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbpcjfy280j20gs05hjsi.jpg) 從下面的執行結果可以看到,AOP生效,日誌功能正常: ![22.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbpckrle5gj209d07n3zi.jpg) >小結: AOP看起來很麻煩, 只要3步就可以了: >1, 將業務邏輯元件和切面類都加入到容器中, 告訴spring哪個是切面類(@Aspect) >2, 在切面類上的每個通知方法上標註通知註解, 告訴Spring何時執行(寫好切入點表示式,參照官方文件) >3, 開啟基於註解的AOP模式 @EableXXXX ### 5.4 Java動態代理 Spring AOP的實現是基於動態代理,在介紹具體實現細節之前,本節先介紹動態代理的原理。 動態代理是一種方便執行時動態構建代理、動態處理代理方法呼叫的機制。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在兩者之間起到中介的作用(可類比房屋中介,房東委託中介銷售房屋、簽訂合同等)。 所謂動態代理,就是實現階段不用關心代理誰,而是在執行階段才指定代理哪個一個物件(不確定性)。如果是自己寫代理類的方式就是靜態代理(確定性)。 很多場景都是利用類似機制做到的,比如用來包裝 RPC 呼叫、面向切面的程式設計(AOP)。 (動態)代理模式主要涉及三個要素: * 抽象類介面 * 被代理類(具體實現抽象介面的類) * 動態代理類:實際呼叫被代理類的方法和屬性的類 **實現方式:** 實現動態代理的方式很多,比如 JDK 自身提供的動態代理,就是主要利用了反射機制。還有其他的實現方式,比如利用位元組碼操作機制,類似 ASM、CGLIB(基於 ASM)、Javassist 等。 舉例,常可採用的JDK提供的動態代理介面InvocationHandler來實現動態代理類。其中invoke方法是該介面定義必須實現的,它完成對真實方法的呼叫。通過InvocationHandler介面,所有方法都由該Handler來進行處理,即所有被代理的方法都由InvocationHandler接管實際的處理任務。此外,我們常可以在invoke方法實現中增加自定義的邏輯實現,實現對被代理類的業務邏輯無侵入。 >反射機制是 Java 語言提供的一種基礎功能,賦予程式在執行時自省(introspect,官方用語)的能力。通過反射我們可以直接操作類或者物件,比如獲取某個物件的類定義,獲取類宣告的屬性和方法,呼叫方法或者構造物件,甚至可以執行時修改類定義。 #### JDK動態代理 代理模式的形式如下圖所示: ![Screen Shot 2020-02-12 at 8.28.31 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbtwioiufvj20r00b2q93.jpg) 代理模式最大的特點就是代理類和實際業務類實現同一個介面(或繼承同一父類),代理物件持有一個實際物件的引用,外部呼叫時操作的是代理物件,而在代理物件的內部實現中又會去呼叫實際物件的操作。Java動態代理其實內部也是通過Java反射機制來實現的,即已知的一個物件,然後在執行時動態呼叫其方法,這樣在呼叫前後作一些相應的處理。 下面舉例說明: **1. 靜態代理** 若代理類在程式執行前就已經存在,那麼這種代理方式被成為靜態代理 ,這種情況下的代理類通常都是我們在Java程式碼中定義的。 通常情況下, 靜態代理中的代理類和委託類會實現同一介面或是派生自相同的父類。 ``` public interface Sell { void sell(); void ad(); } ``` Vendor的定義如下: ``` public class Vendor implements Sell{ @Override public void sell() { System.out.println("In sell method"); } @Override public void ad() { System.out.println("ad method"); } } ``` BusinessAgent的定義如下: ``` /** * 靜態代理,通過聚合來實現,讓代理類有一個委託類的引用即可。 * */ public class BusinessAgent implements Sell{ private Sell vendor; public BusinessAgent(Sell vendor) { this.vendor = vendor; } @Override public void sell() { // 一些業務邏輯 System.out.println("before sell"); vendor.sell(); System.out.println("after sell"); } @Override public void ad() { // 一些業務邏輯 System.out.println("before ad"); vendor.ad(); System.out.println("after ad"); } } ``` 由上面的程式碼可以看到, 通過靜態代理,一方面無需修改Vendor的程式碼就可以加入一些業務處理邏輯;另一方面,實現了客戶端與委託類的解耦。但這種靜態代理的侷限在於,必須在執行前編寫好代理類,如果委託類的方法較多,在新增業務邏輯時的工作量較大,需要對每個方法單獨新增。 **2. 動態代理** 代理類在程式執行時建立的代理方式被成為 動態代理。 也就是說,這種情況下,代理類並不是在Java程式碼中定義的,而是在執行時根據我們在Java程式碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類的函式。 同樣還是上面的例子,需要在委託類的每個方法前後加入一些處理邏輯,在動態代理的實現中,首先需要定義一個位於代理類與委託類之間的中介類,這個中介類被要求實現InvocationHandler介面,這個介面的定義如下: ``` /** * 呼叫處理程式 * 代理類物件作為proxy引數傳入,引數method標識了我們具體呼叫的是代理類的哪個方法,args為這個方法的引數 */ public interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args); } ``` 中介類必須實現InvocationHandler介面,作為呼叫處理器”攔截“對代理類方法的呼叫。 ``` public class DynamicProxy implements InvocationHandler { // obj為委託物件 private Object object; public DynamicProxy(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); Object result = method.invoke(object, args); System.out.println("after"); return result; } } ``` 在使用時需要動態生成代理類,具體如下: ``` public class Main { public static void main(String[] args) { /* Static proxy */ Vendor vendor = new Vendor(); BusinessAgent businessAgent = new BusinessAgent(vendor); businessAgent.sell(); businessAgent.ad(); /* Dynamic proxy */ DynamicProxy inter = new DynamicProxy(new Vendor()); //加上這句將會產生一個$Proxy0.class檔案,這個檔案即為動態生成的代理類檔案 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); // 獲取代理例項sell /** * public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) * throws IllegalArgumentException * loader:定義了代理類的ClassLoder; * interfaces:代理類實現的介面列表; * h:呼叫處理器,也就是我們上面定義的實現了InvocationHandler介面的類例項. */ Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter)); // 通過代理類物件呼叫代理方法,實際上會轉到invoke方法呼叫 sell.sell(); sell.ad(); } } ``` >**總結**: 動態代理的原理就是,首先通過newProxyInstance方法獲取代理類例項,而後我們便可以通過這個代理類例項呼叫代理類的方法,對代理類的方法的呼叫實際上都會呼叫中介類(呼叫處理器)的invoke方法,在invoke方法中我們呼叫委託類的相應方法,並且可以新增自己的處理邏輯。 #### CGLIB動態代理 CGLIB(Code Generation Library)是一個基於ASM的位元組碼生成庫,它允許我們在執行時對位元組碼進行修改和動態生成。CGLIB通過繼承方式實現代理。 來看示例,假設我們有一個沒有實現任何介面的類HelloConcrete: ``` public class HelloConcrete { public String sayHello(String str) { return "HelloConcrete: " + str; } } ``` 因為沒有實現介面該類無法使用JDK代理,通過CGLIB代理實現如下: * 實現一個MethodInterceptor,方法呼叫會被轉發到該類的intercept()方法。 * 在需要使用HelloConcrete的時候,通過CGLIB動態代理獲取代理物件。 ``` // CGLIB動態代理 // 1. 首先實現一個MethodInterceptor,方法呼叫會被轉發到該類的intercept()方法。 class MyMethodInterceptor implements MethodInterceptor{ ... @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { logger.info("You said: " + Arrays.toString(args)); return proxy.invokeSuper(obj, args); } } // 2. 然後在需要使用HelloConcrete的時候,通過CGLIB動態代理獲取代理物件。 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(HelloConcrete.class); enhancer.setCallback(new MyMethodInterceptor()); HelloConcrete hello = (HelloConcrete)enhancer.create(); System.out.println(hello.sayHello("I love you!")); // 輸出結果如下 // 日誌資訊: You said: [I love you!] // HelloConcrete: I love you! ``` 通過CGLIB的Enhancer來指定要代理的目標物件、實際處理代理邏輯的物件,最終通過呼叫create()方法得到代理物件,**對這個物件所有非final方法的呼叫都會轉發給MethodInterceptor.intercept()方法,在intercept()方法裡我們可以加入任何邏輯,比如修改方法引數,加入日誌功能、安全檢查功能等**;通過呼叫MethodProxy.invokeSuper()方法,我們將呼叫轉發給原始物件,具體到本例,就是HelloConcrete的具體方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很類似,都是方法呼叫的中轉站。 >CGLIB是通過繼承(`如上面例子中的enhancer.setSuperclass(HelloConcrete.class)`)實現代理,由於final型別不能有子類,所以CGLIB不能代理final型別,遇到這種情況會丟擲異常。 ### 5.5 AOP原理深入分析 AOP的原理簡單來講,利用動態代理,在IOC容器初始化時,建立Bean的代理類;在代理方法被呼叫時,代理類會攔截方法的呼叫,並在之前或者之後插入切面方法,以此實現AOP的目標。 接下來會從以下幾方面深入分析AOP的原理: - AnnotationAwareAspectJAutoProxyCreator註冊 - AnnotationAwareAspectJAutoProxyCreator分析 - AOP流程分析 #### AnnotationAwareAspectJAutoProxyCreator註冊 在之前使用AOP時,為了啟用AOP,需要在配置類中,宣告`@EnableAspectJAutoProxy`的註解,這個註解的功能就是註冊`AnnotationAwareAspectJAutoProxyCreator`。下面具體分析這個元件是如何註冊的。 ![Screen Shot 2020-02-13 at 11.47.39 AM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbumpbeny2j20f40d4gn5.jpg)@w=250 進入`@EnableAspectJAutoProxy`的原始碼中,可以看到該類引入了`AspectJAutoProxyRegistrar`, ``` @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { //proxyTargetClass屬性,預設false,採用JDK動態代理織入增強(實現介面的方式);如果設為true,則採用CGLIB動態代理織入增強 boolean proxyTargetClass() default false; //通過aop框架暴露該代理物件,aopContext能夠訪問 boolean exposeProxy() default false; } ``` 在`AspectJAutoProxyRegistrar`中, 可以看到實現了ImportBeanDefinitionRegistrar介面,這個介面之前也有介紹,能給容器中自定義註冊元件。 ``` class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { /** * Register, escalate, and configure the AspectJ auto proxy creator based on the value * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing * {@code @Configuration} class. */ @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } } } ``` 重點關注`AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);`,這一步將會註冊`AnnotationAwareAspectJAutoProxyCreator`。下面進入原始碼, ![Pict111ure1.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbun9ltq7pj20o006l0uo.jpg) ![Pictur222e1.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbun9zgx02j20o00etn2j.jpg) 程式的邏輯很清晰, - 如果(registry.containsBeanDefinition(ATUO_PROXY_CREATOR_BEAN_NAME))也就是容器中bean已經有了 internalAutoProxyCreator, 執行內部內容,返回null。 - 如果沒有,建立AnnotationAwareAspectJAutoProxyCreator資訊; 把此bean註冊在registry中. 做完後, 相當於給容器中註冊internalAutoProxyCreator元件, 該元件型別為AnnotationAwareAspectJAutoProxyCreator.class。( 注意這裡ATUO_PROXY_CREATOR_BEAN_NAME值為internalAutoProxyCreator) >**綜上分析,@EnableAspectJAutoProxy的功能就是,利用其中的AspectJAutoProxyRegistrar給我們容器中註冊一個AnnotationAwareAspectJAutoProxyCreator元件,這是後續建立增強Bean的基礎。** #### AnnotationAwareAspectJAutoProxyCreator分析 AnnotationAwareAspectJAutoProxyCreator的類層次結構如下圖所示, ![CDA7BFAFE6E8CF7B8DD9E99993FD64D6.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbv2c4r97gj21vc0uotdf.jpg) 繼承關係為: * AnnotationAwareAspectJAutoProxyCreator * ->AspectJAwareAdvisorAutoProxyCreator * --->AbstractAdvisorAutoProxyCreator * ----->AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware 其中的`SmartInstantiationAwareBeanPostProcessor`是Bean的後置處理器,同時也實現了`BeanFactoryAware`可以在容器初始化時,將beanFactory傳進來進行相關操作。 **由上述分析可知,AnnotationAwareAspectJAutoProxyCreator既具有BeanPostProcessor特點, 也實現了BeanFactoryAware介面,方便操作BeanFactory。** #### AOP實現流程 上面已經介紹了AOP過程中,核心的AnnotationAwareAspectJAutoProxyCreator元件,接下來對整個AOP的流程進行梳理,主要分為如下4個步驟: 1. 註冊`AnnotationAwareAspectJAutoProxyCreator`的BeanDefinition 2. 建立`AnnotationAwareAspectJAutoProxyCreator`,並加入到BeanFactory 3. 利用`AnnotationAwareAspectJAutoProxyCreator`攔截Bean的初始化,建立增強的Bean 4. 增強Bean的呼叫過程 IOC容器初始化的入口是如下的refresh()函式,上面1,2,3步驟,分別發生在如下標出的3個函式中,下面分別對這三個函式進行詳細介紹。 ![Screen Shot 2020-02-15 at 10.56.17 AM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbwzgtx2vgj20zw10wqe9.jpg) **1. 註冊`AnnotationAwareAspectJAutoProxyCreator`的BeanDefinition** 這一步主要是通過`invokeBeanFactoryPostProcessors(beanFactory)`函式,新增`AnnotationAwareAspectJAutoProxyCreator`的定義,最終呼叫的函式如下: ![Screen Shot 2020-02-15 at 12.46.45 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbwzmgsqh5j218m0oqaid.jpg) 註冊的元件型別為AnnotationAwareAspectJAutoProxyCreator.class,元件名稱ATUO_PROXY_CREATOR_BEAN_NAME值為internalAutoProxyCreator。 下面是呼叫棧: ![Screen Shot 2020-02-15 at 12.54.46 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbwzuwcgm7j21e00ckq7x.jpg) **2. 建立`AnnotationAwareAspectJAutoProxyCreator`,並加入到BeanFactory** 這一步入口是`registerBeanPostProcessors(beanFactory)`,進入該函式後,會跳轉到如下的核心函式中進行beanPostProcess的例項化。注意到之前提到過,`AnnotationAwareAspectJAutoProxyCreator`實現了BeanPostProcess介面,所以可以將其當成一個正常的後置處理器來進行例項化。 ![Screen Shot 2020-02-15 at 1.10.12 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx0avcjmfj21by0aa799.jpg) 從下面的debug資訊可以看到,在這一步中,容器需要例項化4個後置處理器,其中最後一個就是我們關注的`AnnotationAwareAspectJAutoProxyCreator`。 ![Screen Shot 2020-02-15 at 1.12.50 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx0ep3jluj21b20d210u.jpg) 整個初始化後置處理器的流程,可以分為如下幾步: 1)先獲取ioc容器已經定義了的需要建立物件的所有BeanPostProcessor 3)優先註冊實現了PriorityOrdered介面的BeanPostProcessor; 4)再給容器中註冊實現了Ordered介面的BeanPostProcessor; 5)註冊沒實現優先順序介面的BeanPostProcessor; ![Screen Shot 2020-02-15 at 1.18.01 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx0izo4mrj211q0oqn55.jpg) 後置處理器`AnnotationAwareAspectJAutoProxyCreator`例項化完成之後,在接下來的Bean的例項化過程中,它會去嘗試攔截Bean的初始化,如果有需要,則會建立代理增強後的Bean。 **3. 利用`AnnotationAwareAspectJAutoProxyCreator`攔截Bean的初始化,建立增強的Bean** 在之前的例子中,定義瞭如下的切面類,實現了相關的advice方法。 ![Screen Shot 2020-02-15 at 1.22.17 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx0nefn1dj215k0pe7d6.jpg) 這是Calculate類,就是需要增強的類。 ![Screen Shot 2020-02-15 at 1.31.49 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx0xmzbi7j20hc072dgv.jpg)@w=300 這一步中主要關注這兩個Bean的例項化。 這一步的入口是refresh函式中的`beanFactory.preInstantiateSingletons()`,下一步進入到`getBean-->doGetBean`函式, ![Screen Shot 2020-02-15 at 3.10.53 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx3t0xzq5j218o0xe14b.jpg) ![Screen Shot 2020-02-15 at 3.12.38 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx3u8s3yyj20ys044wfo.jpg) 接著進入`doGetBean-->createBean`函式, ![Screen Shot 2020-02-15 at 3.17.15 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx3zupf2uj212g03475x.jpg) ![Screen Shot 2020-02-15 at 3.17.41 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx40maqalj21bi0fejw9.jpg) 接著進入到`createBean`函式,會呼叫函式`Object bean = resolveBeforeInstantiation(beanName, mbdToUse);`試圖直接返回proxy物件。 **接下來首先分析這個函式,再分析之後正常的初始化流程。`createBean`函式是理解整個AOP流程的核心。** ![Screen Shot 2020-02-15 at 3.26.04 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx4945g10j216s0vetis.jpg) 進入到函式的實現,可以看到最後會去嘗試呼叫型別為`InstantiationAwareBeanPostProcessor`的後置處理器,由於`AnnotationAwareAspectJAutoProxyCreator`實現了該介面,所以這個時候會被呼叫來試圖返回proxy物件,但是通常情況下,增強bean不會在這裡生成。 ![Screen Shot 2020-02-15 at 3.35.20 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx4i1p4gej211y0xkh99.jpg) 但並不是說這個`AnnotationAwareAspectJAutoProxyCreator`就沒有作用,進入到該函式的實現,可以發現在`shouldSkip`函式中會去找到所有的Advisor,也就是之前例子中的`LogAspects`類,並把這些Advisor放到BeanFactory中,方便後續建立增強的Bean。 ![Screen Shot 2020-02-15 at 4.25.23 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx5y40f0wj213o0qqkav.jpg) 在獲取到所有的Advisor之後,判斷當前bean是否在advisedBeans中(儲存了所有需要增強bean) 以及判斷當前bean是否是基礎型別的Advice、Pointcut、Advisor、AopInfrastructureBean,如果是的話就跳過。 **回到`createBean`函式,下面進入到正常的Bean初始化流程,一步步跟進到initializeBean函式中**,可以看到在初始化Bean的前後都會呼叫對應的後置處理器來完成相應的功能,但是AbstractAutoProxyCreator的實現中,在初始化Bean之前,只是直接返回Bean;但是**在初始化完Bean之後,會呼叫對應的後置處理器,也就是在`applyBeanPostProcessorsAfterInitialization`函式中來建立增強的Bean。** ![Screen Shot 2020-02-15 at 5.00.58 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx6zhw7bbj217y17u1kx.jpg) 下面對該函式進行仔細分析, ![Screen Shot 2020-02-15 at 5.35.30 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx7z0215aj214k14i1kx.jpg) 接著分析`createProxy`函式的實現,下面省略了部分中間呼叫,在最後的實現中,createAopProxy會根據情況使用jdk代理或者CGLib,從程式碼中可以看到,當被代理類是介面或者是proxy類時,就會採用jdk動態代理,反之則採用CGLib。 以後容器中獲取到的就是這個元件的代理物件,執行目標方法的時候,代理物件就會執行通知方法的流程; ![Screen Shot 2020-02-15 at 6.09.40 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx8yj3uzkj21400ia4c8.jpg) >注意一點:在createAopProxy時,會判斷config.isProxyTargetClass(),這個值預設為false。但是在兩個地方進行設定,一個是EnableAspectJAutoProxy註解中,另一個地方是在createProxy函式中,evaluateProxyInterfaces會去查詢目標類的所有interface,如果可用的話,則將其加到proxyFactory中,否則,呼叫setProxyTargetClass,設定為true。在本例子中,calculate類沒有相關介面,所以設定為true。這也是為什麼在createAopProxy函式中,會進行判斷,而不是直接返回jdk動態代理的類。 ![Screen Shot 2020-02-15 at 6.26.00 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbx9fi7k0mj20z40tcdyn.jpg) **4. 增強Bean的呼叫過程** 上面對AOP流程進行了梳理,通過程式碼分析瞭如何代理生成增強的Bean。這部分介紹在呼叫增強Bean的方法時,proxy物件是如何攔截方法呼叫的。 當被增強的Bean在執行時,會進入到下面的攔截執行流程, ![Screen Shot 2020-02-16 at 5.05.32 PM.png](http://ww1.sinaimg.cn/large/a18449c6gy1gbyhmky2zkj21h40vgaml.jpg) 首先,根據ProxyFactory物件獲取將要執行的目標方法攔截器鏈