Spring AOP以及事务详解(二)

书接上回

在上一篇中,详细介绍了Spring是如何对自定义切面通知以及spring内置的事务切面的识别,加载,解析的过程。那本篇主要介绍bean在创建过程中,如果匹配到了符合的通知器时,创建代理,以及在实际执行时,切面通知是如何发挥作用的,以及事物切面是如何发挥作用的。

代理类创建

如上一篇断点如下:自定义的service中方法上添加有 @Transactional 注解(本文重点) 以及被自定义的两个前置通知的方法命中(这个不是本文的重点) ,所以可以发现这里spring创建出了4个interceptor。接着会进入org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy 方法进行创建代理

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy

scss 复制代码
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}

		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);

		if (proxyFactory.isProxyTargetClass()) {
			// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
			if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
				// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
				for (Class<?> ifc : beanClass.getInterfaces()) {
					proxyFactory.addInterface(ifc);
				}
			}
		}
		else {
			// No proxyTargetClass flag enforced, let's apply our default checks...
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}
        // 获取 Advisor
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}

		// Use original ClassLoader if bean class not locally loaded in overriding class loader
		ClassLoader classLoader = getProxyClassLoader();
		if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
			classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
		}
        // 根据 ProxyFactory 创建代理类
		return proxyFactory.getProxy(classLoader);
	}

可以看到这里做的事情也很清晰,定义了一个清晰的执行步骤,创建一个代理生成工厂 ProxyFactory , 对ProxyFactory 设置一些配置,之后将 specificInterceptors

适配成Advisor(实际上这一步什么都没做,本身之前解析完之后,已经都是Advisor了),

接着就是通过 proxyFactory.getProxy(classLoader) 开始获取代理了。

proxyFactory.getProxy(classLoader)

less 复制代码
public Object getProxy(@Nullable ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
}

可以看到内部又是中转了一道,使用的是 "抽象工厂" 模式,先创建具体的代理工厂,之后根据创建出的代理工厂再创建具体的代理,详细看下吧

kotlin 复制代码
protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
		return getAopProxyFactory().createAopProxy(this);
	}

其中 getAopProxyFactory() 其实只有一个实现就是 DefaultAopProxyFactory ,看下createAopProxy(this) ,可以看到这里返回了真正具有创建代理能力的类了

arduino 复制代码
@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (!NativeDetector.inNativeImage() &&
				(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

到这里算是执行完了 return createAopProxy().getProxy(classLoader); 中的createAopProxy()的部分 之后, 然后通过JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 的getProxy 开始创建具体的bean的代理,下面详细看下

先看JdkDynamicAopProxy#getProxy(classLoader)

less 复制代码
@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
		}
		return Proxy.newProxyInstance(determineClassLoader(classLoader), this.proxiedInterfaces, this);
	}

可以看到 JdkDynamicAopProxy 本身就是一个 InvocationHandler ,所以看下

typescript 复制代码
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 {
			// ....... 省略若干行
			Object retVal;

			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				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);

			// Get the interception chain for this method.
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			// Check whether we have any advice. If we don't, we can fall back on direct
			// reflective invocation of the target, and avoid creating a MethodInvocation.
			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);
			}
			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);
			}
		}
	}

所以逻辑也是非常清晰的

1、获取拦截器chain-其实就是一个list

2、构造一个 MethodInvocation ,然后执行其proceed() ,触发逻辑的执行

下面一步步看,先看下获取拦截器chain,这里解释下,为什么要做这个操作,原因是spring中底层抽象拦截器和MethodInvocation的抽象如下

spring会把方法的执行体抽象成 MethodInvocation ,方法的执行是通过 MethodInvocation.proceed()的方式触发的。所以如果要想使一个无参的方法执行来达到正常反射api的效果比如 method.invoke(instance,args) 的话,则MethodInvocation的内部必要要包含各种元信息(包括instance, method, args 等) 。

而spring底层把拦截器抽象成 MethodInterceptor ,看下其定义,可以看到拦截器只有一个invoke方法,而入参正是上面的MethodInvocation,所以这个套路就很清晰了,拦截器中可以在MethodInvocation入参调用proceed执行前,后,返回之前,或者出现异常,亦或者finally中插入各种想做的逻辑了,甚至都可以忽略MethodInvocation的真正的执行。所以想下环绕通知,前置通知,返回通知,等等都是通过这种方式实现的。而且这种套路在Mybatis的Interceptor以及dubbo的invoker 的设计中都有体现。算是一个中间件中比较常用的套路。好了,相信通过对设计思想的解析之后,带着方向出发,再看下面的源码就不会迷路了

less 复制代码
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {

	@Nullable
	Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;

}

上面解释了spring 方法执行体 和 拦截器 的抽象模型和调用关系之后,就明白了为什么还要把Advisor的列表进行转换了吧,就是要把Advisor转成Spring内部最底层真正能运行的组件MethodInterceptor

List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

方法经过层层调用会来到org.springframework.aop.framework.DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice

其实这个方法做的事情也很简单,为了凸显本质,直接删除不相干的代码,只保留最重要的几行代码 , 可以看到就是从配置中取出之前传入的Advisor, 之后对Advisor的PointCut对当前bean再次做校验(之前其实已经做过了)看是否匹配规则,之后就是把Advisor转成MethodInterceptor

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

		AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
		//这里获取配置中所有的Advisor
        Advisor[] advisors = config.getAdvisors();
		List<Object> interceptorList = new ArrayList<>(advisors.length);
	
		for (Advisor advisor : advisors) {
			if (advisor instanceof PointcutAdvisor) {
				// Add it conditionally.
				PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
				if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
					//...... 省略 .......
                    // 把advisor适配成MethodInterceptor
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                    //...... 省略 .......
            }      
               //...... 省略 .......     
		}
		return interceptorList;
	}

下面详细看下转换过程吧

如下所示,就是先获取Advisor的Advice(也能理解,到这一步了,Pointcut已经校验通过了,没什么用了,只需要真正的切面的执行逻辑了,也就是Advice), 然后如果本身就是MethodInteceptor的实现的话,就不用处理,如果不是的话,就尝试通过Adapter(一个转换工具类或者说转换器)把 Advice 转换成一个MethodInterceptor,所以可以看到这里其实是使用了适配器模式,直接在MethodInterceptor内部集成了Advice,所以这样一来,对于一个MethodInterceptor来说就什么都有了,要执行的拦截的逻辑在Advice中,要执行的真正的方法体通过方法入参MethodInvocation传入了,那Spring还剩什么工作呢?编排, 是的,原料都有了,现在spring只需要通过一个编排规则,发动整个流水加工线就行了

scss 复制代码
@Override
	public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
		List<MethodInterceptor> interceptors = new ArrayList<>(3);
		Advice advice = advisor.getAdvice();
		if (advice instanceof MethodInterceptor) {
			interceptors.add((MethodInterceptor) advice);
		}
		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]);
	}

在看编排之前,先简单看下adapter.getInterceptor(advisor) 是怎么玩的,会发现这里只有三个实现类,原因是因为其他类型的Advice已经实现了MethodInterceptor,不需要再做转换了,下面就以前置通知来看下

typescript 复制代码
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}

}

很简单,就是把Advice传入MethodBeforeAdviceInterceptor中,构造一个MethodBeforeAdviceInterceptor类型的拦截器,其他两个同理。

当把MethodInteceptor都组装好了之后,再组装一个MethodInvocation,之后Spring就可以通过编排触发真正的执行了

MethodInvocation 的组装

ini 复制代码
MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);

这里可以看到是创建了一个MethodInvocation的实例ReflectiveMethodInvocation,把需要的原料都传进去了

之后通过invocation.proceed()方法触发了整个逻辑的执行,通过上面的讲解,既然MethodInvocation能通过procceed()无参方法触发原生方法反射的执行,则必然,在proceed的内部执行了方法的反射执行,而在target method执行之前,则必然要执行下其内部持有的拦截器的逻辑,所以MethodInvocation的实现,在其内部肯定已经提前编排好了执行顺存或者说调用规则,能让拦截器的逻辑和target method的逻辑按照定义的顺序正常执行下去。ok,下面就详细分析下,spring是怎么操作的。

在上面构造完成MethodInvocation 之后,接着就是调用其 proceed() 方法了,所有的编排的逻辑应该都在这个方法里有所体现了。注:下面的场景会以一个before前置通知为具体场景分析这个调用链路

kotlin 复制代码
@Override
@Nullable
public Object proceed() throws Throwable {
    // We start with an index of -1 and increment early.
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }

    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // ....... 省略 .......
    }
    else {
        // 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);
    }
}

看下上面这里的逻辑,会发现MethodInvocation的内部有一个Index用于记录当前执行到的Interceptor,如果发现已经执行完了所有的拦截器,那么就会去真正执行Target Method方法。否则就会去执行当前获取到的拦截器,并把当前MethodInvocation作为参数传入拦截器,所以可想而知,对与before前置通知拦截器而言,当他接受到一个MethodInvacation之后,肯定是先执行Advice的逻辑,执行完成之后,无脑通过MethodInvocation.proceed() 把执行权再返回回去,是不是,可看前置通知拦截器的实现

less 复制代码
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    return mi.proceed();
}

可以看到确实如此。

仔细回想下,这种操作模式,是不是跟tomcat的FilterChain的执行方式很像,在http请求处理真正调用到Servlet的service方法之前,需要先走一遍所有的Filter,tomcat也是通过在内部集成一个Filter列表,以及一个index, 当所有的Filter都执行过之后,会真正走service调用。

而且最重要的这种设计模式还解决了一个问题,因为这种设计模式其实有点像栈,先执行的Interceptor反而最后才会被返回,什么意思呢,就是调用栈是类似如下,和Mybatis的Interceptor的调用栈也是一样的,这是他们的共同的特点。

arduino 复制代码
Interceptor1#before
Interceptor2#before
.....
methodInvocation#invoke
.....
Interceptor2# after
Interceptor2# after

那其实这种模式除了使用这种spring以及tomcat的实现方式以为,还可以怎么实现呢?

那就是mybatis的所使用的实现方式:装饰器模式

通过上面的调用栈也可以看到,最终的调用栈其实就是外层装饰逻辑不断先执行,知道内部持有的是真正的执行体的时候,开始执行真正的业务逻辑,这不就是装饰器模式吗

但同时如果使用装饰器模式的话,调用链路就没这么清晰了,spring这种实现显然更清晰些

总结

这一篇还是在打扫AOP以及事务执行的外围,还没开始真正进行事务的分析,不过基本所有的外围都打扫干净了,目前只剩下直接分析拦截器的逻辑了

本篇呢,更侧重分析spring中的设计模式的使用,以及和其他中间件中使用相同设计模式的相同点和差一点,对于常见的中间件中拦截器的设计的理解会很有帮助

下一篇,开始正是对事物拦截器进行发起总攻

相关推荐
用户83071968408215 小时前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解16 小时前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解16 小时前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记20 小时前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者2 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840822 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解2 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者3 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺3 天前
搞懂@Autowired 与@Resuorce
java·spring boot·后端