Spring AOP 机制:JDK Dynamic Proxy vs CGLIB

简而言之,Spring AOP 是基于代理(Proxy-based)的。也就是说,AOP 是通过代理实现的。Spring 使用以下两种方式之一为给定的目标 Bean 创建代理:

  1. JDK 动态代理(JDK Dynamic Proxies):当被代理的目标实现了至少一个接口时,首选此方式。
  2. CGLIB:如果目标对象没有实现任何接口,可以使用 CGLIB。也可以通过以下方式强制使用:

    • <aop:config> 元素的 proxy-target-class 属性设置为 true
    • 在使用 @AspectJ 自动代理支持时,将 proxy-target-class 属性设置为 true

注意事项与局限性

  1. Spring AOP 与 AspectJ 的关系:Spring AOP 其实只是兼容了 AspectJ 的注解,但是底层跟 AspectJ 一点关系都没有。
  2. Spring AOP 的局限性:因为 Spring AOP 是基于代理(proxy-based)和方法(method-based)的,所以它有如下局限性:

使用 AspectJ 可以无限制地使用 AOP,但是使用起来相对复杂很多,需要仔细权衡:

  1. 10.4 Choosing which AOP declaration style to use
  2. 10.8 Using AspectJ with Spring applications

Spring AOP vs AspectJ AOP

  • Spring AOP:运行时织入(Runtime weaving)。如果存在接口则使用动态代理概念,如果提供了直接实现则使用 CGLIB 库。
  • AspectJ:编译时织入(Compile time weaving)。如果源码可用,通过 AspectJ Java 工具(ajc 编译器)进行;或者进行后编译织入(post compilation weaving,使用编译后的文件)。此外,还可以启用与 Spring 结合的加载时织入(load time weaving)——这需要 aspectj 定义文件并提供灵活性。编译时织入在某些情况下可以提供性能优势,而且 Spring AOP 中的连接点(joinpoint)定义仅限于方法定义,而 AspectJ 则不受此限制。

织入时机(Weaving Process Time)

The AspectJ weaver takes class files as input and produces class files as output. The weaving process itself can take place at one of three different times: compile-time, post-compile time, and load-time. The class files produced by the weaving process (and hence the run-time behaviour of an application) are the same regardless of the approach chosen.

Compile-time weaving is the simplest approach. When you have the source code for an application, ajc will compile from source and produce woven class files as output. The invocation of the weaver is integral to the ajc compilation process. The aspects themselves may be in source or binary form. If the aspects are required for the affected classes to compile, then you must weave at compile-time. Aspects are required, e.g., when they add members to a class and other classes being compiled reference the added members.

Post-compile weaving (also sometimes called binary weaving) is used to weave existing class files and JAR files. As with compile-time weaving, the aspects used for weaving may be in source or binary form, and may themselves be woven by aspects.

Load-time weaving (LTW) is simply binary weaving defered until the point that a class loader loads a class file and defines the class to the JVM. To support this, one or more "weaving class loaders", either provided explicitly by the run-time environment or enabled through a "weaving agent" are required.

You may also hear the term "run-time weaving". We define this as the weaving of classes that have already been defined to the JVM (without reloading those classes). AspectJ 5 does not provide explicit support for run-time weaving although simple coding patterns can support dynamically enabling and disabling advice in aspects.

Spring AOP 底层实现

定义了一个 AopProxy 接口,参见 AopProxy

package org.springframework.aop.framework;

/**
 * Delegate interface for a configured AOP proxy, allowing for the creation
 * of actual proxy objects.
 *
 * <p>Out-of-the-box implementations are available for JDK dynamic proxies
 * and for CGLIB proxies, as applied by {@link DefaultAopProxyFactory}.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see DefaultAopProxyFactory
 */
public interface AopProxy {
    Object getProxy();

    Object getProxy(ClassLoader classLoader);
}

AopProxy 主要有两个实现类:

  1. JdkDynamicAopProxy
  2. CglibAopProxy

JdkDynamicAopProxy

JdkDynamicAopProxy 基于 JDK 的 java.lang.reflect.Proxy 实现。基于目标对象的接口创建代理非常简单:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
        // Determine the complete set of interfaces to proxy for the given AOP configuration. 
        Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        // Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler. 
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

}

使用 Proxy 类的 newProxyInstance 方法:

public static Object newProxyInstance(ClassLoader loader,
              Class<?>[] interfaces,
              InvocationHandler h)

注意上面的 InvocationHandler 参数本身就是 JdkDynamicAopProxy 实例。这是真的,因为 JdkDynamicAopProxy 也实现了 InvocationHandler 接口:

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
}

invoke 方法的实现代码太长,此处不再粘贴。如果你感兴趣可以直接查看源码。只需记住它不过是代理模式(Proxy Pattern)。代理实现了目标对象的接口,并持有目标的引用。

proxypattern.png

CglibAopProxy

CglibAopProxy 使用 CGLIB 库来生成代理对象。

final class CglibAopProxy implements AopProxy, Serializable {

    public Object getProxy() {
        return getProxy(null);
    }

    public Object getProxy(ClassLoader classLoader) {
        try {
            Class<?> rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

            Class<?> proxySuperClass = rootClass;
            if (ClassUtils.isCglibProxyClass(rootClass)) {
                proxySuperClass = rootClass.getSuperclass();
                Class<?>[] additionalInterfaces = rootClass.getInterfaces();
                for (Class<?> additionalInterface : additionalInterfaces) {
                    this.advised.addInterface(additionalInterface);
                }
            }

            // Validate the class, writing log messages as necessary.
            validateClassIfNecessary(proxySuperClass);

            // Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class));
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setInterceptDuringConstruction(false);

            Callback[] callbacks = getCallbacks(rootClass);
            enhancer.setCallbacks(callbacks);
            enhancer.setCallbackFilter(new ProxyCallbackFilter(
                    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));

            Class<?>[] types = new Class[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            enhancer.setCallbackTypes(types);

            // Generate the proxy class and create a proxy instance.
            Object proxy;
            if (this.constructorArgs != null) {
                proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
            }
            else {
                proxy = enhancer.create();
            }

            return proxy;
        }
        catch (Throwable ex) {
            // ..Exception handle..
            throw new AopConfigException("Unexpected AOP exception", ex);
        }
    }
}    

注意 CglibAopProxyJdkDynamicAopProxy 的不同之处。CglibAopProxy 将构造一个继承目标类的类(Enhancer),而不是实现目标类的接口。

enhancer.setSuperclass(proxySuperClass);

这就是为什么它可以代理没有接口的目标对象。

另请注意,JdkDynamicAopProxyCglibAopProxy 都是在运行时(on the fly)构造代理对象,而不是编译时。因为它在实例化代理对象之前必须先构造代理类,这可能是一个性能瓶颈。但在 Spring 中,这通常只在启动 Spring 应用上下文时发生一次。

DefaultAopProxyFactory

Spring 使用 DefaultAopProxyFactory 来创建 AOP 代理对象。如果对于给定的 AdvisedSupport 实例,以下任一条件为真,DefaultAopProxyFactory 将自动创建基于 CGLIB 的代理:

  • 设置了 optimize 标志
  • 设置了 proxyTargetClass 标志
  • 未指定代理接口
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    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()) {
                return new JdkDynamicAopProxy(config);
            }
            return CglibProxyFactory.createCglibProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

    /**
     * Inner factory class used to just introduce a CGLIB dependency
     * when actually creating a CGLIB proxy.
     */
    private static class CglibProxyFactory {

        public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
            return new CglibAopProxy(advisedSupport);
        }
    }
}

参考资料

  1. 9. Aspect Oriented Programming with Spring
  2. 8.6 Proxying mechanisms
  3. Java Reflection: Dynamic Proxies
  4. Weaving with AspectJ AspectJ 非常好的介绍文章
  5. Spring AOP APIs Spring AOP API 介绍。介绍了 Spring AOP 底层使用到的核心类和接口。比如 ProxyFactoryBean。强烈推荐阅读。
  6. Spring Auto proxy creator example 介绍怎样使用 ProxyFactoryBean 创建 AOP 代理。

说明:文中部分链接指向 Spring 3.x 版本文档,核心原理在后续版本中依然适用,但具体类实现细节可能随版本演进有所调整。