搞懂AOP之一,拦截器链

4,676 阅读10分钟

写在前面

本篇不准备对着源码一步一步讲解这一过程,而是准备先剖析一下这一过程中所用到的一些类和方法的功能,当这些“螺丝”都熟悉之后,再一步一步看源码的时候,便会畅通无阻。这也是我在阅读源码过程中的感觉,因为spring的整套源码一环套一环,流程很长,可能跟着跟着就不知所云了。但是如果我们分解一长串流程,一步一步去拆解,最后串联起来的那一刻就会恍然大悟。

相关知识

AOP代理增强时机

bean的创建过程中的初始化阶段的后置处理(postProcessAfterInitialization),在满足条件的情况下会对bean进行AOP增强。核心实现就是AbstractAutoProxyCreator的wrapIfNecessary方法。该方法主要逻辑实现就是,找到容器中能够应用到当前所创建的bean的切面,利用切面为bean创建代理对象。

AOP的一些概念

在真正介绍拦截器链之前,先理清一下一些我自己第一次看源码时比较模糊的概念。

切面:对主业务逻辑的一种增强。spring中的Advice和Advisor都是切面的一种实现,只不过Advisor相比Advice能够实现更复杂的逻辑。

织入:将切面应用到目标方法或类的过程。比如cglib的intercept方法,jdk代理的invoke方法,其完成的逻辑都可以叫做织入。

连接点:可以被切面织入的方法。

切点:具体被切面织入的方法。

连接点和切点什么区别呢?一个修饰词是“可以”,一个修饰词是“具体”。在我看来,切点规定了哪些方法将被切面增强,满足切点的限定条件都将得到增强。而连接点表示了满足切点扫描的方法的集合,这些方法可能有些满足切点的限定条件,有些不满足。 来看一下spring中对切点的定义是什么:

public interface Pointcut {
	/**
	 * Return the ClassFilter for this pointcut.
	 * @return the ClassFilter (never {@code null})
	 */
	ClassFilter getClassFilter();
	/**
	 * Return the MethodMatcher for this pointcut.
	 * @return the MethodMatcher (never {@code null})
	 */
	MethodMatcher getMethodMatcher();
	/**
	 * Canonical Pointcut instance that always matches.
	 */
	Pointcut TRUE = TruePointcut.INSTANCE;
}

其中的ClassFilter和MethodMatcher都具有matches方法,决定了哪些类或方法满足切点的限定条件。 把这些概念串起来,我总结就是:

spring的AOP就是把切面(Advice、Advisor)织入(Weaving)到满足切点(PointCut)限定条件的连接点(JoinPoint)的过程。

AOP拦截链

下面开始正菜了,在spring中,我们怎么用切面去增强一个类呢?是拦截!我们拦截方法的执行,去添加一些额外的逻辑。那如何进行拦截呢?当然是动态代理了。

在spring的整个生态中,强依赖动态代理,所以,最最基础的cglib代理和jdk代理是我们学习spring的必备基础。spring有多强依赖动态代理呢,对于cglib代理来说,spring直接copy了一份cglib代理的源码到自己的框架中。以至于你去搜MethodInterceptor会发现有两个相同的类,一个是在net.sf.cglib.proxy包下,一个是在org.springframework.cglib.proxy包下。

spring是如何对方法进行拦截呢?答案是通过MethodInterceptor。

public interface MethodInterceptor extends Interceptor {

	/**
	 * Implement this method to perform extra treatments before and
	 * after the invocation. Polite implementations would certainly
	 * like to invoke {@link Joinpoint#proceed()}.
	 * @param invocation the method invocation joinpoint
	 * @return the result of the call to {@link Joinpoint#proceed()};
	 * might be intercepted by the interceptor
	 * @throws Throwable if the interceptors or the target object
	 * throws an exception
	 */
	Object invoke(MethodInvocation invocation) throws Throwable;

}

也就是说,被增强类的方法执行时,实际是通过MethodInterceptor#invoke被调用的。

需要注意的是,用于AOP拦截的MethodInterceptor与cglib代理增强的MethodInterceptor虽然类名相同,但是却是完全不同的两个类。这里可以过度解读一下spring的命名(spring的命名是一种艺术)。cglib代理依靠MethodInterceptor#intercept方法实现,jdk代理依靠InvocationHandler#invoke方法实现。而spring AOP实现拦截是依靠MethodInterceptor#invoke方法。注意到没有,这是综合了cglib代理的类名和jdk代理的方法名。

现在我们新建两个拦截器,LogInterceptor和TimeInterceptor,分别用于记录日志和计时。 LogInterceptor

public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("开始执行");
        Object retVal = invocation.proceed();
        System.out.println("执行完毕");
        return retVal;
    }
}

TimeInterceptor

public class TimeInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("计时开始");
        Object retVal = invocation.proceed();
        System.out.println("计时结束,耗时:" + (System.currentTimeMillis() - start) / 1000);
        return retVal;
    }
}

同时我们再新建两个spring自己提供的切面Advice,分别是MethodBeforeAdvice和AfterReturningAdvice。 AOPAfterReturningAdvice

public class AOPAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("AfterReturning ....");
    }
}

AOPMethodBeforeAdvice

public class AOPMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("Before...");
    }
}

MethodBeforeAdvice和AfterReturningAdvice的AOP增强最终实现其实也是基于MethodInterceptor,分别对应MethodBeforeAdviceInterceptor和AfterReturningAdviceInterceptor。这个转换是通过AdvisorAdapter去实现的。转换逻辑为:

Advice和MethodInterceptor有什么区别呢? 首先从类继承关系入手 可见,MethodInterceptor是Advice的子接口。spring提供了一些统一的增强接口,如BeforeAdvice、AfterReturningAdvice等。就是我们常说的前置增强、后置增强。而环绕增强则是通过MethodInterceptor去实现。对于前置增强、后置增强,最终也是依靠环绕增强去实现。

MethodBeforeAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
        // 先执行MethodBeforeAdvice的before方法
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		// 后执行目标方法
		return mi.proceed();
	}
##################
AfterReturningAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
		// 先执行目标方法,得到返回值
		Object retVal = mi.proceed();
		// 再执行AfterReturningAdvice的afterReturning方法
		this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
		return retVal;
	}

也就是说,spring内置提供的一些Advice,仅仅是定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是执行之后执行等,比较简单。本质还是环绕增强,即通过将Advice转换成MethodInterceptor去实现的。

接下来我们新建一个接口Animal,以及需要被切面增强的类Dog。

public interface Animal {
    void bark();
}

public class Dog implements Animal {
    @Override
    public void bark() {
        System.out.println("wang wang wang...");
    }
}

将LogInterceptor、TimeInterceptor、AOPAfterReturningAdvice和AOPMethodBeforeAdvice应用到Dog。

public class Main01 {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();

        proxyFactory.addAdvice(new LogInterceptor());
        proxyFactory.addAdvice(new TimeInterceptor());
        proxyFactory.addAdvice(new AOPAfterReturningAdvice());
        proxyFactory.addAdvice(new AOPMethodBeforeAdvice());

        proxyFactory.setTarget(new Dog());
        proxyFactory.setInterfaces(Dog.class.getInterfaces());

        Object proxy = proxyFactory.getProxy();


        if (proxy instanceof Dog) {
            ((Dog) proxy).bark();
        }
    }
}

输出结果

开始执行
计时开始
Before...
wang wang wang...
AfterReturning ....
计时结束,耗时:0
执行完毕

可以看到,在Dog#bark方法执行前后,加入了我们日志和计时的增强、以及前置增强和后置增强。

spring AOP生成代理对象的原理其实就是基于上述。是不是很简单?但是spring之所以如此优秀,就是因为它有许多额外的逻辑和细节需要去打磨。本篇博客先挑其中两个点去分析。

1.代理增强什么时候采用cglib代理,什么时候采用jdk代理?

2.多个MethodInterceptor拦截器时,如何顺序执行?

什么时候采用cglib代理,什么时候采用jdk代理

proxyFactory.getProxy()为入口,一步一步去探究。

	public Object getProxy() {
		return createAopProxy().getProxy();
	}

	protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
		return getAopProxyFactory().createAopProxy(this);
	}

首先是通过createAopProxy()获取AopProxy,然后通过AopProxy的getProxy()方法获取代理后的对象。

AopProxy是用于已配置AOP代理的委托接口,允许创建实际的代理对象。有三个实现,JdkDynamicAopProxy、CglibAopProxy和ObjenesisCglibAopProxy。而ObjenesisCglibAopProxy是CglibAopProxy的子类。 决定AopProxy的具体实现逻辑在AopProxyFactory的createAopProxy方法中,AopProxyFactory只有一个实现类DefaultAopProxyFactory。

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (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)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

主要有以下判断逻辑

判断逻辑说明
config.isOptimize()是否开启优化
config.isProxyTargetClass()true为cglib代理、false为jdk代理
hasNoUserSuppliedProxyInterfaces(config)是否有除SpringProxy外的实现接口,SpringProxy是一个标记接口,所有被AOP代理的类都会实现此接口。spring中有许多标记接口
targetClass.isInterface()目标类是否为借口
Proxy.isProxyClass(targetClass)目标类是否已经被代理过

回到我们的main方法中,Dog实现了我们自己定义的接口Animal,未设置proxyTargetClass的值(默认为false)。因此将会采用jdk代理去实现。 进入JdkDynamicAopProxy的getProxy方法:

@Override
	public Object getProxy() {
		return getProxy(ClassUtils.getDefaultClassLoader());
	}

	@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);
	}

就是一个简单的创建JDK代理的逻辑,注意一点的是,创建代理类的逻辑Proxy.newProxyInstance(classLoader, proxiedInterfaces, this),最后一个参数是this。

回忆一下,创建JDK代理的最后一个参数是什么---InvocationHandler。可见,JdkDynamicAopProxy本身就是一个InvocationHandler,最终方法调用会回到JdkDynamicAopProxy的invoke方法。

多个MethodInterceptor拦截器时,如何执行?

当我们配置了多个拦截器时,这些拦截器是如何执行的呢?

基于上述,我们知道代理对象的方法调用最终会来到JdkDynamicAopProxy的invoke,那进到此方法看一下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 只截取了关键部分 

            // 获取到可应用于当前方法的拦截器链
            // 拦截器链可以先理解为MethodInterceptor的集合,该集合为List,是有序的,与添加的顺序保持一致
			// 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 fallback 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();
			}

	}

核心步骤只有两步:

1、获取拦截器链 跟我们之前的分析一样,这里获取到所有适配当前方法的MethodInterceptor。而所add进去的AOPAfterReturningAdvice和AOPMethodBeforeAdvice,被包装成了AfterReturningAdviceInterceptor和MethodBeforeAdviceInterceptor。

2、把拦截器链应用到方法调用 进入到invocation.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();
		}

		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;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
		    // 调用拦截器的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#invoke方法,当到达拦截器链末尾,则直接调用目标方法。 现在我们把之前定义的LogInterceptor再拿出来看一下:

public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("开始执行");
        Object retVal = invocation.proceed();
        System.out.println("执行完毕");
        return retVal;
    }
}

注意invocation.proceed()。在打印完"开始执行",我们继续执行了MethodInvocation#proceed,这样才能保证拦截器链中位于LogInterceptor之后的MethodInterceptor得到执行,假如我们去掉invocation.proceed()。不会有任何报错,但是拦截器链到LogInterceptor为止就断裂了。

整个逻辑执行图示:

总结

本文对AOP拦截器链实现所用到的MethodInterceptor、ProxyFactory进行了简要分析,并举例说明了拦截器链是如何一环扣一环执行。作为自己学习spring的记录。

后续将会继续分析:

1.引入增强DynamicIntroductionAdvice的原理

2.spring AOP生成代理的源码逻辑。