AOP 执行链的创建和执行原理

警告
本文最后更新于 2020-12-01,文中内容可能已过时。

spring源码系列文章,示例代码的中文注释,均是 copy 自 https://gitee.com/wlizhi/spring-framework

链接中源码是作者从 github 下载,并以自身理解对核心流程及主要节点做了详细的中文注释。


1 主流程

以 jdk 动态代理为例,在代理对象生成后,会加入到 ioc 容器中。当有代码调用到对应的方法时,会走到 InvocationHandler 的 invoke()。

在 spring-aop 模块中,jdk动态代理是在 JdkDynamicAopProxy.getProxy() 中生成的。在生成代理对象时,Proxy.newProxyInstance() 中第三个参数传入的是 this,JdkDynamicAopProxy本身也是 InvocationHandler 的实现类。那么,在调用代理对象方法时,一定会调用到 JdkDynamicAopProxy.invoke()。

来到 invoke() 源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   		MethodInvocation invocation;
   		// 存储在调用方法之前,当前线程中的方法调用链中的上一个代理对象。
   		// 比如 代理A -> 代理B -> 当前代理方法。那么方法内 oldProxy 会存储前面的代理对象。因为在这个方法执行完之前,会将
   		// 一个全局的ThreadLocal变量置换为当前的代理对象,旧的值需要缓存起来,等到当前方法执行结束,再设置回原值。
   		Object oldProxy = null;
   		boolean setProxyContext = false;
   
   		// 从代理工厂中拿到TargetSource对象,该对象包装了被代理实例bean
   		TargetSource targetSource = this.advised.targetSource;
   		Object target = null;
   
   		try {
   			// 被代理对象的equals方法和hashCode方法不被代理的,不会走切面,此处代码省略...
   
   			Object retVal;
   			// 如果需要暴露代理对象(在配置文件或者注解中全局配置的),则将当前代理对象放入到一个全局的ThreadLocal变量中。
   			if (this.advised.exposeProxy) {
   				oldProxy = AopContext.setCurrentProxy(proxy);
   				setProxyContext = true;
   			}
   
   			// 这个target就是被代理实例
   			target = targetSource.getTarget();
   			Class<?> targetClass = (target != null ? target.getClass() : null);
   
   			// 从代理工厂中拿过滤器链 Object是一个MethodInterceptor类型的对象,其实就是一个advice对象,或者说是MethodInterceptor
   			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
   
   			// 如果该方法没有执行链,则说明这个方法不需要被拦截,则直接反射调用。
   			// 因为执行链不论是否为空,都会被缓存到一个ConcurrentHashMap中,执行只有第一次相对较慢。
   			if (chain.isEmpty()) {
   				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
   				// 这里返回了method.invoke()执行后的返回值。
   				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
   			}
   			else {
   				// 这里面调用了执行链,在执行链的调用中,会进行火炬传递。具体执行流程,重点在ReflectiveMethodInvocation.proceed()。
   				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
   				// 在递归结束后,这里最终也会返回实际方法的返回值。火炬传递的过程,实际上也是递归,只是将ReflectiveMethodInvocation
   				// 本身当做参数不断地向下传递。
   				retVal = invocation.proceed();
   			}
            // 省略无关代码...
   			return retVal;
   		}finally {
   		    // 单例情况下,targetSource类型是SingletonTargetSource,这个类中的releaseTarget是个空方法,不用看。
   			if (target != null && !targetSource.isStatic()) {
   				targetSource.releaseTarget(target);
   			}// 如果配置了允许暴露代理对象,在前面设置ThreadLocal变量时,会把局部变量setProxyContext设置为true
   			if (setProxyContext) {
   				// 将旧的代理对象设置到当前代理对象中,这里和执行链火炬传递式无关的。
   				// 如果当前代理方法是从一个代理对象方法内调用过来的,那么这个ThreadLocal变量中是有值的,执行当前代理对象方法时,
   				// 设置为当前的代理对象,执行完后,需要重新设置回原来的代理对象,这样才不会乱套。
   				// 其用途是,当项目中配置允许暴露代理对象时,可以通过AopContext.currentProxy() 来获取到当前线程正在执行的代理对象。
   				// Restore old proxy.
   				AopContext.setCurrentProxy(oldProxy);
   			}
   		}
   	}
}
以上代码有点长,删去了一些非关键点的代码。着重看代码中高亮部分,这些是关键节点。

invoke() 的主流程:

  1. 如果允许暴露代理对象,将 ThreadLocal 类型的全局变量 currentProxy 设置为当前代理对象,将旧的代理对象放到局部变量 oldProxy 中,setProxyContext 设置为 true。
  2. 获取通知方法执行链。(执行链的构建)
  3. 如果执行链不为空,将其封装到 ReflectiveMethodInvocation 中,调用 proceed()。(执行链递归调用的火炬传递)
  4. 如果有必要,全局变量 currentProxy 还原为调入当前方法时的状态。

第1、4条相对简单,主要是第2、3条相对复杂些。

2 执行链的构建

来到 getInterceptorsAndDynamicInterceptionAdvice():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class AdvisedSupport extends ProxyConfig implements Advised {
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
		MethodCacheKey cacheKey = new MethodCacheKey(method);
		List<Object> cached = this.methodCache.get(cacheKey);
		if (cached == null) {
			// 根据Method来获取执行链。将获取到的执行链加入到缓存中,键值是根据MethodCacheKey,实际上包装的就是Method对象。
			cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
					this, method, targetClass);
			this.methodCache.put(cacheKey, cached);
		}
		return cached;
	}
}

这里使用了一个并发安全的map作为缓存,因为执行链的构建相对还是比较耗时的(相对 ConcurrentHashMap 的 get()),构建工作只执行一次,后面都是从缓存中获取。

往下追踪代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable {

	@Override
	public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
			Advised config, Method method, @Nullable Class<?> targetClass) {

		AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
		Advisor[] advisors = config.getAdvisors();
		List<Object> interceptorList = new ArrayList<>(advisors.length);
		Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
		Boolean hasIntroductions = null;

		for (Advisor advisor : advisors) {
			if (advisor instanceof PointcutAdvisor) {
				PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
				if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
					MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
					boolean match;
					// 省略部分代码...
					match = mm.matches(method, actualClass);
					
					if (match) {
						// TODO 重点 获取MethodInterceptor列表。
						MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
						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));
						}
					}
				}
			}
			// 省略...
		}

		return interceptorList;
	}
}

从 Advised 中拿到 advisors,里面包含了前面流程搜集的所有通知方法包装的 Advisor,对其遍历,对 PointcutAdvisor 类型的执行方法匹配,匹配为 true,调用 getInterceptors(),将获取到的 MethodInterceptor 封装到 list 中。

下次再调用到当前方法时,从缓存中直接获取的就是这个 MethodInterceptor 列表。

来到 getInterceptors(),看一下是怎么获取的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
    public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
		// 从Advice获取MethodInterceptor。before、afterReturning的增强会被AdvisorAdapter匹配到。
		// 其他自定义的增强方法,都是MethodInterceptor类型,被强转后,直接加入到interceptors中。
		List<MethodInterceptor> interceptors = new ArrayList<>(3);
		Advice advice = advisor.getAdvice();
		// 如果是MethodInterceptor,将其添加到拦截器链中
		if (advice instanceof MethodInterceptor) {
			interceptors.add((MethodInterceptor) advice);
		}
		// 如果有AdviceorAdapter支持当前advice,进行适配,获取interceptor。adapters变量的值,是在无参构造函数中初始化的。
		for (AdvisorAdapter adapter : this.adapters) {
			if (adapter.supportsAdvice(advice)) {
				interceptors.add(adapter.getInterceptor(advisor));
			}
		}
		if (interceptors.isEmpty()) {
			throw new UnknownAdviceTypeException(advisor.getAdvice());
		}
		return interceptors.toArray(new MethodInterceptor[0]);
	}
}

上面代码流程:

  1. 从 advisor 中获取 Advice 对象,如果这个 Advice 是 MethodInterceptor 类型,则直接添加到 interceptors 中
  2. 遍历 adapters,如果支持当前 Advice,使用 getInterceptor 获取对应的 MethodInterceptor(至于adapters存储了什么,在哪里初始化的,前文中有)。
  3. 将 interceptors 转换为数组返回。

这里看两个典型的getInterceptor得实现。

@Before 注解的通知方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
	// 是否支持指定的 Advice
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}
	// 从Advisor中获取Advice,并封装到MethodBeforeAdviceInterceptor
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}

}

@AfterReturning 注解的通知方法:

1
2
3
4
5
6
7
8
9
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) { return (advice instanceof AfterReturningAdvice); }
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
		return new AfterReturningAdviceInterceptor(advice);
	}
}

从上面可以看到,几乎是一模一样的逻辑。封装过程很简单。

3 执行链递归调用的火炬传递

构建完执行链之后,就是执行了。

主流程源码中已经知道,真正的执行是在 proceed() 中进行的。

看一下 proceed() 源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
    // 当前执行链的索引,标识执行到哪一个通知方法了。
    private int currentInterceptorIndex = -1;
	public Object proceed() throws Throwable {
		//	We start with an index of -1 and increment early.
		// 如果执行链中的advice全部执行完,则直接调用joinPoint方法,就是被代理方法。
		// 这里的成员变量currentInterceptorIndex会记录目前执行到哪一个增强方法了。
		// 最终一定会返回代理方法的返回值,因为没有其他路径返回了,其他路径都是递归调用。
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		// 执行到这个方法,说明进入到了增强方法,当前执行的索引+1,当执行到最后一个时,此值会与执行链的长度-1相等,这时候就会调用被代理方法。
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		// 这段不用看,直接看else里面的代码。
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// 省略...
		}
		else {
			// 调用MethodInterceptor中的invoke方法,并将自身作为参数向下传递,这里就是个火炬传递的过程。
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}
}

已经知道,我们定义的切面通知方法,在执行链中都被封装为 MethodInterceptor 类型了。

注意成员变量 currentInterceptorIndex,它是用来标识执行链执行到哪个节点了。初始值是-1,每调用一个通知方法都会自增,当达到执行链的末尾,就会通过反射执行被代理方法。

执行到上面源码方法末尾时,会调用执行链中索引为 ++this.currentInterceptorIndex 节点的 MethodInterceptor,执行 invoke(this),注意这个 invoke(this) 中的参数,将 this 传递进去,也就是说每一个 MethodInterceptor 都持有了 ReflectiveMethodInvocation。而调用执行链是通过调用 ReflectiveMethodInvocation.proceed()。

4 总结

  1. 执行链的缓存是在 AdvisedSupport 中封装的,成员变量名 methodCache。执行链的构建是在 AdvisorChainFactory 的实现类 DefaultAdvisorChainFactory 中完成的,AdvisedSupport 中有成员变量 advisorChainFactory。AdvisedSupport 负责缓存和获取执行链,AdvisorChainFactory负责构建执行链。

  2. AdvisedSupport 中缓存了所有的 Advisor,在调用代理方法时,第一次调用会计算有哪些 Advisor 匹配到这个代理方法,将匹配到的 Advisor 获取到 Advice,转换为 MethodInterceptor,封装到执行链中,并将 方法名-执行链 缓存到 methodCache 中,methodCache 是一个 ConcurrentHashMap(并发安全的map)。后面再调用会从 methodCache 中获取。

  3. 调用代理方法时,火炬传递式的递归执行,这个操作封装在 ReflectiveMethodInvocation 类中,也就是说 ReflectiveMethodInvocation 负责代理方法的执行。其执行逻辑是递归调用自身,每次调用取执行链中下一个索引的通知方法,并将自身作为参数在通知方法中传递,直至执行链末尾,调用完被代理方法,返回被代理方法执行后的返回值。

  4. 之所以这么做,也是为了解耦。充分体现了开发原则中的单一职责原则(一个类只干一件事,也是最重要的一个原则,其他原则都是为其展开的)。

  5. 使用 currentInterceptorIndex 索引来标识执行节点的位置的好处:不用在具体节点中,关心执行链参数是什么、以及如何传递的,可以看到 ReflectiveMethodInvocation.proceed() 是一个无参数的方法。

  6. 至于为什么在构建执行链的时候,要把所有类型的 Advice 都封装成 MethodInterceptor ?设想一下,如果不这么做,在火炬传递的过程中,会存在大量的 if 语句,且会在一定程度丧失扩展性。这也是策略模式的一种体现,消除了大量 if 语句,且使得代码变得可扩展。

至于执行链中每一个节点是如何执行,并将 ReflectiveMethodInvocation 向下传递的,在别的章节说明。

附 - 感想

spring 真的是将代码写成了艺术。不愧是音乐学博士写出的代码,真的很优雅。

附 - 递归的定义(来源百度百科)

程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有 直接或间接 调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。