Spring源码探究:深入理解Spring AOP的设计与实现

写在前面:本文面向的读者为熟悉Java面向对象程序开发、有过Spring开源框架使用经验的群体。

AOP通常被称为面向切面编程,主要的作用是可以将那些分散在业务系统中相同的代码抽取出来放到一个地方进行管理。这么做的好处是减少了重复代码的编写,并且软件的可维护性也强。为什么叫做面向切面编程呢?举个例子:假如我们的代码中,有许多以update开头的函数的执行都需要管理员权限。如果不使用AOP,那么我们在每个以update开头的函数中都要进行权限验证,这样导致了大量重复代码的产生。与此同时,万一某天需求有变,不再限制只有管理员才能执行这些函数,那么我们又要将原来代码中和这个部分相关的代码逐行移除,十分的麻烦。引入了AOP之后,这项工作就变得简单了:我们可以将权限验证的代码放在某个地方,然后通过某些特定的配置实现在执行系统中以update开头的函数之前,先执行权限验证的代码。如此,万一需求变了,我们也只要改一个地方的代码。那一个个以update开头的函数就是切点,横向地来看,可以把它们抽象成一个切面,所以AOP被称为面向切面编程。

AOP比较常见的应用场景:日志记录、性能统计、安全认证、事务处理、异常处理等。我们将这些代码从业务逻辑代码中分离出来,通过对这些行为的分离,我们把它们独立到非指导业务逻辑的代码中,进而改变这些代码的时候不会影响到我们的业务逻辑代码。并且业务逻辑代码也感知不到它们的存在,因为业务逻辑代码“被代理了”。

Spring AOP中代理机制的实现主要接触了JDK动态代理以及CGLIB动态代理,如果有对这两种动态代理机制不熟悉的同学,可以参考我之前写过的博客:
深入理解JDK动态代理机制
深入理解CGLIB动态代理机制

下面再回顾一下Spring AOP中几个基本的概念:

  • 连接点:目标被增强的函数;
  • Advice通知:定义在连接点做什么,为切面增强提供织入接口,有BeforeAdvice、AfterAdvice、ThrowsAdvice等;
  • Pointcut切点:Pointcut(切点)决定Advice通知应该作用于哪个连接点,也就是说通过Pointcut来定义需要增强的方法的集合,这些集合可以按一定的规则来选取,如可以由特定的正则表达式或根据方法签名进行匹配;
  • Advisor通知器:将目标方法的切面增强设计(Advice)和关注点的设计(Pointcut)结合起来。通过Advisor,可以定义该使用哪个通知并在哪个关注点使用它。
1、Advice

Advice是AOP中的一个基本接口,BeforeAdvice、AfterAdvice、ThrowsAdvice等都继承了它。

图1.1 Advice继承关系图

以前置通知(BeforeAdvice)为例,我们来看看它的类层次关系

图1.2 BeforeAdvice的类层次关系

在BeforeAdvice的继承关系中,定义类为待增强的目标方法设置的前置增强接口MethodBeforeAdvice,使用这个前置接口需要实现一个回调函数before,作为回调函数,before方法的实现在Advice中被配置到目标方法后,会在调用目标方法时被回调。

图1.3 MethodBeforeAdvice以及回调函数before

回调函数before的调用参数有:Method 对象,这个参数是目标方法的反射对象;Object [ ] 对象数组,这个对象数组包含目标方法的输入参数。

同样的,在AfterAdvice继承体系下的AfterReturningAdvice中也有相似的回调函数

图1.4 AfterReturningAdvice及其回调函数afterReturn.png
2、Pointcut切点

从Pointcut的基本接口定义中可以看到,需要返回一个MethodMatcher。对于Point的匹配判断功能,具体是由这个MethodMatcher来完成的,也就是说,由这个MethodMatcher来判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的Advice通知。


图2.1 Pointcut的基本接口定义

而在MethodMatcher接口中,有一个matcher方法,这个方法在匹配连接点的过程中起着至关重要的作用。

在Pointcut的类继承体系中,MethodMatcher对象是可以配置成JdkRegexpMethodPointcut以及NameMatchMethodPointcut来完成方法的匹配判断的。在JdkRegexpMethodPointcut中,我们可以看到一个matches方法,这个matches方法是MethodMatcher定义的接口方法。在JdkRegexpMethodPointcut实现中,这个matches方法就是使用正则表达式来对方法名进行匹配判断的。

图2.2 JdkRegexpMethodPointcut中的matches函数

NameRegexpMethodPointcut中,给出了matches方法的另一个实现--根据方法的全限定名称进行匹配

图2.3 NameRegexpMethodPointcut中matches函数的实现

从图2.4和图2.5中我们可以看到,是在JdkDynamicAopProxy的invoke方法中出发了对matches方法的调用。这个invoke方法就是Proxy对象进行代理回调的入口方法。


图2.4 NameMatchMethodPointcut中matches方法的调用关系链
图2.5 JdkRegexpMethodPointcut中matches方法的调用关系链
3. Advisor通知器

在Spring AOP中,我们以一个Advisor(DefaultPointcutAdvisor)的实现为例来了解Advisor的工作原理。

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

    private Pointcut pointcut = Pointcut.TRUE;


    /**
     * Create an empty DefaultPointcutAdvisor.
     * <p>Advice must be set before use using setter methods.
     * Pointcut will normally be set also, but defaults to {@code Pointcut.TRUE}.
     */
    public DefaultPointcutAdvisor() {
    }

    /**
     * Create a DefaultPointcutAdvisor that matches all methods.
     * <p>{@code Pointcut.TRUE} will be used as Pointcut.
     * @param advice the Advice to use
     */
    public DefaultPointcutAdvisor(Advice advice) {
        this(Pointcut.TRUE, advice);
    }

    /**
     * Create a DefaultPointcutAdvisor, specifying Pointcut and Advice.
     * @param pointcut the Pointcut targeting the Advice
     * @param advice the Advice to run when Pointcut matches
     */
    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
        this.pointcut = pointcut;
        setAdvice(advice);
    }


    /**
     * Specify the pointcut targeting the advice.
     * <p>Default is {@code Pointcut.TRUE}.
     * @see #setAdvice
     */
    public void setPointcut(Pointcut pointcut) {
        this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
    }

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }


    @Override
    public String toString() {
        return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
    }

}

DefaultPointcutAdvisor中,有两个属性,分别为Advice和Pointcut。通过这两个属性,我们可以分别配置Advice和Pointcut。在DefaultPointcutAdvisor中,pointcut默认被设置为Pointcut.True,这在Pointcut接口中被定义为

Pointcut TRUE = TruePointcut.INSTANCE;

TruePointcut.INSTANCE是一个饿汉式的单例:

图2.6 TruePointcut的实现

在TruePointcut的methodMatcher实现中,使用TrueMethodMatcher作为方法匹配器。这个匹配器对任何的方法匹配都要求返回true的结果,也就是说对任何方法名的匹配要求它都会返回匹配成功的结果。类似地,TrueMethodMatcher的实现也是一个单例模式。

图2.7 TrueMethodMatcher的实现
4. Spring AOP的设计分析

我们前面提到过,在使用Spring AOP的过程中,我们可以通过配置达到在目标对象执行前或者执行后进行其他操作的目的。其实也就是AOP完成了一系列的过程,为目标对象建立了代理对象,然后启动代理对象的拦截器来完成各种横切面的注入的过程。这个代理对象可以通过使用JDK动态代理或者是CGLIB动态代理来创建。同时,这一系列的织入设计是通过一系列的Adapter来实现的。通过一系列Adapter的设计,可以把AOP的横切面设计和Proxy模式有机地结合起来。

在Sprong的AOP模块中,代理对象的生成主要是通过配置和调用ProxyFactoryBean来完成的。在ProxyFactoryBean中封装了主要代理对象的生成过程。

图4.1 ProxyFactory的类继承关系

而在ProxyFactoryBean中,代理对象的生成是以getObject方法为入口的

public Object getObject() throws BeansException {
        //初始化通知器链
        initializeAdvisorChain();
        //对singleton和prototype类型加以区分,生成对应的Proxy对象
        if (isSingleton()) {
            return getSingletonInstance();
        }
        else {
            if (this.targetName == null) {
                logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
                        "Enable prototype proxies by setting the 'targetName' property.");
            }
            return newPrototypeInstance();
        }
    }

为Proxy代理对象配置Advisor链是在initializeAdvisorChain方法中完成的,这个初始化的过程中有一个标志位advisorChainInitialized,这个标志用来表示通知器是否已经初始化。如果已经初始化,那么这里就不会再进行初始化,而是直接返回。由于ProxyFactoryBean实现了BeanFactoryAware接口,而在初始化Bean的过程中,会对所有实现了该接口的Bean设置一个setBeanFactory的回调,即可以通过生成的Bean获取对应的BeanFactory,所以我们在这里可以很方便的通过java this.beanFactory.getBean(name)来获得通知器

private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
        if (this.advisorChainInitialized) {
            return;
        }

        if (!ObjectUtils.isEmpty(this.interceptorNames)) {
            if (this.beanFactory == null) {
                throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
                        "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
            }

            // Globals can't be last unless we specified a targetSource using the property...
            if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
                    this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
                throw new AopConfigException("Target required after globals");
            }

            // Materialize interceptor chain from bean names.
            for (String name : this.interceptorNames) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Configuring advisor or advice '" + name + "'");
                }

                if (name.endsWith(GLOBAL_SUFFIX)) {
                    if (!(this.beanFactory instanceof ListableBeanFactory)) {
                        throw new AopConfigException(
                                "Can only use global advisors or interceptors with a ListableBeanFactory");
                    }
                    addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
                            name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
                }

                else {
                    // If we get here, we need to add a named interceptor.
                    // We must check if it's a singleton or prototype.
                    Object advice;
                    if (this.singleton || this.beanFactory.isSingleton(name)) {
                        // Add the real Advisor/Advice to the chain.
                        advice = this.beanFactory.getBean(name);
                    }
                    else {
                        // It's a prototype Advice or Advisor: replace with a prototype.
                        // Avoid unnecessary creation of prototype bean just for advisor chain initialization.
                        advice = new PrototypePlaceholderAdvisor(name);
                    }
                    addAdvisorOnChainCreation(advice, name);
                }
            }
        }

        this.advisorChainInitialized = true;
    }

getObject方法中所示,如果是单例对象,则调用getSingletonInstance方法生成单例的代理对象,否则将调用newPrototypeInstance方法。

图4.2 生成单例代理对象
图4.3 生成prototype类型的代理对象
图4.4 上面两个方法都是通过createAopProxy返回的AopProxy传入getProxy方法中来得到代理对象

这里出现了AopProxy类型的对象,Spring利用这个AopProxy接口类把AOP代理对象的实现与框架的其他部分有效地分离开来。AopProxy是一个接口,它由两个子类实现,一个是CglibAopProxy,另一个是JdkDynamicProxy。即对这两个AopProxy接口的子类的实现,Spring分别通过CGLIB和JDK来的AopProxy对象。

具体代理对象的生成是在ProxyFactoryBean的基类AdvisedSupport的实现中借助AopProxyFactory完成的,这个代理对象要么从JDK中生成,要么借助CGLIB获得。因为ProxyFactoryBean本身就是AdvisedSupport的子类,所以在ProxyFactoryBean中获得AopFactory是比较方便的,可以在ProxyCreatorSupport中看到,具体的AopProxy是通过AopProxyFactory来生成的。至于需要生成什么样的对象,所有的信息都在AdvisedSupport里,这个对象也是生成AopProxy的方法的输入参数,这里设置为this本身,因为ProxyCreatorSupport本身就是AdvisedSupport的子类。

图4.5 ProxyCreatorSupport生成AopProxy对象

ProxyCreatorSupport中,我们通过createAopProxy生成AopProxy对象。在上方代码中我们可以看到是使用了一个AopProxyFactory。这个AopProxyFactory实际上使用的是DefaultAopProxyFactory。它作为AopProxyFactory的创建工厂对象,是在ProxyFactoryBean的基类ProxyCreatorSupport中被创建的。在创建AopProxyFactory时,它被设置为DefaultAopProxyFactory

AopProxy代理对象生成的过程中,需要考虑使用哪种生成方式。如果目标对象是接口类,那么使用JDK动态代理来生成代理对象;否则将使用CGLIB来生成目标对象的代理对象。为了满足不同类型的代理对象的生成要求,DefaultAopProxyFactory作为AopProxy对象的生成工厂,可以根据不同的需求来生成这两种AopProxy对象。

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.");
            }
            // 如果 targetClass 是接口类,使用JDK来生成AopProxy
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            // 否则使用CGLIB来生成AopProxy对象
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

JDK动态代理生成AopProxy代理对象

@Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       // 调用JDK生成Proxy的地方
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

CGLIB动态代理生成AopProxy代理对象

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

    @Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
        }
        // 从Advised中获得在IoC容器里配置的target对象
        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, classLoader);

            // 验证代理对象的接口设置
                       // 创建并配置CGLIB的Enhancer,这个Enhancer对象是CGLIB的主要操作类
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));

            Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            // fixedInterceptorMap only populated at this point, after getCallbacks call above
            enhancer.setCallbackFilter(new ProxyCallbackFilter(
                    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
            enhancer.setCallbackTypes(types);

            // Generate the proxy class and create a proxy instance.
            return createProxyClassAndInstance(enhancer, callbacks);
        }
        catch (CodeGenerationException ex) {
            throw new AopConfigException("Could not generate CGLIB subclass of class [" +
                    this.advised.getTargetClass() + "]: " +
                    "Common causes of this problem include using a final class or a non-visible class",
                    ex);
        }
        catch (IllegalArgumentException ex) {
            throw new AopConfigException("Could not generate CGLIB subclass of class [" +
                    this.advised.getTargetClass() + "]: " +
                    "Common causes of this problem include using a final class or a non-visible class",
                    ex);
        }
        catch (Throwable ex) {
            // TargetSource.getTarget() failed
            throw new AopConfigException("Unexpected AOP exception", ex);
        }
    }

通过使用AopProxy对象封装目标对象之后,ProxyFactoryBean的getObject方法得到的对象就不是一个普通的Java对象了,而是一个AopProxy代理对象。这时已经不会让应用调用在ProxyFactoryBean中配置的target目标的方法实现,而是作为AOP实现的一部分。对target目标对象的方法调用会首先被AopProxy代理对象拦截,对于不同的AopProxy代理对象生成方式,会使用不同的拦截回调入口。

推荐阅读更多精彩内容