上篇文章,非常仔细的讲解到AOP的一些概念的理解,和Spring中AOP如何创建代理,并且从反编译字节码中推理出代理方法的MethodInterceptor方法拦截。所以此篇文章继续讲解方法拦截的详细步骤,并且也会对上篇文章的一些点做回顾。
案例代码:
@Component
public class Test {
public void test(){
System.out.println("这是test的内容");
}
}
@Aspect // 声明这是一个切面
@Component
public class Section { // 切面
@Before(value = ("execution(* com.liha.bean.Test.test(..))")) // 切点
public void testProxy(){
System.out.println("在之前"); // 通知
}
}
这样就可以生成代理类的字节码文件了,然后通过IDEA反编译。
源码讲解
所以看到0号方法拦截的具体逻辑。
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// 获取到target的类对象,也就是被代理类
target = targetSource.getTarget();
// 获取到被代理类的Class对象。
Class<?> targetClass = (target != null ? target.getClass() : null);
// 使用MethodMatcher的matches()方法对当前方法做切点的匹配,
// 因为当前代理类中方法可能存在需要被切面增强的,也存在没有被增强的
// 所以这里就通过ProxyFactory这个Advised的子类获取到所有的切点+通知,
// 上篇文章有介绍在代理之前把当前代理类中对应的所有切点+通知给放入到ProxyFactory中
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// 此时我们要明白,当前类已经是一个代理类了,所有的方法都会走方法拦截
// 所以这里的判断,意思就是判断当前方法有没有被切面做增强,也就是判断上面匹配出来的结果
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// 这里通过反射+Cglib的FastClass执行没被代理的方法
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
// 创建一个CglibMethodInvocation对象,将当前的类信息和切点信息给传入,后面会介绍这个类的意义
else {
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
大致流程如下(详细的流程看代码块中的注释):
- 获取到被代理类的对象和Class对象
- 根据原有方法和Class对象,然后遍历当前代理工厂中的所有切点,然后通过getInterceptorsAndDynamicInterceptionAdvice()方法,方法中使用
MethodMatcher的matches()方法来做匹对,最后获取到当前方法的所有切点+通知。 - 因为当前代理类可能存在被切面做增强的方法,还有存在普通方法,所以这里通过当前方法和MethodMatcher的matches()方法做匹对得出所有切点+通知。如果为空就代表当前方法不需要被切面增强,也就是此方法是普通方法,就直接反射+使用Cglib的FastClass执行普通方法。
- 如果当前方法是被切面做增强了,也就是切点+通知有1个或者多个。此时就创建CglibMethodInvocation对象,并且把所有的类信息和代理类信息给传入。
CglibMethodInvocation对象好比就是Aop中真正执行切面增强逻辑+原本方法逻辑的调度者。内部使用递归的方式,把我们的切点+通知包装成一个链来递归处理。因为在Aop中通知,分为前后通知、环绕通知、异常通知、返回值后通知。所以这里就是把他们串成一个链来处理。并且这些通知也在前面的操作以及解析完毕,目前就只剩执行了。所以我们看到proceed()方法。
@Override
@Nullable
public Object proceed() throws Throwable {
// currentInterceptorIndex的初始值为-1,可以理解为索引下标
// this.interceptorsAndDynamicMethodMatchers就是我们的切点+通知,前面解析成一个List集合
// 在我的案例里是存在一个Before的通知,但是在Aop中会自带一个切点+通知,所以我的案例是存在2个
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 能进到这个if中,就代表要执行目标方法,也就是被增强的方法逻辑
return invokeJoinpoint();
}
// 这里取List集合中的值,也就是取切点+通知
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 这边逻辑,笔者不是特别清楚,但是我有追踪到创建处,
// 也就是通过方法来匹对切点的时候会对其做判断,但是永远返回的为false,所以这里是不存在的,所以直接看到else代码块中
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
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代码块中
else {
// 执行通知的逻辑了。 我项目中是before,但是笔者会把其他的通知也展示出来,并且说明区别
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
这里也就是通过索引来控制,通知的逻辑和目标方法的逻辑前后执行。然后每次递归就会取到切点链中的数据,除非第一个if已经通过了,也就是执行了目标方法了。就代表切点链已经遍历完毕了(但是并没有执行完所有的切点链(因为是递归的),因为还有后置,环绕之类的通知)。
所以我们直接看到不同的通知的invoke执行的逻辑区别。
前置通知:
后置通知:
返回值后通知
其他的就不一一介绍了,大家可以看invoke方法的各种子类的实现。
大家一定要思考是递归的场景,并且可以debug来走N遍流程理解,并且可以根据IDEA的debug的工具带有栈帧功能快速定位。
总结
没啥总结,这里稍微复杂就是递归这块,希望大家使用debug好好理解~!
最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!