方法扫描,还原Spring AOP的本源面貌
2024-02-14 03:33:34
在上一篇文章《Spring 源码阅读 61:基于 JDK 的 AOP 代理在代理工厂中的注册》中,我们介绍了 JdkDynamicAopProxy 的基本原理,以及它如何在代理工厂中注册。JdkDynamicAopProxy 是 Spring AOP 中基于 JDK 动态代理机制实现的 AOP 代理。其中 invoke
方法是 JdkDynamicAopProxy 的核心,它负责处理代理对象的所有方法调用,并根据配置决定是否应用增强逻辑。本文将深入探讨 invoke
方法的第一部分逻辑:如何处理那些不需要被增强,需要直接执行目标方法的特殊方法调用 。
在 Java 中,方法调用可以分为两种情况:调用同一个类中的方法和调用不同类中的方法。
- 调用同一个类中的方法,例如
invoke
方法调用invokeJoinpoint()
方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return invokeJoinpoint(proxy, method, args);
}
- 调用不同类中的方法,例如
invoke
方法调用AdvisedSupport.getTargetClass()
方法:
Class<?> targetClass = this.advised.getTargetClass();
在运行时,可以通过 method.getDeclaringClass()
方法判断方法的调用类型。该方法返回包含该方法的类。
JdkDynamicAopProxy 中对特殊方法的处理逻辑主要体现在以下几个方面:
invokeJoinpoint()
方法的作用
invokeJoinpoint()
方法是 AOP 增强逻辑的入口。在这个方法中,会判断当前方法是否需要被增强。例如,我们之前提到的 toString()
、hashCode()
等方法就不需要增强,这个判断逻辑就在 invokeJoinpoint()
方法中实现。具体来说,它会调用 interceptBefore
方法,该方法最终会调用 invokeAdviceMethod
方法来执行增强逻辑。
需要注意的是,方法扫描是基于目标类中的方法进行的。
AdvisedSupport.getTargetClass()
方法的作用
AOP 的核心是代理目标对象。代理对象的所有方法调用最终都会被转发到目标对象上。在方法扫描过程中,如果需要判断目标对象的类型,就会调用 getTargetClass()
方法,该方法返回目标对象的类型。在 JdkDynamicAopProxy 中,目标对象的类型与代理类的类型相同。因此,方法扫描所依赖的目标对象类型实际上就是代理类的类型。
- 方法扫描的限制
JdkDynamicAopProxy 中的方法扫描只针对 toString()
、equals()
、hashCode()
和 finalize()
这几个方法。这是因为这些方法的调用通常与业务逻辑无关,不需要进行 AOP 增强。
if (!(method.getName().equals("equals") ||
method.getName().equals("hashCode") ||
method.getName().equals("toString") ||
method.getName().equals("finalize"))) {
try {
if (interceptBefore(method, args)) {
return invokeAdviceMethod(proxy, method, args, null);
}
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not even record invoker invocation on an exception: " + ex);
}
}
}
对于其他方法,JdkDynamicAopProxy 认为它们都与业务逻辑相关,需要进行扫描并应用增强逻辑。
- 方法扫描过程示例
为了更清楚地理解方法扫描过程,我们来看一个例子。
假设我们有一个目标类 Foo
,其中包含一个 toString()
方法:
// 代码清单 1
public class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Foo{" +
"name='" + name + '\'' +
'}';
}
}
我们希望对 Foo
类的 toString()
方法应用增强逻辑,例如打印方法执行前后的日志。我们可以定义一个增强类 FooAdvice
:
// 代码清单 2
public class FooAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before " + invocation.getMethod().getName());
Object proceed = invocation.proceed();
System.out.println("after " + invocation.getMethod().getName());
return proceed;
}
}
FooAdvice
类实现了 MethodInterceptor
接口,它的 invoke()
方法会在目标方法执行前后打印日志。
当 JdkDynamicAopProxy 扫描目标类 Foo
时,它会发现 toString()
方法是一个特殊方法,不需要应用增强逻辑。
然后,JdkDynamicAopProxy 会基于 JDK 动态代理机制为 Foo
类生成一个代理类,例如 Foo$$EnhancerBySpringCGLIB$$3f206030
。这个代理类会覆盖 toString()
方法,直接调用目标类的 toString()
方法,而不会应用增强逻辑。
// 代码清单 3
@Override
public String toString() {
MethodProxyCGLIB methodProxy = getMethodProxy(Foo.class.getMethod("toString", null));
return (String) methodProxy.invokeSuper(this, null);
}
通过这种方式,JdkDynamicAopProxy 实现了对特殊方法的处理,确保它们不会被 AOP 增强逻辑干扰。
常见问题及解答
- 为什么需要特殊处理
toString()
、equals()
、hashCode()
等方法?
答:因为这些方法通常与业务逻辑无关,对它们应用 AOP 增强逻辑可能会导致 unexpected behavior。例如,如果对 equals()
方法应用增强逻辑,可能会导致两个对象无法正确地比较。
- 除了
toString()
、equals()
、hashCode()
和finalize()
,还有哪些方法需要特殊处理?
答:这取决于具体的应用场景。一般来说,如果一个方法的调用与业务逻辑无关,就可以考虑特殊处理它。
- 如何判断一个方法是否需要特殊处理?
答:需要根据具体的应用场景和需求来判断。可以参考 Spring AOP 的官方文档,或者查阅相关资料。
- 如果我想对
toString()
方法应用增强逻辑,应该怎么做?
答:可以通过自定义 Pointcut
来实现。Pointcut
可以用来定义哪些方法需要被增强,哪些方法不需要被增强。
- JdkDynamicAopProxy 中的方法扫描过程是如何实现的?
答:JdkDynamicAopProxy 使用反射机制来扫描目标类中的方法,并根据配置决定是否应用增强逻辑。
希望本文能够帮助你更好地理解 JdkDynamicAopProxy 中对特殊方法的处理逻辑。