1. 程式人生 > >Spring 原始碼分析(三) —— AOP(二)Spring AOP 整體架構

Spring 原始碼分析(三) —— AOP(二)Spring AOP 整體架構

Spring AOP 架構

        先是生成代理物件,然後是攔截器的作用,最後是編織的具體實現。這是AOP實現的三個步驟,當然Spring AOP也是一樣。

        而從Spring AOP整體架構上看其核心都是建立在代理上的。當我們建立增強例項時,我們必須先使用 ProxyFactory 類加入我們需要織入該類的所有增強,然後為該類建立代理。一般而言,AOP實現代理的方法有三種,而 Spring 內部用到了其中的兩種方法:動態代理和靜態代理,而動態代理又分為JDK動態代理和CGLIB代理而前面提到的 ProxyFactory 類控制著 Spring AOP 中的織入和建立代理的過程。在真正建立代理之前,我們必須指定被增強物件或者目標物件。我們是通過 setTarget() 方法來完成這個步驟的。而 ProxyFactory 內部將生成代理的過程全部轉給 DefaultAopProxyFactory 物件來完成,然後根據設定轉給其他的類來完成。下面是 Spring AOP 生成代理的原理圖:

        而特別需要注意的是,在 Spring AOP 中,一個 Advisor(通知者,也翻作 通知器或者增強器)就是一個切面,他的主要作用是整合切面增強設計(Advice)和切入點設計(Pointcut)。Advisor有兩個子介面:IntroductionAdvisor 和 PointcutAdvisor,基本所有切入點控制的 Advisor 都是由 PointcutAdvisor 實現的。而下面的是Spring AOP的整體架構圖 

        乍一看,非常的複雜,但如果結合上面的Spring AOP生成代理的原理圖一起看,也就那麼回事,只是豐富的許多屬性了,我們會慢慢介紹的。

Spring AOP 使用範例

 專案結構

        分析 Spring AOP 原始碼的話,直接從最簡單業務程式碼入手,一步步的對原始碼進行解析。希望藉此能看清楚 Spring AOP 縱向和橫向程式碼之間的聯絡,而且思維也不會太跳躍。下面就是 Spring AOP 測試程式 Test  專案的結構圖:

        建立用於攔截的Bean

       在實際專案中,Bean應該可以算是核心邏輯了,其中的 printTest 方法中可能會封裝整個的核心業務邏輯,如此重要的地方我們想在printTest方法前後加入日誌來進行跟蹤或者除錯也是很自然地想法,但是如果直接修改原始碼是不符合面向物件的設計原則的,它並不符合面向物件的設計方式,而且隨意的改動原有的程式碼也會造成一定的風險,這裡就體現 AOP 的優越性了。

package test;

/**
 * 測試Bean
 *
 * @Auther kay
 * @Date   2016-03-08
 */
public class TestBean {

    private String testStr = "testStr";

    public String getTestStr(){
        return testStr;
    }

    public void setTestStr(String testStr){
        this.testStr = testStr;
    }

    public void printTest(){
        System.out.println("test Bean");
    }
}

        建立Advisor

        Spring AOP 實現的核心,這裡採用了@AspectJ註釋對POJO進行標註,使AOP的工作大大簡化。

package test;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

/**
 * Aspect切面
 *
 * @Auther kay
 * @Date   2016-03-08
 */
@Aspect
public class AspectTest {

    /**
     * 配置切入點,主要為方便同類中其他方法使用此處配置的切入點
     */
    private final String POINT_CUT = "execution(* test.TestBean.*(..))";

    /**
     * 配置前置通知,使用在方法aspect()上註冊的切入點
     * 同時接受JoinPoint切入點物件,可以沒有該引數
     */
    @Before(POINT_CUT)
    public void beforeTest(){
        System.out.println("before Test");
    }

    /**
     * 配置後置通知,使用在方法aspect()上註冊的切入點
     */
    @After(POINT_CUT)
    public void afterTest(){
        System.out.println("after Test");
    }

    /**
     * 配置環繞通知,使用在方法aspect()上註冊的切入點
     *
     * @param point JoinPoint的支援介面
     * @return Object
     */
    @Around(POINT_CUT)
    public Object arountTest(ProceedingJoinPoint point){
        System.out.println("before1");
        Object object = null;
        
        try{
            object = point.proceed();
        }
        catch (Throwable e){
            e.printStackTrace();
        }
        System.out.println("after1");

        return object;
    }
}

        配置檔案

        XML是Spring的基礎,儘管Spring一再簡化配置,並且大有使用註解取代XML配置之勢,但無論如何XML還是Spring的基石。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.1.xsd
                            http://www.springframework.org/schema/aop
                            http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">

    <!-- 啟用自動代理功能 -->
    <aop:aspectj-autoproxy />

    <!-- 業務邏輯切面配置 -->
    <bean id="test" class = "test.TestBean" />
    <bean class="test.AspectTest" />

</beans>

        測試

        測試程式,驗證效果。

package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 測試程式
 *
 * @Auther kay
 * @Date   2016-03-08
 */
public class Test {

    public static void main(String[] arge){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        TestBean bean = context.getBean("test", TestBean.class);

        bean.printTest();
    }
}

 列印結果

        控制檯列印如下程式碼:

        Spring AOP是如何實現AOP的?首先我們知道,Spring是否支援註釋的AOP是由一個配置檔案來控制的,也就是<aop:aspectj-autoproxy />,下面我們分析就從這句配置開始。

Spring AOP 的入口

        BeanDefinition 的解析

        首先spring-config.xml配置檔案裡的<aop:aspectj-autoproxy>進行解析,發現其如果不是bean標籤,則會用不同的類來解析。解析AOP的是:

        http\://www.springframework.org/schema/aop=org.springframeworl.aop.config.AopNamespaceHandler。也就是  AopNamespaceHandler 類來解析,我們對 AopNamespaceHandler 類進行分析

        發現 AopNamespaceHandler 的一段程式碼是 <aop:aspectj-autoproxy> 解析的入口

AopNamespaceHandler.java

        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());

BeanDefinition 的載入

  所有解析器,都是由 BeanDefinitionParser 介面的統一實現,入口都是從 parse函式開始的,AspectJAutoProxyBeanDefinitionParser 的 parse 函式如下:

AspectJAutoProxyBeanDefinitionParser .java

public BeanDefinition parse(Element element, ParserContext parserContext) {

   // 註冊 AnnotationAwareAspectJAutoProxyCreator
   AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
   
   // 對於註釋中子類進行處理
   extendBeanDefinition(element, parserContext);
   return null;
}
    其中的 registerAspectJAnnotationAutoProxyCreatorIfNecessary 函式是 AnnotationAwareAspectJAutoProxyCreator 註冊的地方,也是註冊的主要邏輯實現的地方。

AopNamespaceUtils.java

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
      ParserContext parserContext, Element sourceElement) {

   // 註冊或升級 AutoProxyCreator 定義 beanName 為 org.Springframework.aop.config.internalAutoProxyCreator的
   // BeanDefinition
   BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
         parserContext.getRegistry(), parserContext.extractSource(sourceElement));
         
   // 對於 proxy-target-class 以及 expose-proxy 屬性的處理
   useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
   
   // 註冊元件並通知,便於監聽器作進一步處理
   // 其中 beanDefinition 的 className 為 AnnotationAwareAspectJAutoProxyCreator
   registerComponentIfNecessary(beanDefinition, parserContext);
}

            BeanDefinition 的註冊

在對於AOP實現上,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成的,它可以根據@Point 註解定義的切點來自動代理相匹配的 bean。但是為了配置簡便,Spring 使用了自定義配置來幫我們自動註冊 AnnotationAwareAspectJAutoProxyCreator。具體實現如下

AopNamespaceUtils.java

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

AopConfigUtils.java

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)) {
      // AUTO_PROXY_CREATOR_BEAN_NAME="org.springframework.aop.config.internalAutoProxyCreator"
      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) {
            // 改變bean 最重要的就是改變bean 所對應的 className 屬性
            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);
   // AUTO_PROXY_CREATOR_BEAN_NAME="org.springframework.aop.config.internalAutoProxyCreator"
   registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
   return beanDefinition;
}
     以上程式碼中實現了自動註冊 AnnotationAwareAspectJAutoProxyCreator 類的功能,同時這裡還涉及了一個優先順序的問題,如果已經存在了自動代理建立器,而且存在的自動代理建立器與現在的不一致,那麼就需要根據優先順序來判斷到底需要使用哪一個?   

屬性處理

  一般而言,Spring AOP 內部使用 JDK 動態代理或者 CGLIB 來為目標物件建立代理。如果被代理的目標物件實現了至少一個介面,則會使用 JDK 動態代理。所有該目標型別實現的介面都將被代理。若該目標物件沒有實現任何介面,則建立一個 CGLIB 代理。一般情況下,使用 CGLIB 需要考慮增強(advise)Final 方法不能被複寫以及需要指定 CGLIB 包的位置,儘管 CGLIB 效率更高,但還是推薦使用 JDK 動態代理。

        而 AOP 配置中的 prioxy-target-class 和 expose-proxy 屬性在代理生成中起到了至關重要的。prioxy-target-class 主要負責上面兩種代理的實現,而 expose-proxy 則負責自我呼叫切面中的增強。

AopNamespaceUtils.java

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {
   if (sourceElement != null) {
      // 對於 proxy-target-class 屬性的處理
      boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
      if (proxyTargetClass) {
         AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
      }
      // 對於 expose-proxy 屬性的處理
      boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
      if (exposeProxy) {
         AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
      }
   }
}

AopConfigUtils.java

public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
   // 強制使用,其實也是一個屬性設定
   if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
      BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
      definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
   }
}

static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
   if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
      BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
      definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
   }
}

        這些,基本就是Spring AOP部分的實現框架了,下節就要對AOP的主要實現類 AnnotationAwareAspectJAutoProxyCreator 進行分析。