搞懂AOP之一,拦截器链
写在前面
本篇不准备对着源码一步一步讲解这一过程,而是准备先剖析一下这一过程中所用到的一些类和方法的功能,当这些“螺丝”都熟悉之后,再一步一步看源码的时候,便会畅通无阻。这也是我在阅读源码过程中的感觉,因为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生成代理的源码逻辑。
转载自:https://juejin.cn/post/6877137943149051911