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

本文详细介绍了Spring AOP中基于JDK动态代理的执行链构建过程,包括从`InvocationHandler`的`invoke()`方法开始,如何构建通知方法的执行链,以及递归调用的火炬传递机制。文中通过对源码的分析,揭示了执行链的缓存、构建、执行等关键步骤,展现了Spring优雅的代码设计。
摘要由CSDN通过智能技术生成

由于md文档部分hugo插件语法不兼容,建议访问作者网站查阅文章:wlizhi.cc

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() 源码:
{ {< highlight java “linenos=table,hl_lines=20 21 29 36 40 43 58,linenostart=1” >}}
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);
		}
	}
}

}
{ {< /highlight >}}
以上代码有点长,删去了一些非关键点的代码。着重看代码中高亮部分,这些是关键节点。

invoke() 的主流程:

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

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

2 执行链的构建

来到 getInterceptorsAndDynamicInterceptionAdvice():

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(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值