返回

方法扫描,还原Spring AOP的本源面貌

后端

在上一篇文章《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 中对特殊方法的处理逻辑主要体现在以下几个方面:

  1. invokeJoinpoint() 方法的作用

invokeJoinpoint() 方法是 AOP 增强逻辑的入口。在这个方法中,会判断当前方法是否需要被增强。例如,我们之前提到的 toString()hashCode() 等方法就不需要增强,这个判断逻辑就在 invokeJoinpoint() 方法中实现。具体来说,它会调用 interceptBefore 方法,该方法最终会调用 invokeAdviceMethod 方法来执行增强逻辑。

需要注意的是,方法扫描是基于目标类中的方法进行的。

  1. AdvisedSupport.getTargetClass() 方法的作用

AOP 的核心是代理目标对象。代理对象的所有方法调用最终都会被转发到目标对象上。在方法扫描过程中,如果需要判断目标对象的类型,就会调用 getTargetClass() 方法,该方法返回目标对象的类型。在 JdkDynamicAopProxy 中,目标对象的类型与代理类的类型相同。因此,方法扫描所依赖的目标对象类型实际上就是代理类的类型。

  1. 方法扫描的限制

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 认为它们都与业务逻辑相关,需要进行扫描并应用增强逻辑。

  1. 方法扫描过程示例

为了更清楚地理解方法扫描过程,我们来看一个例子。

假设我们有一个目标类 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 增强逻辑干扰。

常见问题及解答

  1. 为什么需要特殊处理 toString()equals()hashCode() 等方法?

答:因为这些方法通常与业务逻辑无关,对它们应用 AOP 增强逻辑可能会导致 unexpected behavior。例如,如果对 equals() 方法应用增强逻辑,可能会导致两个对象无法正确地比较。

  1. 除了 toString()equals()hashCode()finalize(),还有哪些方法需要特殊处理?

答:这取决于具体的应用场景。一般来说,如果一个方法的调用与业务逻辑无关,就可以考虑特殊处理它。

  1. 如何判断一个方法是否需要特殊处理?

答:需要根据具体的应用场景和需求来判断。可以参考 Spring AOP 的官方文档,或者查阅相关资料。

  1. 如果我想对 toString() 方法应用增强逻辑,应该怎么做?

答:可以通过自定义 Pointcut 来实现。Pointcut 可以用来定义哪些方法需要被增强,哪些方法不需要被增强。

  1. JdkDynamicAopProxy 中的方法扫描过程是如何实现的?

答:JdkDynamicAopProxy 使用反射机制来扫描目标类中的方法,并根据配置决定是否应用增强逻辑。

希望本文能够帮助你更好地理解 JdkDynamicAopProxy 中对特殊方法的处理逻辑。