Spring AOP學習筆記02:如何開啓AOP

  上文簡要總結了一些AOP的基本概念,並在此基礎上敘述了Spring AOP的基本原理,而且輔以一個簡單例子幫助理解。從本文開始,咱們要開始深刻到源碼層面來一探Spring AOP魔法的原理了。html

  要使用Spring AOP,第一步是要將這一功能開啓,通常有兩種方式:spring

  • 經過xml配置文件的方式;
  • 經過註解的方式;

 

1. 配置文件開啓AOP功能

  咱們先來看一下配置文件的方式,這個上文也提到過,在xml文件中加上對應的標籤,並且別忘了加上對應的名稱空間(即下面的xmlns:aop。。。):緩存

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop = "http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
     
     <aop:aspectj-autoproxy/>

</beans>

  這裏是經過標籤<aop:aspectj-autoproxy/>來完成開啓AOP功能,這是一個自定義標籤,須要自定義其解析,而這些spring都已經實現好了,前面專門寫過一篇文章講述spring是如何解析自定義xml標籤的,咱們這裏大體回顧一下解析流程:springboot

  • 定義一個XML文件來描述你的自定義標籤元素;
  • 建立一個Handler,擴展自NamespaceHandlerSupport,用於註冊下面的parser;
  • 建立若干個BeanDefinitionParser的實現,用來解析XML文件中的定義;
  • 將上述文件註冊到Spring中,這裏實際上是作一下配置;

  咱們就不照着這個步驟來了,咱們直接參考spring對這個自定義標籤的解析過程,上面的4個步驟只是做爲參考,在整個解析過程當中都會涉及到。架構

  前面講解析自定義xml標籤時候提到過,解析的流程大體以下:dom

  • 首先會去獲取自定義標籤對應的名稱空間;
  • 而後根據名稱空間找到對應的NamespaceHandler;
  • 調用自定義的NamespaceHandler進行解析;

1.1 獲取名稱空間

  這裏<aop:aspectj-autoproxy/>對應的名稱空間是什麼呢?在上面的開啓aop的配置文件裏面名稱空間那裏給出了一些線索,其實就是下面這個:ide

http://www.springframework.org/schema/aop

  至於名稱空間的獲取,也無甚好說的,其實就是直接調用org.w3c.dom.Node提供的相應方法來完成名稱空間的提取。post

1.2 獲取handler

  而後又是如何根據名稱空間找到對應的NamespaceHandler呢?以前也說到過,在找對應的NamespaceHandler時會去META-INF/spring.handlers這個目錄下加載資源文件,咱們來找一下spring.handlers這個文件看看(須要去spring-aop對應的jar報下找):學習

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

  看到沒,這裏是以key-value的形式維護着名稱空間和對應handler的關係的,因此對應的handler就是這個AopNamespaceHandler。spring根據名稱空間找到這個handler以後,會經過反射的方式將這個類加載,並緩存起來。ui

1.3 解析標籤

  上面的handler只有一個自定義的方法:

public void init() {
    // In 2.0 XSD as well as in 2.1 XSD.
    registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
    registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
    registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

    // Only in 2.0 XSD: moved to context namespace as of 2.1
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}

  這是一個初始化方法,在加載的時候會執行,主要做用就是註冊一些解析器,這裏咱們主要關注AspectJAutoProxyBeanDefinitionParser,這就是咱們要找的,它的做用就是解析<aop:aspectj-autoproxy/>標籤的。主要流程就是,spring會調用上一步拿到的AopNamespaceHandler的parse()方法,在這個方法裏面,會將解析的工做委託給AspectJAutoProxyBeanDefinitionParser來完成具體解析工做,咱們就來看一下具體幹了啥吧。

  開始解析的工做從這裏開始:

return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

  此時咱們拿到的handler實際上是咱們自定義的AopNamespaceHandler了,可是它並無實現parse()方法,因此這裏這個應該是調用的父類(NamespaceHandlerSupport)中的parse()方法:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 尋找解析器並進行解析操做
    return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 獲取元素名稱,也就是<aop:aspectj-autoproxy/>中的aspectj-autoproxy
    String localName = parserContext.getDelegate().getLocalName(element);
    // 根據aspectj-autoproxy找到對應的解析器,也就是在registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
    // 註冊的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
            "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

  首先是尋找元素對應的解析器,而後調用其parse()方法。結合咱們前面的示例,其實就是首先獲取在AopNamespaceHandler類中的init()方法中註冊對應的AspectJAutoProxyBeanDefinitionParser實例,並調用其parse()方法進行進一步解析:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
    extendBeanDefinition(element, parserContext);
    return null;
}

// 下面的代碼在AopConfigUtils中
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        ParserContext parserContext, Element sourceElement) {

    BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    registerComponentIfNecessary(beanDefinition, parserContext);
}

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

  上面這一堆代碼最核心的部分就在後兩個方法中,就是完成了對AnnotationAwareAspectJAutoProxyCreator類的註冊,到這裏對自定義標籤<aop:aspectj-autoproxy/>的解析也就完成了,能夠看到其最核心的部分就是完成了對AnnotationAwareAspectJAutoProxyCreator類的註冊,那爲何註冊了這個類就開啓了aop功能呢?這裏先賣個關子,後面詳細說。

  這裏再回過頭來看一下上面說到的spring對自定義標籤解析的4個步驟,其實第一步的schema對應的是在org.springframework.aop.config路徑下的spring-aop-3.0.xsd文件,其映射關係是維護在META-INF/spring.schemas文件中的,而spring-aop-3.0.xsd的主要做用就是描述自定義標籤。

  當經過META-INF/spring.handlers找到對應的AopNamespaceHandler,並經過在其加載後執行init()方法過程當中完成了AspectJAutoProxyBeanDefinitionParser的註冊,有這個parser再來完成對自定義標籤的解析工做,這對應上面4個步驟中的第二步和第三部。至於第四步的配置工做,無非就是將spring.schemas和spring.handlers這兩個配置文件放在META-INF/目錄下罷了。

  關於這部分解析過程,寫得不是很是詳細,若是有不明白,能夠參考以前一篇文章,講spring是如何解析自定義xml標籤

 

2. 註解方式開啓aop

  另外一種開啓spring aop的方式是經過註解的方式,使用的註解是@EnableAspectJAutoProxy,能夠經過配置類的方式完成註冊:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

   也能夠在啓動類上直接加上這個註解,這在springboot中比較常見,其實質也是上面的方式。經過這種方式配置以後,就開啓了aop功能,那具體又是如何實現的呢?咱們看一下這個註解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

    /**
     * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
     * to standard Java interface-based proxies. The default is {@code false}.
     */
    boolean proxyTargetClass() default false;

    /**
     * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
     * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
     * Off by default, i.e. no guarantees that {@code AopContext} access will work.
     * @since 4.3.1
     */
    boolean exposeProxy() default false;

}

  這裏咱們的關注點是其經過@Import(AspectJAutoProxyRegistrar.class)引入了AspectJAutoProxyRegistrar,那這又是什麼?

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * Register, escalate, and configure the AspectJ auto proxy creator based on the value
     * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
     * {@code @Configuration} class.
     */
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }

}

  看到這裏,是否是有點眼熟了呢?是的,其實它也是和上面說的xml配置使用的方式同樣,經過AopConfigUtils來完成AnnotationAwareAspectJAutoProxyCreator類的註冊。是否是比xml配置文件的方式方便許多呢。

 

4. 開啓aop的魔法

  經過前面的學習咱們瞭解了能夠經過Spring自定義配置完成對AnnotationAwareAspectJAutoProxyCreator類型的自動註冊,而這個類究竟是作了什麼工做來實現AOP的操做呢?這裏仍是先來看一下AnnotationAwareAspectJAutoProxyCreator的類層次結構:

  這裏有一個很重要的點,就是AnnotationAwareAspectJAutoProxyCreator實現了BeanPostProcessor接口。在IOC部分的文章中有詳細說過,Spring在加載Bean的過程當中會在實例化bean先後調用BeanPostProcessor的相關方法(相關邏輯是在initializeBean方法中,調用postProcessBeforeInitialization、postProcessAfterInitialization方法),而AOP的魔法就是從這裏開始的。

  每次看到這裏,我心裏對spring的軟件架構設計都是涌現出無比的佩服,經過後處理器的方式來作擴展,對原有模塊是沒有任何改動,也不會產生耦合,spring親自踐行着對修改關閉,對擴展開放的原則。

 

3. 總結

   本文咱們學習了spring是如何開啓aop功能的,不管是經過xml配置文件方式,仍是經過Java config這種註解的方式,其最終都是完成了將AnnotationAwareAspectJAutoProxyCreator這個類註冊到spring容器當中,那這個類又有什麼魔法,能夠達到將其註冊到容器即達到開啓aop的功效,其實其繼承自BeanPostProcessor接口,經過後處理器的方式擴展出了開啓spring aop的功能。

相關文章
相關標籤/搜索