目標:
1. 什麼是AOP, 什麼是AspectJ
2. 什麼是Spring AOP
3. Spring AOP註解版實現原理
4. Spring AOP切面原理解析
一. 認識AOP及其使用
詳見博文1: 5.1 Spring5原始碼--Spring AOP原始碼分析一
二. AOP的特點
2.1 Spring AOP
2.1.1 他是基於動態代理實現的
Spring 提供了很多的實現AOP的方式:Spring 介面方式,schema配置方式和註解的方式.
如果使用介面方式引入AOP, 就是用JDK提供的動態代理來實現.
如果沒有使用介面的方式引入. 那麼就是使用CGLIB來實現的.
Spring使用介面方式實現AOP, 下面有詳細說明.
研究使用介面方式實現AOP, 目的是為了更好地理解spring使用動態代理實現AOP的兩種方式
2.1.2 spring3.2以後, spring-core直接把CGLIB和ASM的原始碼引入進來了, 所以, 後面我們就不需要再顯示的引入這兩個依賴了.
2.1.3 Spring AOP依賴於Spring ioc容器來管理
2.1.4 Spring AOP只能作用於bean的方法.
如果某個類, 沒有注入到ioc容器中, 那麼是不能被增強的
2.1.5 Spring提供了對AspectJ的支援, 但只提供了部分功能的支援: 即AspectJ的切點解析(表示式)和匹配
我們在寫切面的時候,經常使用到的@Aspect, @Before, @Pointcut, @After, @AfterReturning, @AfterThrowing等就是AspectJ提供的.
我們知道AspectJ很好用, 效率也很高. 那麼為什麼Spring不使用AspectJ全套的東西呢? 尤其是AspectJ的靜態織入.
先來看看AspectJ有哪些特點
AspectJ的特點
1. AspectJ屬於靜態織入. 他是通過修改程式碼實現的. 它的織入時機有三種
1) Compile-time weaving: 編譯期織入. 例如: 類A使用AspectJ增加了一個屬性. 類B引用了類A, 這個場景就需要在編譯期的時候進行織入, 否則類B就沒有辦法編譯, 會報錯.
2) Post-compile weaving: 編譯後織入.也就是已經生成了.class檔案了, 或者是都已經達成jar包了. 這個時候, 如果我們需要增強, 就要使用到編譯後織入
3) Loading-time weaving: 指的是在載入類的時候進行織入.
2. AspectJ實現了對AOP變成完全的解決方案. 他提供了很多Spring AOP所不能實現的功能
3. 由於AspectJ是在實際程式碼執行前就完成了織入, 因此可以認為他生成的類是沒有額外執行開銷的.
擴充套件: 這裡為什麼沒有使用到AspectJ的靜態織入呢? 因為如果引入靜態織入, 需要使用AspectJ自己的解析器. AspectJ檔案是以aj字尾結尾的檔案, 這個檔案Spring是沒有辦法, 因此要使用AspectJ自己的解析器進行解析. 這樣就增加了Spring的成本.
2.1.6 Spring AOP和AspectJ的比較。由於,Spring AOP基於代理實現. 容器啟動時會生成代理物件, 方法呼叫時會增加棧的深度。使得Spring AOP的效能不如AspectJ好。
三. AOP的配置方式
上面說了Spring AOP和AspectJ. 也說道了AspectJ定義了很多註解, 比如: @Aspect, @Pointcut, @Before, @After等等. 但是, 我們使用Spring AOP是使用純java程式碼寫的. 也就是說他完全屬於Spring, 和AspectJ沒有什麼關係. Spring只是沿用了AspectJ中的概念. 包括AspectJ提供的jar報的註解. 但是, 並不依賴於AspectJ的功能.
我們使用的@Aspect, @Pointcut, @Before, @After等註解都是來自於AspectJ, 但是其功能的實現是純Spring AOP自己實現的.
Spring AOP有三種配置方式.
-
第一種: 基於介面方式的配置. 在Spring1.2版本, 提供的是完全基於介面方式實現的
-
第二種: 基於schema-based配置. 在spring2.0以後使用了xml的方式來配置.
-
第三種: 基於註解@Aspect的方式. 這種方式是最簡單, 方便的. 這裡雖然叫做AspectJ, 但實際上和AspectJ一點關係也沒有.
因為我們在平時工作中主要使用的是註解的方式配置AOP, 而註解的方式主要是基於第一種介面的方式實現的. 所以, 我們會重點研究第一種和第三種配置方式.
3.1 基於介面方式的配置. 在Spring1.2版本, 提供的是完全基於介面方式實現的
這種方式是最古老的方式, 但由於spring做了很好的向後相容, 所以, 現在還是會有很多程式碼使用這種方式, 比如:宣告式事務.
我們要了解這種配置方式還有另一個原因, 就是我們要看原始碼. 原始碼裡對介面方式的配置進行了相容處理. 同時, 看原始碼的入口是從介面方式的配置開始的.
那麼, 在沒有引入AspectJ的時候, Spring是如何實現AOP的呢? 我們來看一個例子:
1. 定義一個業務邏輯介面類
package com.lxl.www.aop.interfaceAop; /** * 使用介面方式實現AOP, 預設通過JDK的動態代理來實現. 非介面方式, 使用的是cglib實現動態代理 * * 業務介面類-- 計算器介面類 * * 定義三個業務邏輯方法 */ public interface IBaseCalculate { int add(int numA, int numB); int sub(int numA, int numB); int div(int numA, int numB); int multi(int numA, int numB); int mod(int numA, int numB); }
2.定義業務邏輯類
package com.lxl.www.aop.interfaceAop;//業務類,也是目標物件 import com.lxl.www.aop.Calculate; import org.springframework.aop.framework.AopContext; import org.springframework.stereotype.Service; /** * 業務實現類 -- 基礎計算器 */ public class BaseCalculate implements IBaseCalculate { @Override public int add(int numA, int numB) { System.out.println("執行目標方法: add"); return numA + numB; } @Override public int sub(int numA, int numB) { System.out.println("執行目標方法: sub"); return numA - numB; } @Override public int multi(int numA, int numB) { System.out.println("執行目標方法: multi"); return numA * numB; } @Override public int div(int numA, int numB) { System.out.println("執行目標方法: div"); return numA / numB; } @Override public int mod(int numA, int numB) { System.out.println("執行目標方法: mod"); int retVal = ((Calculate) AopContext.currentProxy()).add(numA, numB); return retVal % numA; } }
3. 定義通知類
前置通知
package com.lxl.www.aop.interfaceAop; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * 定義前置通知 * 實現MethodBeforeAdvice介面 */ public class BaseBeforeAdvice implements MethodBeforeAdvice { /** * * @param method 切入的方法 * @param args 切入方法的引數 * @param target 目標物件 * @throws Throwable */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("===========進入beforeAdvice()============"); System.out.println("前置通知--即將進入切入點方法"); System.out.println("===========進入beforeAdvice() 結束============\n"); } }
後置通知
package com.lxl.www.aop.interfaceAop; import org.aspectj.lang.annotation.AfterReturning; import org.springframework.aop.AfterAdvice; import org.springframework.aop.AfterReturningAdvice; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * 後置通知 * 實現AfterReturningAdvice介面 */ public class BaseAfterReturnAdvice implements AfterReturningAdvice { /** * * @param returnValue 切入點執行完方法的返回值,但不能修改 * @param method 切入點方法 * @param args 切入點方法的引數陣列 * @param target 目標物件 * @throws Throwable */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("\n==========進入afterReturning()==========="); System.out.println("後置通知--切入點方法執行完成"); System.out.println("==========進入afterReturning() 結束=========== "); } }
環繞通知
package com.lxl.www.aop.interfaceAop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * 環繞通知 * 實現MethodInterceptor介面 */ public class BaseAroundAdvice implements MethodInterceptor { /** * invocation :連線點 */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("===========around環繞通知方法 開始==========="); // 呼叫目標方法之前執行的動作 System.out.println("環繞通知--呼叫方法之前: 執行"); // 執行完方法的返回值:呼叫proceed()方法,就會觸發切入點方法執行 Object returnValue = invocation.proceed(); System.out.println("環繞通知--呼叫方法之後: 執行"); System.out.println("===========around環繞通知方法 結束==========="); return returnValue; } }
配置類
package com.lxl.www.aop.interfaceAop; import org.springframework.aop.framework.ProxyFactoryBean; import org.springframework.context.annotation.Bean; /** * 配置類 */ public class MainConfig { /** * 被代理的物件 * @return */ @Bean public IBaseCalculate baseCalculate() { return new BaseCalculate(); } /** * 前置通知 * @return */ @Bean public BaseBeforeAdvice baseBeforeAdvice() { return new BaseBeforeAdvice(); } /** * 後置通知 * @return */ @Bean public BaseAfterReturnAdvice baseAfterReturnAdvice() { return new BaseAfterReturnAdvice(); } /** * 環繞通知 * @return */ @Bean public BaseAroundAdvice baseAroundAdvice() { return new BaseAroundAdvice(); } /** * 使用介面方式, 一次只能給一個類增強, 如果想給多個類增強, 需要定義多個ProxyFactoryBean * 而且, 曾增強類的粒度是到類級別的. 不能指定對某一個方法增強 * @return */ @Bean public ProxyFactoryBean calculateProxy() { ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setInterceptorNames("baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice"); proxyFactoryBean.setTarget(baseCalculate()); return proxyFactoryBean; } }
之前說過, AOP是依賴ioc的, 必須將其註冊為bean才能實現AOP功能
方法入口
package com.lxl.www.aop.interfaceAop; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class InterfaceMainClass{ public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class); IBaseCalculate calculate = context.getBean("calculateProxy", IBaseCalculate.class); System.out.println(calculate.getClass()); calculate.add(1, 3); } }
執行結果:
===========進入beforeAdvice()============
前置通知--即將進入切入點方法
===========進入beforeAdvice() 結束============
===========around環繞通知方法 開始===========
環繞通知--呼叫方法之前: 執行
執行目標方法: add
環繞通知--呼叫方法之後: 執行
===========around環繞通知方法 結束===========
==========進入afterReturning()===========
後置通知--切入點方法執行完成
==========進入afterReturning() 結束===========
通過觀察, 我們發現, 執行的順序是: 前置通知-->環繞通知的前置方法 --> 目標邏輯 --> 環繞通知的後置方法 --> 後置通知.
那麼到底是先執行前置通知, 還是先執行環繞通知的前置方法呢? 這取決於配置檔案的配置順序
這裡,我們將環繞通知放在最後面, 所以, 環繞通知在前置通知之後執行.
@Bean public ProxyFactoryBean calculateProxy() { ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setInterceptorNames( "baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice"); proxyFactoryBean.setTarget(baseCalculate()); return proxyFactoryBean; }
那麼, 如果我們將環繞通知放在前置通知之前. 就會先執行環繞通知
@Bean public ProxyFactoryBean calculateProxy() { ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setInterceptorNames("baseAroundAdvice", "baseAfterReturnAdvice", "baseBeforeAdvice"); proxyFactoryBean.setTarget(baseCalculate()); return proxyFactoryBean; }
執行結果
===========around環繞通知方法 開始=========== 環繞通知--呼叫方法之前: 執行 ===========進入beforeAdvice()============ 前置通知--即將進入切入點方法 ===========進入beforeAdvice() 結束============ 執行目標方法: add ==========進入afterReturning()=========== 後置通知--切入點方法執行完成 ==========進入afterReturning() 結束=========== 環繞通知--呼叫方法之後: 執行 ===========around環繞通知方法 結束===========
思考: 使用ProxyFactoryBean實現AOP的方式有什麼問題?
1. 通知加在類級別上, 而不是方法上. 一旦使用這種方式, 那麼所有類都會被織入前置通知, 後置通知, 環繞通知. 可有時候我們可能並不想這麼做
2. 每次只能指定一個類. 也就是類A要實現加日誌, 那麼建立一個A的ProxyFactoryBean, 類B也要實現同樣邏輯的加日誌. 但是需要再寫一個ProxyFactoryBean.
基於以上兩點原因. 我們需要對其進行改善.
下面, 我們來看看, ProxyFactoryBean是如何實現動態代理的?
ProxyFactoryBean是一個工廠bean, 我們知道工廠bean在建立類的時候呼叫的是getObject(). 下面看一下原始碼
public class ProxyFactoryBean extends ProxyCreatorSupport implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware { ...... @Override @Nullable public Object getObject() throws BeansException { /** * 初始化通知鏈: 將通知放入鏈中 * 後面初始化的時候, 是通過責任鏈的方式呼叫這些通知鏈的的. * 那麼什麼是責任鏈呢? */ initializeAdvisorChain(); if (isSingleton()) { /** * 建立動態代理 */ return getSingletonInstance(); } else { if (this.targetName == null) { logger.info("Using non-singleton proxies with singleton targets is often undesirable. " + "Enable prototype proxies by setting the 'targetName' property."); } return newPrototypeInstance(); } } ...... }
傳送到initializeAdvisorChain是初始化各型別的Advisor通知, 比如, 我們上面定義的通知有三類: "baseAroundAdvice", "baseAfterReturnAdvice", "baseBeforeAdvice". 這裡採用的是責任鏈呼叫的方式.
然後呼叫getSingletonInstance()建立動態代理.
private synchronized Object getSingletonInstance() { if (this.singletonInstance == null) { this.targetSource = freshTargetSource(); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { // Rely on AOP infrastructure to tell us what interfaces to proxy. Class<?> targetClass = getTargetClass(); if (targetClass == null) { throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy"); } setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader)); } // Initialize the shared singleton instance. super.setFrozen(this.freezeProxy); /** * 建立動態代理 */ this.singletonInstance = getProxy(createAopProxy()); } return this.singletonInstance; }
呼叫getProxy(CreateAopProxy())呼叫代理建立動態代理. 我們這裡使用介面的方式, 這裡呼叫的是JDKDynamicAopProxy動態代理.
@Override public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); } Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); /** * 建立動態代理 */ return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); }
最終, 動態代理建立, 就是在JDKDynamicAopProxy了. 通過執行Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);建立動態代理例項.
其實我們通過ctx.getBean("calculateProxy")獲得的類, 就是通過JDKDynamicAopProxy建立的動態代理類.
這裡也看出, 為什麼每次只能給一個類建立動態代理了.
上面提到了責任鏈, 那麼什麼是責任鏈呢? 如下圖所示:
有一條流水線. 比如生產流水線. 裡面有許多道工序. 完成工序1 ,才能進行工序2, 依此類推.
流水線上的工人也是各司其職. 工人1做工序1, 工人2做工序2, 工人3做工序3.....這就是一個簡單的流水線模型.
工人的責任就是完成每一道工序, 那麼所有工人的責任就是完成這條流水線. 這就是工人的責任鏈.
其實, 我們的通知也是一類責任鏈. 比如, 前置通知, 環繞通知, 後置通知, 異常通知. 他們的執行都是有順序的. 一個工序完成, 做另一個工序.各司其職. 這就是責任鏈.
為了能工統一排程, 我們需要保證, 所有工人使用的都是同一個抽象. 這樣, 就可以通過抽象類的呼叫方式. 各個工人有具體的工作實現.
通知也是如此. 需要有一個抽象的通知類Advicor. 進行統一呼叫.
結合上面的demo, 來看一個責任鏈呼叫的demo.
上面我們定義了兩個方法. 一個是前置通知BaseBeforeAdvice 實現了MethodBeforeAdvice, 另一個是環繞通知BaseAroundAdvice 實現了MethodInterceptor. 如果想把這兩個通知放在一個鏈上. 那麼他們必須實現相同的介面. 但是, 現在不同.
我們知道環繞通知, 由兩部分, 一部分是環繞通知的前置通知, 一部分是環繞通知的後置通知. 所以, 我們可以將前置通知看作是環繞通知的前置通知部分.
package com.lxl.www.aop.interfaceAop.chainDemo; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.MethodBeforeAdvice; /** * 為什麼 我們可以讓MethodBeforeAdvice 前置通知繼承自環繞通知的介面呢? * 主要原因是, 環繞通知的前半部分, 就是前置通知 */ public class BeforeAdviceInterceptor implements MethodInterceptor { // 前置通知 MethodBeforeAdvice methodBeforeAdvice; public BeforeAdviceInterceptor(MethodBeforeAdvice methodBeforeAdvice) { this.methodBeforeAdvice = methodBeforeAdvice; } /** * 使用了環繞通知的前半部分. 就是一個前置通知 * @param invocation the method invocation joinpoint * @return * @throws Throwable */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { methodBeforeAdvice.before(invocation.getMethod(), invocation.getArguments(), invocation.getClass()); return invocation.proceed(); } }
這段程式碼包裝了前置通知, 讓其擴充套件為實現MethodInterceptor介面. 這是一個擴充套件介面的方法.
接下來我們要建立一條鏈. 這條鏈就可以理解為流水線上各個工人. 每個工人處理一個工序. 為了能夠統一呼叫. 所有的工人都要實現同一個介面. 責任鏈的定義如下:
/** * 把一條鏈上的都初始化 * * 有一條鏈, 這條鏈上都有一個父類介面 MethodInterceptor. * 也就是說, 鏈上都已一種型別的工人. 但每種工人的具體實現是不同的. 不同的工人做不同的工作 * * 這裡定義了一個責任鏈. 連上有兩個工人. 一個是前置通知. 一個是環繞通知. * 前置通知和環繞通知實現的介面是不同的. 為了讓他們能夠在一條鏈上工作. 我們自定義了一個MethodBeforeAdviceInterceptor * 相當於為BaseBeforeAdvice()包裝了一層MethodInterceptor */ List<MethodInterceptor> list = new ArrayList<>(); list.add(new BeforeAdviceInterceptor(new BaseBeforeAdvice())); list.add(new BaseAroundAdvice());
這裡定義了一個責任鏈. 連上有兩個工人. 一個是前置通知. 一個是環繞通知.
前置通知和環繞通知實現的介面是不同的. 為了讓他們能夠在一條鏈上工作. 我們自定義了一個MethodBeforeAdviceInterceptor
相當於為BaseBeforAdvice()包裝了一層MethodInterceptor
接下來是責任鏈的呼叫.
/** * 責任鏈呼叫 */ public static class MyMethodInvocation implements MethodInvocation { // 這是責任鏈 protected List<MethodInterceptor> list; // 目標類 protected final BaseCalculate target; public MyMethodInvocation(List<MethodInterceptor> list) { this.list = list; this.target = new BaseCalculate(); } int i = 0; public Object proceed() throws Throwable { if (i == list.size()) { /** * 執行到責任鏈的最後一環, 執行目標方法 */ return target.add(2, 2); } MethodInterceptor interceptor = list.get(i); i++; /** * 執行責任鏈呼叫 * 這個呼叫鏈第一環是: 包裝後的前置通知 * 呼叫鏈的第二環是: 環繞通知. * 都執行完以後, 執行目標方法. */ return interceptor.invoke(this); } @Override public Object getThis() { return target; } @Override public AccessibleObject getStaticPart() { return null; } @Override public Method getMethod() { try { return target.getClass().getMethod("add", int.class, int.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; } @Override public Object[] getArguments() { return new Object[0]; } } }
這裡重點看proceed() 方法. 我們迴圈獲取了list責任鏈的通知, 然後執行invoke()方法
proceed() 方法是一個鏈式迴圈. 剛開始i=0, list(0)是前置通知, 當呼叫到前置通知的時候, BeforeAdviceInterceptor.invoke()方法, 又呼叫了invocation.proceed()方法, 回到了MyMethodInvocation.proceed()方法.
然後i=1, list(1)是環繞通知, 當呼叫環繞通知的時候, 又呼叫了invocation.proceed(); 有回到了MyMethodInvocation.proceed()方法.
這是已經是list的最後一環了, 後面不會在呼叫invoke()方法了. 而是執行目標方法. 執行結束以後, 整個呼叫結束.
這就是一個呼叫鏈.
對於責任鏈有兩點:
1. 要有一個統一的呼叫, 也就是一個共同的抽象類.
2. 使用迴圈或者遞迴, 完成責任鏈的呼叫
總結:
上面這種方式, 使用的是ProxyFactoryBean 代理bean工廠的方式. 他有兩個限制:
/** * 使用介面方式, 一次只能給一個類增強, 如果想給多個類增強, 需要定義多個ProxyFactoryBean * 而且, 曾增強類的粒度是到類級別的. 不能指定對某一個方法增強 * @return */ @Bean public ProxyFactoryBean calculateProxy() { ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setInterceptorNames("baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice"); proxyFactoryBean.setTarget(baseCalculate()); return proxyFactoryBean; }
1. 一次只能給1個類增強, 如果給多個類增強就需要定義多個ProxyFactoryBean
2. 增強的粒度只能到類級別上, 不能指定給某個方法增強.
這樣還是有一定的限制.
為了解決能夠在類級別上進行增強, Spring引入了Advisor和Pointcut.
Advisor的種類有很多
RegexpMethodPointcutAdvisor 按正則匹配類
NameMatchMethodPointcutAdvisor 按方法名匹配
DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
InstantiationModelAwarePointcutAdvisorImpl 註解解析的advisor(@Before @After....)
我們使用按方法名的粒度來增強, 所示使用的是NameMatchMethodPointcutAdvisor
/** * Advisor 種類很多: * RegexpMethodPointcutAdvisor 按正則匹配類 * NameMatchMethodPointcutAdvisor 按方法名匹配 * DefaultBeanFactoryPointcutAdvisor xml解析的Advisor * InstantiationModelAwarePointcutAdvisorImpl 註解解析的advisor(@Before @After....) */ @Bean public NameMatchMethodPointcutAdvisor aspectAdvisor() { /** * 通知和通知者的區別: * 通知(Advice) :是我們的通知類 沒有帶切點 * 通知者(Advisor):是經過包裝後的細粒度控制方式。 帶了切點 */ NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(); // 切入點增強的通知型別--前置通知 advisor.setAdvice(baseBeforeAdvice()); // 指定切入點方法名--div advisor.setMappedNames("div"); return advisor; }
這裡設定了切入點需要增強的通知, 和需要切入的方法名.
這樣就可以對類中的某個方法進行增強了. 我們依然需要使用ProxyFactoryBean()代理工廠類來進行增強
@Bean
public ProxyFactoryBean calculateProxy() {
ProxyFactoryBean userService = new ProxyFactoryBean();
userService.setInterceptorNames("aspectAdvisor");
userService.setTarget(baseCalculate());
return userService;
}
注意, 這裡增強的攔截器名稱要寫剛剛定義的 NameMatchMethodPointcutAdvisor 型別的攔截器.目標類還是我們的基礎業務類baseCalculate()
這只是解決了可以對指定方法進行增強. 那麼, 如何能夠一次對多個類增強呢? Spring又引入了ProxyCreator.
/** * Advisor 種類很多: * RegexpMethodPointcutAdvisor 按正則匹配類 * NameMatchMethodPointcutAdvisor 按方法名匹配 * DefaultBeanFactoryPointcutAdvisor xml解析的Advisor * InstantiationModelAwarePointcutAdvisorImpl 註解解析的advisor(@Before @After....) */ @Bean public NameMatchMethodPointcutAdvisor aspectAdvisor() { /* * 通知和通知者的區別: * 通知(Advice) :是我們的通知類 沒有帶切點 * 通知者(Advisor):是經過包裝後的細粒度控制方式。 帶了切點 */ NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(); // 切入點增強的通知型別--前置通知 advisor.setAdvice(baseBeforeAdvice()); // 指定切入點方法名--div advisor.setMappedNames("div"); return advisor; } /** * autoProxy: BeanPostProcessor 手動指定Advice方式, * @return */ @Bean public BeanNameAutoProxyCreator autoProxyCreator() { // 使用bean名字進行匹配 BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator(); beanNameAutoProxyCreator.setBeanNames("base*"); // 設定攔截鏈的名字 beanNameAutoProxyCreator.setInterceptorNames("aspectAdvisor"); return beanNameAutoProxyCreator; }
如上程式碼, beanNameAutoProxyCreator.setBeanNames("base*"); 表示按照名字匹配以base開頭的類, 對其使用的攔截器的名稱是aspectAdvisor
而aspectAdvisor使用的是按照方法的細粒度進行增強. 這樣就實現了, 對以base開頭的類, 對其中的某一個或某幾個方法進行增強. 使用的增強類是前置通知.
下面修改main方法, 看看執行效果
public class InterfaceMainClass{ public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class); IBaseCalculate calculate = context.getBean("baseCalculate", IBaseCalculate.class); calculate.add(1, 3); System.out.println("******************"); calculate.div(1, 3); } }
這裡面執行了兩個方法, 一個是add(), 另一個是div(). 看執行結果
執行目標方法: add
******************
===========進入beforeAdvice()============
前置通知--即將進入切入點方法
===========進入beforeAdvice() 結束============
執行目標方法: div
我們看到, add方法沒有被增強, 而div方法被增強了, 增加了前置通知.
以上就是使用介面方式實現AOP. 到最後增加了proxyCreator, 能夠根據正規表示式匹配相關的類, 還能夠為某一個指定的方法增強. 這其實就是我們現在使用的註解型別AOP的原型了.
3.2 基於註解@Aspect的方式. 這種方式是最簡單, 方便的. 這裡雖然叫做AspectJ, 但實際上和AspectJ一點關係也沒有.
3.2.1 @Aspect切面的解析原理
上面第一種方式詳細研究了介面方式AOP的實現原理. 註解方式的AOP, 最後就是將@Aspect 切面類中的@Befor, @After等註解解析成Advisor. 帶有@Before類會被解析成一個Advisor, 帶有@After方法的類也會被解析成一個Advisor.....其他通知的方法也會被解析成Advisor 在Advisor中定義了增強的邏輯, 也就是@Befor和@After等的邏輯, 以及需要增強的方法, 比如div方法.
下面來分析一下使用註解@Aspect , @Before, @After的實現原理. 上面已經說了, 就是將@Before, @After生成Advisor
這裡一共有三個部分.
- 第一部分: 解析@Aspect下帶有@Before等的通知方法, 將其解析為Advisor
- 第二部分: 在createBean的時候, 建立動態代理
- 第三部分: 呼叫. 呼叫的時候, 執行責任鏈, 迴圈裡面所有的通知. 最後輸出結果.
下面我們按照這三個部分來分析.
第一步: 解析@Aspect下帶有@Before等的通知方法, 將其解析為Advisor. 如下圖:
第一步是在什麼時候執行的呢?
在createBean的時候, 會呼叫很多PostProcessor後置處理器, 在呼叫第一個後置處理器的時候執行.
執行的流程大致是: 拿到所有的BeanDefinition,判斷類上是不是帶有@Aspect註解. 然後去帶有@Aspect註解的方法中找@Before, @After, @AfterReturning, @AfterThrowing, 每一個通知都會生成一個Advisor
Advisor包含了增強邏輯, 定義了需要增強的方法. 只不過這裡是通過AspectJ的execution的方式進行匹配的.
第二步: 在createBean的時候, 建立動態代理
createBean一共有三個階段, 具體在哪一個階段建立的動態代理呢?
其實, 是在最後一個階段初始化之後, 呼叫了一個PostProcessor後置處理器, 在這裡生成的動態代理
整體流程是:
在createBean的時候, 在初始化完成以後呼叫bean的後置處理器. 拿到所有的Advisor, 迴圈遍歷Advisor, 然後根據execution中的表示式進行matchs匹配. 和當前建立的這個bean進行匹配, 匹配上了, 就建立動態代理.
pointcut的種類有很多. 上面程式碼提到過的有:
/** * Advisor 種類很多: * RegexpMethodPointcutAdvisor 按正規表示式的方式匹配類 * NameMatchMethodPointcutAdvisor 按方法名匹配 * DefaultBeanFactoryPointcutAdvisor xml解析的Advisor * InstantiationModelAwarePointcutAdvisorImpl 註解解析的advisor(@Before @After....) */
第三步: 呼叫. 呼叫的時候, 執行責任鏈, 迴圈裡面所有的通知. 最後輸出結果.
呼叫的類,如果已經生成了動態代理. 那麼呼叫的方法, 就是動態代理生成的方法了.然後拿到所有的advisor, 作為一個責任鏈呼叫. 執行各類通知, 最後返回執行結果
3.2.2 AOP切面原始碼分析
原始碼分析也分為三部分
1. 解析切面
2. 建立動態代理
3. 呼叫
原始碼分析的入口, 從註解開始:
元件的入口是一個註解, 比如啟用AOP的註解@EnableAspectJAutoProxy. 在註解的實現類裡面, 會有一個@Import(""). 這個@Import("")就是引入的原始碼實現類. 比如AOP的@Import(AspectJAutoProxyRegistrar.class)
通常, Spring要開啟某一個功能, 都會增加一個註解, 如果我們再想要看某一個功能的原始碼, 那麼就可以從他的註解跟進去看,在找到@Import("")就找到原始碼的入口了
原始碼分析的入口, AOP註解:
package com.lxl.www.aop;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configurable
// 使用註解的方式引入AOP
@EnableAspectJAutoProxy
@ComponentScan("com.lxl.www.aop")
public class MainConfig {
}
引入AOP, 我們需要在配置檔案中增加@EnableAspectJAutoProxy代理. 那麼想要去掉AOP的引入, 只需要將這個註解註釋掉就可以了. 這個註解解釋整個AOP的入口.
提示: 其他元件的引入也是類似的, 通常引入元件, 需要增加一個註解, 而整個功能的入口就在這個主機上面.
接下來, 進入到註解類
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
這是, 我們看到EnableAspectJAutoProxy類增加了一個@Import註解類, 我們知道Import註解可以向IoC容器中增加一個bean.
下面進入到AspectJAutoProxyRegistrar類
package org.springframework.context.annotation; import org.springframework.aop.config.AopConfigUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; 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); } } } }
我們看到, 使用ImportBeanDefinitionRegistrar註冊了一個BeanDefinition.
需要記住的是, 通常使用ImportBeanDefinitionRegistrar結合@Import可以向容器中註冊一個BeanDefinition.
如何註冊的呢? 看具體實現.
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
註冊名字是internalAutoProxyCreator的AnnotationAwareAspectJAutoProxyCreator
@Nullable public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) { /** * 註冊一個AnnotationAwareAspectJAutoProxyCreator型別的bean定義 */ return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source); }
如上結構梳理如下:
我們看到, 註冊了類AnnotationAwareAspectJAutoProxyCreator型別的bean. 這是一個什麼樣的類呢? 我們來看一下類的結構. 這個類的繼承結構很龐大, 我們只看和本次內容相關的繼承結構
解析切面, 建立動態代理, 都是在bean的後置處理器中進行的, 下面對照著AOP的實現原理以及createBean(建立bean)的過程來看
上圖是bean載入過程中呼叫的9次後置處理器. 在建立bean之前呼叫了InstantiationAwareBeanPostProcessor後置處理器判斷是否需要為這個類建立AOP, 也就是解析切面的過程. 所以在AnnotationAwareAspectJAutoProxyCreator裡面實現了InstantiationAwareBeanPostProcessor後置處理器的介面. 重寫了postProcessBeforeInstantiation方法.
在createBean的第三階段初始化之後, 要建立AOP的動態代理, 呼叫了BeanPostProcess後置處理器, AnnotationAwareAspectJAutoProxyCreator也實現了BeanPostProcess介面. 重寫了postProcessAfterInitialization.
同時也需要處理AOP的迴圈依賴的問題, 處理迴圈依賴是在屬性賦值之前呼叫SmartInstantiationAwareBeanPostProcessor後置處理器, 然後重寫getEarlyBeanReference方法. 我們看到AnnotationAwareAspectJAutoProxyCreator也實現了SmartInstantiationAwareBeanPostProcessor介面. 並重寫getEarlyBeanReference方法.
1) AOP解析切面
通過上面的分析,我們知道了, 解析切面是在重寫了InstantiationAwareBeanPostProcessor後置處理器的postProcessBeforeInstantiation方法. 所以,我們要找到AnnotationAwareAspectJAutoProxyCreator重寫的postProcessBeforeInstantiation方法.
小貼士
如何找到呢? 在idea中使用快捷鍵ctrl + o, 找到當前類重寫的所有方法. 在搜尋postProcessBeforeInstantiation, 就可以找到了
進入建立動態代理的bean的後置處理器, 這是解析切面的第一個入口
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
......
}
我們在postProcessBeforeInstantiation方法的入口處打一個斷點, 接下來看一下這個介面的呼叫鏈
如上圖, 可以看出我們的入口是main方法, 然後呼叫了refresh()方法, 執行的是refresh()方法的finishBeanFactoryInitialization()方法, 然胡呼叫了doGetBean()下的createBean().然後呼叫的是resolveBeforeInstantiation的applyBeanPostProcessorsBeforeInstantiation方法,在這裡獲取到所有的bean的後置處理器, 判斷這個bean的後置處理器是否是InstantiationAwareBeanPostProcessor的一個例項. 如果是, 那麼就呼叫postProcessBeforeInstantiation()方法.
@Nullable protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) { /** * 獲取容器中所有的後置處理器 * 這之前有一個註冊bean定義的方法, 已經註冊過了. 所以在這裡可以獲取到列表 * * 9次bean的後置處理器, 都是一個類實現InstantiationAwareBeanPostProcessor類, 重寫postProcessBeforeInstantiation方法 */ for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName); if (result != null) { return result; } } } return null; }
下面就來分析postProcessBeforeInstantiation()方法
@Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) { /** * 在第一個bean建立的時候, 就會去呼叫所有的bean的後置處理器, 並且解析所有的切面. * 這一步是非常消耗效能的. 所以, 會放到快取當中 */ // 構建快取的key Object cacheKey = getCacheKey(beanClass, beanName); // 沒有beanName或者不包含在targetSourcedBeans if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) { // 判斷是否已經被解析過? if (this.advisedBeans.containsKey(cacheKey)) { // 解析過, 則直接返回 return null; } /* * 判斷當前這個類是不是需要跳過的類.如果是基礎類或者是應該跳過裡的類, 則返回null, 表示這個類不需要被解析 * * 判斷是不是基礎bean(是不是切面類, 通知, 切點). 因為如果類本身是一個通知, 切面, 那我們不需要解析它 * 跳過的類: 預設是false. 在shouldSkip裡面拿到所有的bean定義, 標記是不是@Aspect, 然後將每一個通知生成一個advisor */ if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { /** * advisedBean是一個集合, 用來儲存類是否是一個advise */ this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; } } // Create proxy here if we have a custom TargetSource. // Suppresses unnecessary default instantiation of the target bean: // The TargetSource will handle target instances in a custom fashion. TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) { if (StringUtils.hasLength(beanName)) { this.targetSourcedBeans.add(beanName); } Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); // 建立了代理 Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } return null; }
第一步: 構建快取
構建快取的key
Object cacheKey = getCacheKey(beanClass, beanName);
在第一個bean建立的時候, 就會去呼叫所有的bean的後置處理器, 並且解析所有的切面.
這一步是非常消耗效能的. 所以, 會放到快取當中. 已經建立過的,後面將不再建立
第二步: 校驗bean是否被解析過. 如果已經解析過, 則不再解析
// 判斷是否已經被解析過 if (this.advisedBeans.containsKey(cacheKey)) { // 解析過, 則直接返回 return null; }
第三步: 判斷類是否是需要跳過的類
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { /** * advisedBean是一個集合, 用來儲存類是否是一個advise */ this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; }
如果是基礎類或者是應該跳過的類, 則返回null, 表示這個類不需要被解析.
這裡有兩個判斷.
isInfrastructureClass(beanClass) 判斷當前這個類是不是基礎類, 這裡的基礎類的含義如下: Advice、Pointcut、Advisor、AopInfrastructureBean。如果本身就是基礎類,那麼不用在解析了
protected boolean isInfrastructureClass(Class<?> beanClass) { // 如果這個類是一個Advice型別的類, 或者 Pointcut型別的類, 或者Adivsor型別的類, 或者AOPInsfrastructureBean型別的類. boolean retVal = Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass) || AopInfrastructureBean.class.isAssignableFrom(beanClass); if (retVal && logger.isTraceEnabled()) { logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]"); } return retVal; }
shouldSkip(beanClass, beanName)判斷當前是否是需要跳過的類 .
@Override protected boolean shouldSkip(Class<?> beanClass, String beanName) { // 找到候選的Advisors(前置通知, 後置通知等) List<Advisor> candidateAdvisors = findCandidateAdvisors(); for (Advisor advisor : candidateAdvisors) { if (advisor instanceof AspectJPointcutAdvisor && ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) { return true; } } return super.shouldSkip(beanClass, beanName); }
findCandidateAdvisors(); 找到候選的類, 然後將候選類構造成Advisor物件. 進到方法裡看看是如何篩選出候選物件的.
AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors()
@Override protected List<Advisor> findCandidateAdvisors() { // Add all the Spring advisors found according to superclass rules. // 找到xml方式配置的Advisor和原生介面的AOP的advisor 以及找到事務相關的advisor List<Advisor> advisors = super.findCandidateAdvisors(); // Build Advisors for all AspectJ aspects in the bean factory. // 將找到的aspect, 封裝為一個Advisor if (this.aspectJAdvisorsBuilder != null) { //buildAspectJAdvisors()方法就是用來解析切面類, 判斷是否含有@Aspect註解, 然後將每一個通知生成一個advisor advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); } // 返回所有的通知 return advisors; }
這裡做了兩件事
第一步: 解析xml方式配置的Advisor (包括原生介面方式配置的advisor 以及找到事務相關的advisor)
第二步: 解析註解方式的切面. buildAspectJAdvisors()方法是用來解析切面類的. 解析每一個切面類中的通知方法, 併為每個方法匹配切點表示式.
public List<Advisor> buildAspectJAdvisors() { /* * aspectNames: 用於儲存切面名稱的集合 * aspectNames是快取的類級別的切面, 快取的是已經解析出來的切面資訊 */ List<String> aspectNames = this.aspectBeanNames; // 如果aspectNames值為空, 那麼就在第一個單例bean執行的時候呼叫後置處理器(AnnotationAwareAspectJAutoProxy) if (aspectNames == null) { // 加鎖, 防止多個執行緒, 同時載入 Aspect synchronized (this) { aspectNames = this.aspectBeanNames; // 雙重檢查 if (aspectNames == null) { // 儲存所有從切面中解析出來的通知 List<Advisor> advisors = new ArrayList<>(); // 儲存切面名稱的集合 aspectNames = new ArrayList<>(); /* * 掃描Object的子類. 那就是掃描所有的類 * * 這裡傳入要掃描的物件是Object.class. 也就是說去容器中掃描所有的類. * 迴圈遍歷. 這個過程是非常耗效能的, 所以spring增加了快取來儲存切面 * * 但事務功能除外, 事務模組是直接去容器中找到Advisor型別的類 選擇範圍小 * spring 沒有給事務模組加快取 */ String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this.beanFactory, Object.class, true, false); // 迴圈遍歷beanNames for (String beanName : beanNames) { if (!isEligibleBean(beanName)) { continue; } // We must be careful not to instantiate beans eagerly as in this case they // would be cached by the Spring container but would not have been weaved. // 通過beanName去容器中獲取到對應class物件 Class<?> beanType = this.beanFactory.getType(beanName); if (beanType == null) { continue; } // 判斷bean是否是一個切面, 也就是腦袋上是否有@Aspect註解 if (this.advisorFactory.isAspect(beanType)) { aspectNames.add(beanName); // 將beanName和class物件構建成一個AspectMetadata物件 AspectMetadata amd = new AspectMetadata(beanType, beanName); if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); // 解析切面類中所有的通知--一個通知生成一個Advisor. List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory); // 加入到快取中 if (this.beanFactory.isSingleton(beanName)) { this.advisorsCache.put(beanName, classAdvisors); } else { this.aspectFactoryCache.put(beanName, factory); } advisors.addAll(classAdvisors); } else { // Per target or per this. if (this.beanFactory.isSingleton(beanName)) { throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton"); } MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName); this.aspectFactoryCache.put(beanName, factory); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } } this.aspectBeanNames = aspectNames; return advisors; } } } if (aspectNames.isEmpty()) { return Collections.emptyList(); } List<Advisor> advisors = new ArrayList<>(); for (String aspectName : aspectNames) { List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName); if (cachedAdvisors != null) { advisors.addAll(cachedAdvisors); } else { MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } return advisors; }
我們來看看如何生成List<Advisor>的
// 解析切面類中所有的通知--一個通知生成一個Advisor. List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) { // 獲取標記了@Aspect的類 Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); // 獲取切面類的名稱 String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName(); // 驗證切面類 validate(aspectClass); // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator // so that it will only instantiate once. // 使用包裝的模式來包裝 aspectInstanceFactory, 構建成MetadataAwareAspectInstanceFactory類 MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory); // 通知的集合, 按照排序後 List<Advisor> advisors = new ArrayList<>(); // 獲取切面類中所有的通知方法, 除了帶有@Pointcut註解的方法 for (Method method : getAdvisorMethods(aspectClass)) { // 將候選方法解析為Advisor. Advisor中包含advise和pointcut. 注意: getAdvisor()方法中定義了切面解析的順序 Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName); if (advisor != null) { advisors.add(advisor); } } // If it's a per target aspect, emit the dummy instantiating aspect. if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory); advisors.add(0, instantiationAdvisor); } // Find introduction fields. for (Field field : aspectClass.getDeclaredFields()) { Advisor advisor = getDeclareParentsAdvisor(field); if (advisor != null) { advisors.add(advisor); } } return advisors; }
這裡主要有兩點, 第一個是getAdvisorMethods(aspectClass)獲取當前切面類的所有的AdvisorMethod , 第二個是封裝成的Advisor物件
- 第一步: 解析切面類中所有的通知方法.getAdvisorMethods(aspectClass)
/** * 獲取切面類中所有的方法, 且方法中有@Pointcut註解 * @param aspectClass * @return */ private List<Method> getAdvisorMethods(Class<?> aspectClass) { final List<Method> methods = new ArrayList<>(); // 呼叫doWithMethods. 第二個引數是一個匿名函式, 重寫了doWith方法 ReflectionUtils.doWithMethods(aspectClass, method -> { // 解析切面類中所有的方法, 除了Pointcut if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) { methods.add(method); } }, ReflectionUtils.USER_DECLARED_METHODS); if (methods.size() > 1) { // 對方法進行排序 methods.sort(METHOD_COMPARATOR); } return methods; }
這個方法是, 掃描切面類的所有方法, 將其新增到methods中, 除了Pointcut註解的方法
然後對methods進行排序, 如何排序呢?
private static final Comparator<Method> METHOD_COMPARATOR; static { Comparator<Method> adviceKindComparator = new ConvertingComparator<>( new InstanceComparator<>( Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class), (Converter<Method, Annotation>) method -> { AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method); return (ann != null ? ann.getAnnotation() : null); }); Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName); METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator); }
按照Aroud, Before, After, AferReturning, AfterThrowing的順序對通知方法進行排序
- 第二步: 將候選的方法解析為Advisor. 這裡也是有兩步.具體如下:
/** * 解析切面類中的方法 * @param candidateAdviceMethod 候選的方法 */ @Override @Nullable public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) { validate(aspectInstanceFactory.getAspectMetadata().getAspectClass()); // 獲取切面中候選方法的切點表示式 AspectJExpressionPointcut expressionPointcut = getPointcut( candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass()); if (expressionPointcut == null) { return null; } // 將切點表示式和通知封裝到InstantiationModelAwarePointcutAdvisorImpl物件中, 這是一個Advisor通知 return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName); }
在getPointcut中解析了method,以及切點表示式pointcut
/** * 找到候選方法method屬於哪一種型別的Aspectj通知 * @param candidateAdviceMethod 候選的通知方法 * @param candidateAspectClass 候選的切面類 * @return */ @Nullable private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) { // 第一步: 解析候選方法上的註解,類似@Before(value="pointcut()") // 找到Aspectj註解: @Pointcut, @Around, @Before, @After, @AfterReturning, @AfterThrowing AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); if (aspectJAnnotation == null) { return null; } // 第二步: 解析aspect切面中的切點表示式 AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]); // 解析切點表示式 ajexp.setExpression(aspectJAnnotation.getPointcutExpression()); if (this.beanFactory != null) { ajexp.setBeanFactory(this.beanFactory); } return ajexp; }
如上程式碼, 可知, 這裡也是有兩個操作. 分別是將method解析為Advise, 另一個是解析切面類中的pointcut切點表示式. 返回返回切點表示式.
接下來, 就是將候選方法和切點表示式封裝成Advisor. 在getAdvisor(...)方法中:
// 將切點表示式和通知封裝到InstantiationModelAwarePointcutAdvisorImpl物件中, 這是一個Advisor通知 return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
expressionPointcut: 即切點表示式; candidateAdviceMethod: 即候選方法
public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { // 當前的切點 this.declaredPointcut = declaredPointcut; // 切面類 this.declaringClass = aspectJAdviceMethod.getDeclaringClass(); // 切面方法名 this.methodName = aspectJAdviceMethod.getName(); //切面方法引數的型別 this.parameterTypes = aspectJAdviceMethod.getParameterTypes(); //切面方法物件 this.aspectJAdviceMethod = aspectJAdviceMethod; // aspectJ的通知工廠 this.aspectJAdvisorFactory = aspectJAdvisorFactory; // aspectJ的例項工廠 this.aspectInstanceFactory = aspectInstanceFactory; // advisor的順序 /** * 前面我們看到, Advisor會進行排序, Around, Before, After, AfterReturning, AfterThrowing, 按照這個順序. * 那麼order值是什麼呢?是advisors的size. 如果size是0, 那麼就是第一個方法. 這裡第一個不一定是Around, 他可能沒有Around通知, 也沒有Before通知. */ this.declarationOrder = declarationOrder; // 切面名 this.aspectName = aspectName; if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { // Static part of the pointcut is a lazy type. Pointcut preInstantiationPointcut = Pointcuts.union( aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut); this.pointcut = new PerTargetInstantiationModelPointcut( this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory); this.lazy = true; } else { // A singleton aspect. this.pointcut = this.declaredPointcut; this.lazy = false; this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut); } }
前面已經得到了切入點表示式, 這裡會進行初始化Advice, 初始化的時候, 根據通知的型別進行初始化.
- 環繞通知, 構建一個環繞通知的物件
- 前置通知, 構建一個前置通知的物件
- 後置通知, 構建一個後置通知的物件
- 異常通知, 構建一個異常通知的物件
- 返回通知, 構建一個返回通知的物件
具體程式碼如下:
@Override @Nullable public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { // 候選的切面類 Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); validate(candidateAspectClass); // 通知方法上的註解內容 AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); if (aspectJAnnotation == null) { return null; } // If we get here, we know we have an AspectJ method. // Check that it's an AspectJ-annotated class if (!isAspect(candidateAspectClass)) { throw new AopConfigException("Advice must be declared inside an aspect type: " + "Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]"); } if (logger.isDebugEnabled()) { logger.debug("Found AspectJ method: " + candidateAdviceMethod); } AbstractAspectJAdvice springAdvice; switch (aspectJAnnotation.getAnnotationType()) { case AtPointcut: if (logger.isDebugEnabled()) { logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); } return null; case AtAround: // 封裝成環繞通知的物件 springAdvice = new AspectJAroundAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtBefore: // 封裝成前置通知物件 springAdvice = new AspectJMethodBeforeAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtAfter: // 封裝成後置通知物件 springAdvice = new AspectJAfterAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtAfterReturning: // 封裝成返回通知物件 springAdvice = new AspectJAfterReturningAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation(); if (StringUtils.hasText(afterReturningAnnotation.returning())) { springAdvice.setReturningName(afterReturningAnnotation.returning()); } break; case AtAfterThrowing: // 封裝異常通知物件 springAdvice = new AspectJAfterThrowingAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation(); if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); } break; default: throw new UnsupportedOperationException( "Unsupported advice type on method: " + candidateAdviceMethod); } // Now to configure the advice... springAdvice.setAspectName(aspectName); springAdvice.setDeclarationOrder(declarationOrder); String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); if (argNames != null) { springAdvice.setArgumentNamesFromStringArray(argNames); } springAdvice.calculateArgumentBindings(); return springAdvice; }
這就是我們在之前的結構中說過的, 在解析切面的時候, 會解析切面中的每一個方法, 將其解析成一個Advisor, 而每一個Advisor都包含兩個部分:Advise和pointcut.
最後, 將所有的切面類都解析完, 將所有的Advisor放入到集合advisors中返回.
這樣就完成了切面的解析.
2) 呼叫動態代理
在ioc解析的過程中, 是在什麼時候建立動態代理的呢?
通常是在建立bean初始化之後建立動態代理. 如果有迴圈依賴, 會在例項化之後建立動態代理, 再來感受一下建立bean過程中的操作.
下面我們來看正常的流程, 在初始化之後建立AOP動態代理 .
在建立bean的過程中,一共有三步, 來看看AbstractAutowireCpableBeanFactory.doCreateBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { //第一步: 例項化 instanceWrapper = createBeanInstance(beanName, mbd, args); } // 這裡使用了裝飾器的設計模式 final Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); ...... try { // 第二步:填充屬性, 給屬性賦值(呼叫set方法) 這裡也是呼叫的後置處理器 populateBean(beanName, mbd, instanceWrapper); // 第三步: 初始化. exposedObject = initializeBean(beanName, exposedObject, mbd); } ...... }
在第三步初始化的時候, 要處理很多bean的後置處理器.
@Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
postProcessAfterInitialization(result, beanName);就是處理初始化之後的後置處理器, 下面就從這個方法作為入口分析.
AnnotationAwareAspectJAutoProxyCreator也實現了postProcessAfterInitialization(result, beanName);介面
@Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { /** * 每一個bean在解析的時候都會解析一遍切面. * 為什麼每次都要解析一遍呢? 因為還有另外一種方式-實現Advisor介面的方式實現AOP, 在載入過程中, 可能隨時有新的bean被解析出來. 所以, 需要每次都重新解析一遍,. * 我們在第一次解析的Advisor都已經放入到快取, 在這裡會先從快取中取, 也就是已經解析過的不會重複解析. 也就是不 消耗效能 */ if (bean != null) { // 獲取快取key Object cacheKey = getCacheKey(bean.getClass(), beanName); /** * 因為有可能在迴圈依賴處理的時候已經建立國一遍, 如果是那麼現在就不再建立了,並且刪除 * 在這裡, 我們要處理的是普通類的動態代理, 所以, 需要將迴圈以來建立的動態代理刪掉 */ if (this.earlyProxyReferences.remove(cacheKey) != bean) { // 該方法將返回動態代理的例項 return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
接下來處理的流程如下:
這裡,第三步:刪除迴圈依賴建立的動態代理物件, 為什麼要這樣處理呢?
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 已經被處理過(解析切面的時候, targetSourcedBeans用來儲存自己實現建立動態代理的邏輯) if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } // 判斷bean是否是需要增強的bean /** * 哪些類是不需要增強的呢? * 在解析切面的時候, 基礎類和應該跳過的類是不需要增強的. */ if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } // 判斷是否是基礎類, 或者是否是需要跳過的類 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { // 將其標記為不需要增強的類 this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // 匹配Advisor. 根據類匹配advisors, 至少匹配上一個, 才建立動態代理, 否則不建立動態代理 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); // 匹配了至少一個advisor, 建立動態代理 if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
首先判斷是否是需要跳過的類. 哪些類是需要跳過的類呢?
第一類:基礎類. Advice, Pointcut, Advisor, AopInfrastructureBean.
第二類: 原始的介面類, 以.ORIGINAL開頭的類
接下來, 匹配Advisor.
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { // 第一步: 拿到已經解析出來的advisors(這次是從快取中獲取) List<Advisor> candidateAdvisors = findCandidateAdvisors(); // 第二步:迴圈判斷advisor能否作用於當前bean(原理: 切點是否命中bean) List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); // 第三步: 對匹配bean的advisor進行增強 extendAdvisors(eligibleAdvisors); // 第四步: 對匹配bean的advisor進行排序 if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); } // 返回匹配到的advisors return eligibleAdvisors; }
這裡經過了四步, 具體詳見上述程式碼及註釋.
- 第一步: 從快取中拿到已經解析出來的advisors
- 第二步:迴圈判斷advisor能否作用於當前bean
- 第三步: 對匹配bean的advisor進行增強
- 第四步: 對匹配bean的advisor進行排序
這裡面的第一步: 從快取中取出了已經解析出來的advisors集合. 解析方式是從快取中取出已經解析的advisors
接下來,迴圈遍歷獲得到的advisors, 得到每一個advisor. 判斷advisor是否是目標bean需要增強的通知.
這裡在篩選的時候, 根據切點表示式進行了兩次篩選. 第一次粗篩, 第二次是精篩. 整個目標類, 只要有一個類命中切點表示式, 那麼這個類就是需要被建立動態代理的類, 返回true.
接下來就是要建立動態代理了. 然後,返回建立的動態代理物件.
下面來看看是如何建立動態代理的.
建立動態代理物件有兩種方式: 一種是jdk代理, 一種是cglib代理.
無論是使用xml配置的方式, 還是使用註解的方式, 都有一個引數proxy-target-class, 如果將這個引數設定為true, 表示強制使用cglib代理. 如下所示設定:
使用註解的方式
@EnableAspectJAutoProxy(proxyTargetClass=true)
使用xml配置的方式
<aop: sapectj-autoproxy proxy-target-class="true"></aop:>
所以在建立動態代理之前, 先解析註解或者配置, 看是否配置了proxy-target-class引數. 如果配置了這個引數,且其值為true, 那麼就建立一個cglib代理物件. 否則建立一個JDK代理物件.通常, 我們使用的更多的是spring自己定義的JDK代理物件. 通過Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);建立動態代理
在JDKDynamicAopProxy代理類中有一個invoke()方法. 這個invoke方法, 就是執行代理物件的方法時呼叫的方法.
該方法是通過反射的方法執行目標類中定義的方法的.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
}
3. 呼叫動態代理.
public class LxlMainClass { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class); Calculate calculate = (Calculate) ctx.getBean("lxlCalculate"); /** * 上面的calculate, 就是返回的動態代理的類 * 當呼叫下面的div方法時, 實際上呼叫的是JdkDynamicAopProxy.invoke(...)方法 */ calculate.div(2, 4); ProgramCalculate programCalculate = (ProgramCalculate) ctx.getBean("lxlCalculate"); String s = programCalculate.toBinary(5); System.out.println(s); } }
如上程式碼, 我們在main方法中, 獲取的Calculate物件, 其實是動態代理生成的物件. 當呼叫calculate.div(2, 4)方法時, 其實呼叫的是動態代理的invoke()方法.
@Override @Nullable public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Object target = null; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } else if (method.getDeclaringClass() == DecoratingProxy.class) { // There is only getDecoratedClass() declared -> dispatch to proxy config. return AopProxyUtils.ultimateTargetClass(this.advised); } else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; if (this.advised.exposeProxy) { // 把代理物件暴露線上程變數中. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // Get as late as possible to minimize the time we "own" the target, // in case it comes from a pool. // 獲取目標物件 target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); // 把aop的advisor全部轉化為攔截器, 通過責任鏈模式依次呼叫 /** * 將advisor物件轉換為interceptor物件. * * 為什麼要將advisor都轉化為interceptor攔截器呢? * 主要還是因為要進行責任鏈呼叫. 之前說過, 要想進行責任鏈呼叫, 他們要有一個共同的方法. * 轉化為interceptor以後, 這裡共同的方法就是invoke(). * beforeAdivsor, afterAdvisor, returningAdvisor, throwingAdvisor. 這幾種型別. 只有returningAdvisor和throwingAdvisor會轉化為Interceptor. * 因為beforeAdvisor和adgerAdvisor本身就實現了interceptor介面 */ List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // 攔截器鏈為空 if (chain.isEmpty()) { // 通過反射直接呼叫執行 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // We need to create a method invocation... MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); } // Massage return value if necessary. Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned "this" and the return type of the method // is type-compatible. Note that we can't help if the target sets // a reference to itself in another returned object. retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException( "Null return value from advice does not match primitive return type for: " + method); } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // Must have come from TargetSource. targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } }
呼叫這裡有一個非常經典的呼叫邏輯--呼叫鏈.
在這裡會將匹配方法的advisor物件轉換為interceptor攔截器物件. 為什麼要將advisor轉換為interceptor攔截器呢?
因為要進行責任鏈呼叫. 前面說過, 要想進行責任鏈呼叫, 他們要有一個共同的方法.
轉化為interceptor以後, 這裡共同的方法就是invoke().
beforeAdivsor, afterAdvisor, returningAdvisor, throwingAdvisor. 這幾種型別. 只有returningAdvisor和throwingAdvisor會轉化為Interceptor.
因為beforeAdvisor和adgerAdvisor本身就實現了interceptor介面
如上圖, 呼叫鏈的邏輯是, 呼叫動態代理方法,比如說div(arg1, arg2), 然後執行呼叫鏈中第一個通知advisor1, 然後第一個通知呼叫第二個通知, 在執行第二個, 以此類推, 當所有的通知執行完, 呼叫目標方法div(arg1, arg2), 然後返回執行結果. 我們來看看程式碼的邏輯實現