SpringAop源碼分析(基於註解)四:攔截器鏈

在以前的文章咱們分析了通知器的建立與篩選和AOP建立代理對象的過程,如今代理對象已經有了,接下來咱們看一下是如何執行通知器的邏輯的。java

前言

經過閱讀這篇文章,能夠了解到如下幾個問題:緩存

  • 通知的是如何起做用的?
  • 多個通知的執行順序是怎樣的?
  • 多個切面的多個通知的執行順序是怎樣的?
  • @Transactional註解,調用本類方法爲何不起做用?

下面咱們能夠帶着這些疑問來看bash

1、invoke()

本文依據JdkDynamicAopProxy來分析,對CGLIB感興趣的同窗看一看ObjenesisCglibAopProxy相關代碼。 JdkDynamicAopProxy實現了InvocationHandler接口,咱們來看下invoke()方法:app

//JdkDynamicAopProxy.java

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	MethodInvocation invocation;
	Object oldProxy = null;
	boolean setProxyContext = false;

	TargetSource targetSource = this.advised.targetSource;
	Object target = null;

	try {
		//若是目標對象沒有定義equals()方法的話,就會直接調用而不會加強
<1>		if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
			// The target does not implement the equals(Object) method itself.
			return equals(args[0]);
		}
		//若是目標對象沒有定義hashCode()方法的話,就會直接調用而不會加強
		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);
		}
		//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;

		// 若是 exposeProxy 屬性爲 true,則暴露代理對象
		// exposeProxy 是 @EnableAspectJAutoProxy 註解的屬性之一
<2>		if (this.advised.exposeProxy) {
			// Make invocation available if necessary.
			// 向 AopContext 中設置代理對象
			oldProxy = AopContext.setCurrentProxy(proxy);
			setProxyContext = true;
		}

		//得到目標對象的類
<3>		target = targetSource.getTarget();
		Class<?> targetClass = (target != null ? target.getClass() : null);

		//獲取能夠應用到此方法上的 Interceptor 攔截器 列表,而且排序
<4>		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);


		//若是沒有能夠應用到此方法的通知(Interceptor),此直接反射調用 method.invoke(target, args)
<5>		if (chain.isEmpty()) {
			// We can skip creating a MethodInvocation: just invoke the target directly
			// Note that the final invoker must be an InvokerInterceptor so we know it does
			// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
			Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
			retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
		}
<6>		else {
			//建立 MethodInvocation,將攔截器鏈放入
			invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
			// 執行攔截器鏈
			retVal = invocation.proceed();
		}

		// 獲取方法返回值類型
<7>		Class<?> returnType = method.getReturnType();
		if (retVal != null && retVal == target &&
				returnType != Object.class && returnType.isInstance(proxy) &&
				!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
			// 若是方法返回值爲 this,即 return this; 則將代理對象 proxy 賦值給 retVal
			retVal = proxy;
		}
		// 若是返回值類型爲基礎類型,好比 int,long 等,當返回值爲 null,拋出異常
		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);
		}
	}
	}
複製代碼

這個invoke()方法主要有如下幾個步驟:框架

  • <1>處,對equals()、hashCode()等方法進行判斷
  • <2>處,處理exposeProxy屬性,代理的暴露方式
  • <3>處,獲取目標對象的Class
  • <4>處,獲取能夠應用到當前方法的攔截器鏈
  • <5>處,攔截器鏈爲空,直接調用當前方法,不作加強
  • <6>處,執行攔截器鏈
  • <7>處,獲取返回值類型,並校驗

咱們重點關注第<4>步和第<6>步,這兩個地方很是重要,第<2>步涉及比較多,最後咱們再分析,先來看下第<4>步。ide

1.一、獲取能夠應用到當前方法的攔截器鏈

代碼:源碼分析

//AdvisedSupport.java

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
	MethodCacheKey cacheKey = new MethodCacheKey(method);
	//從緩存中獲取
	List<Object> cached = this.methodCache.get(cacheKey);
	if (cached == null) {
		//獲取全部的攔截器
		cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
				this, method, targetClass);
		this.methodCache.put(cacheKey, cached);
	}
	return cached;
	}
複製代碼

繼續深刻:單元測試

//DefaultAdvisorChainFactory.java

/** * 從提供的配置實例config中獲取advisor列表,遍歷處理這些advisor.若是是IntroductionAdvisor, * 則判斷此Advisor可否應用到目標類targetClass上.若是是PointcutAdvisor,則判斷 * 此Advisor可否應用到目標方法method上.將知足條件的Advisor經過AdvisorAdaptor轉化成Interceptor列表返回. */
@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, @Nullable Class<?> targetClass) {

    List<Object> interceptorList = new ArrayList<>(config.getAdvisors().length);
    Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
    //查看是否包含IntroductionAdvisor
    boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
    //這裏實際上註冊一系列AdvisorAdapter,用於將 通知Advisor 轉化成 方法攔截器MethodInterceptor
    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    //遍歷
    for (Advisor advisor : config.getAdvisors()) {
    	if (advisor instanceof PointcutAdvisor) {
    		PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
    		//經過ClassFilter對當前Bean類型進行匹配
    		if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
    			//將 通知Advisor 轉化成 攔截器Interceptor
    			MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
    			MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
    			//檢查當前advisor的pointcut是否能夠匹配當前方法
    			if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
    				if (mm.isRuntime()) {
    					// Creating a new object instance in the getInterceptors() method
    					// isn't a problem as we normally cache created chains.
    					for (MethodInterceptor interceptor : interceptors) {
    						//加入攔截器鏈
    						interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
    					}
    				}
    				else {
    					//加入攔截器鏈
    					interceptorList.addAll(Arrays.asList(interceptors));
    				}
    			}
    		}
    	}
    	else if (advisor instanceof IntroductionAdvisor) {
    		IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
    		// IntroductionAdvisor 類型的通知器,僅進行類級別的匹配便可
    		if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
    			Interceptor[] interceptors = registry.getInterceptors(advisor);
    			interceptorList.addAll(Arrays.asList(interceptors));
    		}
    	}
    	else {
    		Interceptor[] interceptors = registry.getInterceptors(advisor);
    		interceptorList.addAll(Arrays.asList(interceptors));
    	}
    }
    
    return interceptorList;
}
複製代碼

這裏的主邏輯不是很複雜:測試

  • 遍歷全部通知器
  • 對於 PointcutAdvisor 類型的通知器,這裏要調用通知器所持有的切點(Pointcut)對類和方法進行匹配,匹配成功說明應向當前方法織入通知邏輯
  • 將通知Advisor 轉化成 攔截器Interceptor
  • 返回攔截器數鏈

這裏返回的攔截器鏈是有序的,按照afterReturn、after、around、before排好序,(具體排序是在獲取全部通知的時候sortAdvisors(eligibleAdvisors)),方便後面執行。this

1.二、執行攔截器鏈

如今有了攔截器鏈,接下來再看下怎麼執行的:

//ReflectiveMethodInvocation.java

//當前攔截器下標
private int currentInterceptorIndex = -1;
//攔截器鏈集合
protected final List<?> interceptorsAndDynamicMethodMatchers;

public Object proceed() throws Throwable {
	// We start with an index of -1 and increment early.
	// 攔截器鏈中的最後一個攔截器執行完
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		//執行目標方法
		return invokeJoinpoint();
	}

	//每次執行新的攔截器,下標+1
	Object interceptorOrInterceptionAdvice =
			this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

	//若是要動態匹配切點
	if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
		// Evaluate dynamic method matcher here: static part will already have
		// been evaluated and found to match.
		InterceptorAndDynamicMethodMatcher dm =
				(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
		// 若是是動態切點,須要進行參數匹配,以肯定是否須要執行該動態橫切邏輯
		if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
			return dm.interceptor.invoke(this);
		}
		else {
			//動態切點匹配失敗,略過當前Intercetpor,調用下一個Interceptor
			return proceed();
		}
	}
	else {
		//執行當前Intercetpor
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
	}
	}
複製代碼

proceed 根據currentInterceptorIndex按照倒序執行攔截器鏈,每次執行完+1,當全部攔截器執行完後,再執行目標方法。 這裏咱們能夠提出幾個疑問:

  • 當一方法匹配了多個通知時,不一樣通知之間是按照什麼順序來執行的?
  • 從代碼上看,全部通知都執行完畢後纔會執行目標方法,那後置通知又是如何實現的?

咱們帶着這2個疑問往下看,

proceed中的invoke(this)方法根據通知的類型,有不一樣的實現類,如:

  • @Before 對應 MethodBeforeAdviceInterceptor

  • @After 對應 AspectJAfterAdvice

  • @AfterReturning 對應 AfterReturningAdviceInterceptor

  • @AfterThrowing 對應 AspectJAfterThrowingAdvice

  • @Around 對應 AspectJAroundAdvice

1.2.一、後置通知

因爲攔截器鏈是按照倒序排列的,咱們先來看下 後置通知 的代碼:

//AspectJAfterAdvice.java

public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable {

	public AspectJAfterAdvice( Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
		super(aspectJBeforeAdviceMethod, pointcut, aif);
	}


	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			//執行下一個攔截器鏈
			return mi.proceed();
		}
		finally {
			//執行後置邏輯
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		}
	}

	@Override
	public boolean isBeforeAdvice() {
		return false;
	}

	@Override
	public boolean isAfterAdvice() {
		return true;
	}
}
複製代碼

能夠看到,後置攔截器是會先繼續執行下一個攔截器,當攔截器鏈執行完畢以後,proceed()再執行目標方法,最後執行後置邏輯。

1.2.二、環繞通知

咱們再來看下環繞攔截器

//AspectJAroundAdvice.java

public Object invoke(MethodInvocation mi) throws Throwable {
	if (!(mi instanceof ProxyMethodInvocation)) {
		throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
	}
	ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
	ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
	JoinPointMatch jpm = getJoinPointMatch(pmi);
	//執行環繞邏輯
	return invokeAdviceMethod(pjp, jpm, null, null);
	}
複製代碼

環繞攔截器會直接執行環繞邏輯,而 因爲咱們以前在LogAspect中配置了以下代碼:

@Aspect
@Component
@EnableAspectJAutoProxy
public class LogAspect {

	@Pointcut("execution(* com.mydemo.work.StudentController.getName(..))")
	private void log(){}


	@Before("log()")
	public void doBefore() {
		System.out.println("===before");
	}

	@After("execution(* com.mydemo.work.StudentController.getName(..))")
	public void doAfter() {
		System.out.println("===after");
	}

	@AfterReturning("execution(* com.mydemo.work.StudentController.getName(..))")
	public void doAfterReturn() {
		System.out.println("===afterReturn");
	}

	@Around("execution(* com.mydemo.work.StudentController.getName(..))")
	public void doAround(ProceedingJoinPoint pjp) throws Throwable {

		System.out.println("===around before");
		pjp.proceed();
		System.out.println("===around after");
	}
}
複製代碼

因此該攔截器會先執行========around before,而後再執行下一個攔截器。

1.2.三、前置通知

咱們再來看下前置攔截器

//MethodBeforeAdviceInterceptor.java

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {

	private MethodBeforeAdvice advice;

	/** * Create a new MethodBeforeAdviceInterceptor for the given advice. * @param advice the MethodBeforeAdvice to wrap */
	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		//執行前置邏輯
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
		//執行下一個攔截器
		return mi.proceed();
	}
}
複製代碼

能夠看到前置攔截器在執行完 前置方法 以後,又調用MethodInvocation#proceed()方法,繼續去執行下一個攔截器。

2、通知的執行順序

2.一、同一Aspect下

到這裏你們可能有點繞,咱們來捋一下通知的執行順序,也就是攔截器的執行順序。
首先,前面說過攔截器鏈是按照倒序排序的,以下:

能夠看到這裏的通知是按照:afterReturn、after、around、before排序的。
畫一下執行流程圖:

再用單元測試跑一遍 LogAspect的相關代碼,打印結果以下:

===around before
===before
do getName
===around after
===after
===afterReturn
複製代碼

和咱們猜想的同樣。如今咱們能夠回答前面的問題了,就是一個方法匹配多個通知時,按照什麼順序執行。

2.二、多個Aspect下

可是,在實際中,一般一個方法可能被多個Aspect攔截,好比咱們想讓業務方法先被 日誌Aspect 攔截,而後被 異常Aspect攔截。

Spring框架已經替咱們想到了這個問題,若是咱們有多個Aspect,實際上它們會隨機執行,沒有明確的順序。可是Spring提供了@Order註解,可讓咱們指定Aspect的執行順序。 好比咱們新加一個處理異常的Aspect:

@Aspect
@Component
@EnableAspectJAutoProxy
@Order(2)
public class ErrorAspect {

	@Pointcut("execution(* com.mydemo.work.StudentController.getName(..))")
	private void log(){}


	@Before("log()")
	public void doBeforeError() {
		System.out.println("=== error before");
	}

	@After("execution(* com.mydemo.work.StudentController.getName(..))")
	public void doAfterError() {
		System.out.println("=== error after");
	}

	@AfterReturning("execution(* com.mydemo.work.StudentController.getName(..))")
	public void doAfterReturnError() {
		System.out.println("=== error afterReturn");
	}

	@Around("execution(* com.mydemo.work.StudentController.getName(..))")
	public void doAroundError(ProceedingJoinPoint pjp) throws Throwable {

		System.out.println("=== error around before");
		pjp.proceed();
		System.out.println("=== error around after");
	}
}
複製代碼

同時給LogAspect加上註解@Order(1),也就意味着LogAspect的通知要比ErrorAspect先執行。 先來單元測試跑一下看看結果:

===around before
===before
=== error around before
=== error before
do getName
=== error around after
=== error after
=== error afterReturn
===around after
===after
===afterReturn
複製代碼

能夠看到,確實是LogAspect的通知先執行了,這是爲何呢?咱們來debug源碼看一下,主要看攔截器鏈,如圖:

能夠看到,攔截器鏈已經按照順序排列好了,因此代碼只要按照這個攔截器鏈順序執行,就能保證2個切面有序攔截。
那麼它爲何要這樣排序呢?咱們來畫個圖:

如上圖所示,這2個Aspect就像2個圓圈在外面攔截,中間是目標方法。
當一個請求進來要執行目標方法:

  • 首先會被外圈的@Order(1)攔截器攔截
  • 而後被內圈@Order(2)攔截器攔截
  • 執行完目標方法後,先通過@Order(2)的後置攔截器
  • 最後再經過@Order(1)的後置攔截器

到這裏咱們多個通知的執行順序就分析完了,這裏面仍是要去從源碼層面理解,不能死記硬背,這樣才能記得牢固。

3、exposeProxy

上面咱們分析invoke()方法的時候,有下面這段代碼:

// 若是 exposeProxy 屬性爲 true,則暴露代理對象
	// exposeProxy 是 @EnableAspectJAutoProxy 註解的屬性之一
	if (this.advised.exposeProxy) {
		// Make invocation available if necessary.
		// 向 AopContext 中設置代理對象
		oldProxy = AopContext.setCurrentProxy(proxy);
		setProxyContext = true;
	}
複製代碼

本小節咱們就來詳細分析一下這裏是什麼意思。
首先 exposeProxy 是註解@EnableAspectJAutoProxy中的一個屬性,它能夠被設置爲true或false,默認是false。
這個屬性是爲了解決目標方法調用同對象中其餘方法時,其餘方法的切面邏輯沒法執行的問題
啥意思呢?咱們來舉個例子:

public class Student implements Person {

    @Override
    public void haveFun() {
        System.out.println("籃球");
        this.hello("足球");
    }

    @Override
    public void haveFun(String action) {
        System.out.println("haveFun " +  action);
    }
}
複製代碼

在同一個類Student.class中,一個haveFun()方法調用了本類中的另外一個haveFun(String action)方法,此時haveFun(String action)上的切面邏輯就沒法執行了。

3.一、爲何本類調用無效?

那爲何會這樣呢?

咱們知道AOP的本質就是給目標對象生成一個代理對象,對本來的方法進行加強,以後執行的方法都是咱們代理類中的方法。
haveFun()方法中使用的是this.hello("足球"),這裏的this不是代理對象,而是原始對象,所以經過原始對象調用haveFun(String action)是不會被加強的,因此切面就不會起做用。

那麼問題又來了,爲何這裏的this不是代理對象,而是原始對象呢?

3.二、爲何this不是代理對象

上面代碼中有一個ReflectiveMethodInvocation#proceed()方法以下:

public Object proceed() throws Throwable {
	// We start with an index of -1 and increment early.
	// 攔截器鏈中的最後一個攔截器執行完
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		//執行目標方法
		return invokeJoinpoint();
	}

	//...省略
		if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
			return dm.interceptor.invoke(this);
		}
        //...省略
	}
	else {
		//執行當前Intercetpor
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
	}
	}
複製代碼

在全部攔截器執行完以後,會去執行目標方法,咱們跟蹤這個方法invokeJoinpoint()

//ReflectiveMethodInvocation.java

protected Object invokeJoinpoint() throws Throwable {
        //重點 this.target
	return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
	}
複製代碼
//ReflectiveMethodInvocation.java

public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args) throws Throwable {

	// Use reflection to invoke the method.
	try {
		ReflectionUtils.makeAccessible(method);
		//重點 target
		return method.invoke(target, args);
	}
    //...省略
	}
複製代碼

能夠看到,這裏調用的是原始目標對象target來執行咱們的目標方法,因此此時haveFun()方法中的this.hello("足球")的這個this實際上是原始目標對象。

3.三、exposeProxy是如何起做用的?

因此exposeProxy這個屬性就是來解決這個問題的,那麼它是如何起做用的呢? 咱們回過頭來看代碼:

// 若是 exposeProxy 屬性爲 true,則暴露代理對象
	// exposeProxy 是 @EnableAspectJAutoProxy 註解的屬性之一
	if (this.advised.exposeProxy) {
		// Make invocation available if necessary.
		// 向 AopContext 中設置代理對象
		oldProxy = AopContext.setCurrentProxy(proxy);
		setProxyContext = true;
	}
複製代碼

繼續跟蹤:

//AopContext.java
//存儲代理對象 ThreadLocal
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");

static Object setCurrentProxy(@Nullable Object proxy) {
	Object old = currentProxy.get();
	if (proxy != null) {
	        //存儲代理對象到 ThreadLocal
		currentProxy.set(proxy);
	}
	else {
		currentProxy.remove();
	}
	return old;
}
複製代碼

若是exposeProxy被設置爲了true,會把代理對象存到ThreadLocal中,而在本類中調用的時候,會從ThreadLocal中獲取代理類來調用目標方法,就能夠解決原本調用的問題。

最後再來看下面這種狀況:

public class Student implements Person {

    @Override
    @Transactional
    public void haveFun() {
        System.out.println("籃球");
        this.hello("足球");
    }

    @Override
    @Transactional
    public void haveFun(String action) {
        System.out.println("haveFun " +  action);
    }
}
複製代碼

這種狀況下,haveFun(String action)的事務不會生效,緣由就是咱們剛纔分析的。實際上@Transactional本質上也是AOP實現的,因此也會有這個問題。
解決方案就是給@EnableAspectJAutoProxy註解設置exposeProxy=true

總結

時序圖:

到這裏SpringAOP的源碼分析就告一段落了,因爲本人經驗和技術水平有限,因此只能先了解這麼多,若有錯誤,歡迎提出意見。

參考:
www.tianxiaobo.com/2018/06/22/…

相關文章
相關標籤/搜索