掘金 后端 ( ) • 2024-05-09 09:52

一、概述

ReflectiveMethodInvocation(ProxyMethodInvocation)+ MethodInterceptor实现了具体切面逻辑,接下来看看AOP的最基本原理——动态代理,更具体的说,是JDK/CGLIB这种动态代理的技术。

思考一个经典的问题,看下面这个类(接口代码省略):

public class HelloGreet implements Greet {

    @Override
    public String greet(@String name) {
        another(name);
        return "hello " + name + " !";
    }

    @Override
    public void another(String arg) {
        System.out.println(arg);
    }
}

在greet方法中,调用了自己的another方法,如果我们分别使用JDK和CGLIB对这个类的这两个方法进行代理,问题来了:

调用代理对象的greet方法,greet又调用了another方法,那么another方法会被代理么?

JDK vs CGLIB原理的区别,相信你已经耳熟能详了,就是代理原理不同

  • JDK - 基于接口,代理接口方法
  • CGLIB - 基于继承,只能代理非final方法

那么,答案就比较明显了

  • JDK不会,JDK基于接口实现,greet调用another就是内部调用,another是不会被代理的;
  • CGLIB可以,CGLIB基于继承,greet调用another,相当于多态,自然会调用到子类的another方法;

那么,如果是使用Spring提供的代理功能呢?

比较反直觉的是,即使是设置了proxy-target-class=true,强制使用CGLIB代理,内部调用时,也不会被代理。

其实原因之前的文章已经揭晓了,代理执行时,调用的ReflectiveMethodInvocation的proceed的方法,在执行完拦截器方法后,执行被代理对象的方法时,使用的是真正的被代理对象调用的,也就无法使用多态的特性。拦截器链都执行完成后,执行的invokeJoinpoint方法:

protected Object invokeJoinpoint() throws Throwable {
   return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

那该如何让Spring AOP的代理对象,内部调用被代理到呢?别着急,后面会有回答。

二、源码解析

回过头来,我们来看下spring是如何利用JDK和CGLIB实现动态代理的。不出意外,这两种模式生成的代理对象,抽象成了一个接口AopProxy。两种实现方式,分别是:

  • JdkDynamicAopProxy - JDK方式代理实现
  • CglibAopProxy - CGLIB方式实现
    • ObjenesisCglibAopProxy - CglibAopProxy子类,复写了创建代理的方法,spring4之后的默认使用这个来创建代理

2.1 简单工厂

不出意外,使用哪种方式创建对象,使用了简单工厂模式,接口是AopProxyFactory,默认实现是DefaultAopProxyFactory,从他的createAopProxy方法可以得到Spring的默认规则,如果被代理对象实现了接口,使用JDK代理,否则使用CGLIB,如果开启了optimize或者设置proxyTargetClass=true,则强制使用CGLIB。当然还有其他细节,比如被代理的类仅仅是个接口,或者本身就是一个JDK代理生成的类,则只能使用JDK的方式代理。

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 开启optimize || proxyTarget属性为true || 没有用户实现除SpringProxy以外的接口
    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.");
        }
        // 目标就是一个接口,当然就只能使用JDK了
        // 或者被代理的calss是一个由JDK代理生成的类
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}
/**
* Determine whether the supplied {@link AdvisedSupport} has only the
* {@link org.springframework.aop.SpringProxy} interface specified
* (or no proxy interfaces specified at all).
*/
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
    Class<?>[] ifcs = config.getProxiedInterfaces();
    return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}

源码出自:DefaultAopProxyFactory

2.2 JdkDynamicAopProxy - JDK动态代理

接下来,看下JDK方式的动态代理的实现。翻开JdkDynamicAopProxy的是实现,使用JDK代理,自然实现了InvocationHandler方法,直接看下对应的invoke方法。其中最核心的代码,是构造了ReflectiveMethodInvocation,并调用他的proceed方法,也就是上一章的内容。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;
    try {
        // 处理equals/hashCode
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        // 如果是DecoratingProxy,返回最终的targetClass,也不知道这是哪种场景需要的
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            // There is only getDecoratedClass() declared -> dispatch to proxy config.
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        // 如果被代理对象是Advised的实现类,不代理,Advised是代理配置的持有类,忽略这样的对象
        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }
        Object retVal;
        // 如果exposeProxy为true,则设置被代理对象到ThreadLocal中
        //这是因为一个被代理的对象的a方法直接调用b方法的话,b是无法被代理的,把代理对象暴露出来,a方法可以这样调用b方法
        //((XXXService)AopContext.currentProxy()).b();
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // 尽量晚的获取target对象,减少持有时间,防止这时一个“池”中的对象
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // 获取拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        // 没有拦截器,则直接调用
        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 {
            // 否则创建ReflectiveMethodInvocation,调用proceed方法
            MethodInvocation invocation =
                    new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            retVal = invocation.proceed();
        }
        // 如果返回的this,则处理成代理对象,方便链式调用
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
                returnType != Object.class && returnType.isInstance(proxy) &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        // 归还target
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        // 处理多层代理的场景
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}
源码出自:JdkDynamicAopProxy

其中有几个点,需要详细介绍一下。

2.2.1 expose-proxy

aop有个expose-proxy属性配置,用于暴露代理对象,解决之前提到的被代理类内部调用的问题。expose-proxy=true,spring会在代理方法被调用的时候,暴露当前代理对象到AopContext的ThreadLocal中,这样,如果同一个类中的方法内部调用,可以从AopContext中获取代理对象,比如我们内部调用another方法,可以这样: ((XXXService)AopContext.currentProxy()).another();

所以,在调用ReflectiveMethodInvocation.proceed之前,如果exposeProxy=true,则设置当前代理对象到ThreadLocal中:

if (this.advised.exposeProxy) {
    // Make invocation available if necessary.
    oldProxy = AopContext.setCurrentProxy(proxy);
    setProxyContext = true;
}

执行完成之后,在finally块中,设置成原来的proxy,因为可能有多个代理对象之间在调用,需要还原回去。

if (setProxyContext) {
    // Restore old proxy.
    AopContext.setCurrentProxy(oldProxy);
}

2.2.2 返回值

Spring还考虑到了返回值是this的场景,通常我们为了链式调用,比如StringBuilder的append方法,我们可以sb.append("hello").append("world"),这样不停的append下去,就是因为append返回了this,这种情况下,spring会把this,替换成代理对象,以让代理方法能够在后续的链式调用中执行。

2.2.3 拦截器链的获取

所有的拦截器,其实在构造JdkDynamicAopProxy的时候,就已经存在了,就在this.advised中,也就是在AdvisedSupport中已经存在了。而getInterceptorsAndDynamicInterceptionAdvice方法只是过滤了一下,并对结果加了缓存。为什么要过滤呢,因为构造JdkDynamicAopProxy时的拦截器链,是针对整个被代理的对象的,而不是某个方法,到具体方法执行的时候,还要再次过滤一下,只保留针对该方法的拦截器。其实也就是根据注解中定义的Pointcut去过滤。

2.2.4 TargetSource

JdkDynamicAopProxy并没有直接持有目标(被代理)对象,而是持有的TargetSource对象(在AdvisedSupport类中)。我们看下TargetSource:

public interface TargetSource extends TargetClassAware {
   Class<?> getTargetClass();
   boolean isStatic();
   Object getTarget() throws Exception;
   void releaseTarget(Object target) throws Exception;
}

为什么不直接持有target对象呢,这主要是可以代理一个“动态”对象,比如说,这个对象是一个“池化对象”,比如数据库的连接池中的连接(这也是为什么要尽量短的持有对象,且最后要归还的原因),也可以是一个具有代码热部署能力的对象。通过这个TargetSource接口的抽象,在执行的时,才通过getTarget方法获取对象,执行完成后,在finally中归还对象,就可以实现代理“动态”对象的能力。

参考: Spring Aop之Target Source详解

2.3 ObjenesisCglibAopProxy & CglibAopProxy - CGLIB代理

2.3.1 ObjenesisCglibAopProxy

上文动态代理创建中可以看到,CGLIB代理,使用的是ObjenesisCglibAopProxy,它是CglibAopProxy的子类,只是复写了createProxyClassAndInstance方法

@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
   Class<?> proxyClass = enhancer.createClass();
   Object proxyInstance = null;

   // 尝试使用objenesis方式创建
   if (objenesis.isWorthTrying()) {
      try {
         proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
      }
      catch (Throwable ex) {
         logger.debug("Unable to instantiate proxy using Objenesis, " +
               "falling back to regular proxy construction", ex);
      }
   }

   if (proxyInstance == null) {
      //出现异常,采用普通方式创建,省略代码。。。
   }

   ((Factory) proxyInstance).setCallbacks(callbacks);
   return proxyInstance;
}

那么objenesis究竟是个啥呢,简单来说,就是一个用于创建对象的类库,可以绕过构造方法来创建对象。猜测Spring是基于性能考虑,使用objenesis方式,创建CGLIB增加后的类的对象。关于objenesis更多细节可以参考 Java编程之创建对象——Objenesis简单使用_objenesis cglib创建对象-CSDN博客

2.3.2 CglibAopProxy

其实GCLIB代理的大部分逻辑,还是靠CglibAopProxy来实现的。CGLIB,主要要看他设置的callback,也就是MethodInterceptor的实现。 CglibAopProxy设置了好多callback,其中就有类似equals/hashCode的callback,不过最重要的,还是负责实现aop的DynamicAdvisedInterceptor,它也实现了诸如处理expose-proxy的功能,处理了返回值,最重要的逻辑,还是创建了CglibMethodInvocation,然后调用了它的proceed方法。

CglibMethodInvocation又是个什么呢,他是ReflectiveMethodInvocation的子类。proceed方法,还是调用的父类的方法,只是复写了invokeJoinpoint方法,稍微优化了一下。

CglibAopProxy的具体实现细节就不看了,和JDK都是类似的。