1. 程式人生 > >Spring原始碼——AOP

Spring原始碼——AOP

前言

內容主要參考自《Spring原始碼深度解析》一書,算是讀書筆記或是原書的補充。進入正文後可能會引來各種不適,畢竟閱讀原始碼是件極其痛苦的事情。

  • 面向物件程式設計為什麼需要面向切面程式設計(AOP),AOP又是什麼?
  • AspectJ 和 Spring AOP 又是什麼關係?
  • AOP 中一些基本概念。
  • 幫助理解本文第一部分 動態AOP使用示例

下面正式Spring原始碼 AOP部分的研究。

I. 動態AOP使用示例

為了在 spring-mytest 模組中使用上Spring AOP,需要額外引入 spring-aopspring-aspects 兩個模組, spring-aop

是可以正常引入的,而 spring-aspects 模組我們在編譯原始碼時由於IDEA不識別所以我們當初去除了該模組,參考這篇文章的做法,我添加了打包好的 spring-aspects 模組。下面就是 spring-mytest 模組的 gradle 配置:

dependencies {
    compile(project(":spring-beans"))
    compile(project(":spring-context"))
    compile(project(":spring-aop"))
    compile group: 'org.springframework'
, name: 'spring-aspects', version: '5.0.7.RELEASE' testCompile group: 'junit', name: 'junit', version: '4.12' }

建立需要被攔截的bean

在實際工作中,一個 bean 可能是滿足業務需要的核心邏輯。例如下面建立的 bean 中的 test() 方法中可能會封裝著某個核心業務,這裡就用列印語句簡單代替一下。但是,如果我們想在 test() 方法核心心業務前後加入日誌來跟蹤測試,如果直接修改原始碼並不符合面向物件的設計方法,而且隨意改動原有程式碼也會造成一定的風險,還好Spring AOP能夠幫我們做到了這一點。

public class TestBean {

   private String testStr = "testStr";

   public String getTestStr() {
      return testStr;
   }

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

   public void test() {
      // 核心業務
      System.out.println("test");
   }
} 

建立Advisor

Spring中摒棄了最原始的繁雜配置方式而採用 @AspectJ 註解對 POJO 進行標註,使AOP的工作大大簡化,例如,在 AspectJTest 類中,我們要做的就是在所有類的 test() 方法執行前在控制檯列印 beforeTest 。而在所有類的 test() 方法執行後列印 afterTest,同時又使用環繞的方式在所有類的 test() 方法執行前後在此分別列印 before1after1

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

@Aspect
public class AspectJTest {

   @Pointcut("execution(* *.test(..))")
   public void test() {

   }

   @Before("test()")
   public void beforeTest() {
      System.out.println("beforeTest");
   }

   @After("test()")
   public void afterTest() {
      System.out.println("afterTest");
   }

   @Around("test()")
   public Object aroundTest(ProceedingJoinPoint proceedingJoinPoint) {
      System.out.println("before1");
      Object o = null;
      try {
         o = proceedingJoinPoint.proceed();
      } catch (Throwable throwable) {
         throwable.printStackTrace();
      }
      System.out.println("after1");
      return o;
   }
}

建立配置檔案

要在Spring中開啟AOP功能,還需要在配置檔案中作如下宣告:

<?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.xsd
                     http://www.springframework.org/schema/aop    http://www.springframework.org/schema/aop/spring-aop.xsd">

   <aop:aspectj-autoproxy />

   <bean id="test" class="guo.ping.aop.usedemo.TestBean" />
   <bean class="guo.ping.aop.usedemo.AspectJTest" />

</beans>

可以看到,開啟AOP功能的是 <aop:aspectj-autoproxy /> 這個自定義標籤。

測試

首先,先編寫測試方法:

public class AopDemoTest {

   @Test
   public void testAopDemo() {
      ApplicationContext context = new ClassPathXmlApplicationContext("aopDemo-Test.xml");
      TestBean testBean = (TestBean) context.getBean("test");
      testBean.test();
   }
}

測試結果: 測試結果 Spring實現了對所有類的 test() 方法進行增強,使輔助功能可以獨立於核心業務之外,方便與程式的擴充套件和解耦。這裡的測試結果可以發現,先執行環繞通知的前後處理方法,然後再處理 @Before@After 通知。

II. 動態AOP自定義標籤

我們配置了 <aop:aspectj-autoproxy /> 之後便可以開啟Spring的AOP功能,而且該標籤屬於自定義標籤型別,那麼回顧之前介紹Spring對於自定義標籤解析的流程,實現這一功能必然存在著某個繼承自 NamespaceHandlerSupport 的實現類,在其 init() 方法中註冊了針對 aspectj-autoproxyBeanDefinitionParser 解析器。進入原始碼詳細檢視。

參照之前的幾篇文章,我們debug進入到解析自定義標籤 <aop:aspectj-autoproxy />,可以看到即將進入解析自定義標籤環節。繼續深入。 解析自定義標籤 根據名稱空間獲取 NamespaceHandlerSupport獲取NamespaceHandlerSupport 獲取結果為 AopNamespaceHandler獲取結果AopNamespaceHandler 類中的 init() 方法中可以清晰的看到,配置了關於 aspectj-autoproxy 的解析器類 AspectJAutoProxyBeanDefinitionParser

public class AopNamespaceHandler extends NamespaceHandlerSupport {

   /**
    * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
    * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
    * and '{@code scoped-proxy}' tags.
    */
   @Override
   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());
   }

}

這樣,我們就已經知道Spring對於 <aop:aspectj-autoproxy /> 標籤的解析相關功能是用 AspectJAutoProxyBeanDefinitionParser 類實現的,一起研究一下這個類。

我們著重研究 AspectJAutoProxyBeanDefinitionParser 類的 parse() 方法。

/**
 * 解析aspectj-autoproxy自定義標籤
 * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions}
 * @param parserContext the object encapsulating the current state of the parsing process;
 * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
 * @return
 */
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
   // 註冊AnnotationAwareAspectJAutoProxyCreator
   AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
   // 對於註解中子類的處理
   extendBeanDefinition(element, parserContext);
   return null;
}

因為我們該標籤主要用於配置,不用於容器進行建立 bean,所以經過兩個函式的設定最終返回了 null。我們著重研究兩個函式具體做了什麼。

註冊AnnotationAwareAspectJAutoProxyCreator

首先研究 AopNamespaceUtilsregisterAspectJAnnotationAutoProxyCreatorIfNecessary() 方法。

/**
 * 註冊AnnotationAwareAspectJAutoProxyCreator
 * @param parserContext
 * @param sourceElement
 */
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);
}

這其中呼叫了三個函式,分別做了三件事情。

① 註冊或升級AnnotationAwareAspectJAutoProxyCreator

檢視 AopConfigUtils 中的 registerAspectJAnnotationAutoProxyCreatorIfNecessary() 方法,傳入的 registry 引數實際就是 DefaultListableBeanFactory 的例項。

/**
 * 註冊或升級AutoProxyCreator定義beanName為org.Springframework.aop.config.internalAutoProxyCreator的BeanDefinition
 * @param registry
 * @param source
 * @return
 */
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
      @Nullable Object source) {

   return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

繼續深入:

@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
      @Nullable Object source) {

   Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

   // 如果已經存在了自動代理建立器且存在的自動代理建立器與現在的不一致那麼需要根據優先順序來判斷到底需要使用哪個
   if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
      // 是否存在org.springframework.aop.config.internalAutoProxyCreator名稱的BeanDefinition
      BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
      if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
         // 如果已經存在的beanDefinition型別不是AnnotationAwareAspectJAutoProxyCreator
         int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
         int requiredPriority = findPriorityForClass(cls);
         // 如果AnnotationAwareAspectJAutoProxyCreator的優先順序比原來的高,要進行更改
         if (currentPriority < requiredPriority) {
            // 改變bean最重要的就是改變bean所對應的className屬性
            apcDefinition.setBeanClassName(cls.getName());
         }
      }
      // 如果已經存在自動代理建立器並且與將要建立的一致,那麼無需再次建立
      return null;
   }

   // 如果不存在org.springframework.aop.config.internalAutoProxyCreator名稱的BeanDefinition,則建立並註冊
   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,對應的 beannameorg.springframework.aop.config.internalAutoProxyCreator。如果已經存在就與 AnnotationAwareAspectJAutoProxyCreator 型別的優先順序進行比較,如果現存的優先順序低要進行替換。優先順序的查詢是通過 findPriorityForClass() 方法獲得的。

private static int findPriorityForClass(Class<?> clazz) {
   return APC_PRIORITY_LIST.indexOf(clazz);
}

private static int findPriorityForClass(@Nullable String className) {
   for (int i = 0; i < APC_PRIORITY_LIST.size(); i++) {
      Class<?> clazz = APC_PRIORITY_LIST.get(i);
      if (clazz.getName().equals(className)) {
         return i;
      }
   }
   throw new IllegalArgumentException(
         "Class name [" + className + "] is not a known auto-proxy creator class");
}

優先順序儲存在了 APC_PRIORITY_LIST 中,APC_PRIORITY_LISTAopConfigUtils 的靜態程式碼塊中進行了初始化。

static {
   APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
   APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
   APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}

當然,如果容器中沒有定義好的 beanDefinition,那麼就新建一個並進行註冊(新增至Spring容器的 beanDefinitionMap 中)。

② 處理proxy-target-class和expose-proxy屬性

useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) 函式主要對於自定義標籤的 proxy-target-class 以及 expose-proxy 屬性的處理,這兩個屬性設定方式如下:

<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>

我們進入函式體,方法 registry 引數還是那樣,sourceElement 就是自定義標籤:

/**
 * 對於proxy-target-class以及expose-proxy屬性的處理
 * @param registry
 * @param sourceElement
 */
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
   if (sourceElement != null) {
      // 實現了對proxy-target-class的處理
      boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
      // 當需要使用CGLIB代理和@AspectJ自動代理支援,設定proxy-target-class為true
      if (proxyTargetClass) {
         AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
      }
      // 對expose-proxy的處理,設定為true主要是解決目標物件內部的自我呼叫
      boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
      if (exposeProxy) {
         AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
      }
   }
}

程式碼主要分兩塊,分別對兩個屬性值存在與否以及是否為 true 進行了判斷,從而決定是否進行相應的設定。我們來看一下 AopConfigUtils 中的兩個方法。

/**
 * 設定proxy-target-class為true
 * @param registry
 */
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);
   }
}

/**
 * 設定expose-proxy為true
 * @param registry
 */
public 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);
   }
}

兩個函式其實就是對第①步建立(或升級)的自動代理建立器 beanbeanDefinition 進行了屬性的設定,強制使用的過程其實就是對 bean 的定義的修改過程。

那麼這兩個屬性具體有什麼作用呢?

proxy-target-class 和Spring的動態代理方式選擇相關,如果設定為 true,會強制使用CGLIB動態代理。這一點在後面還會具體分析。

我們具體來看 expose-proxy 屬性,顧名思義,其作用是是否暴露代理物件。關於這一點,參考文章中有相關的具體使用場景,這裡給大家復現一個簡單的案例。

我們簡單的修改第一部分的案例。

增加一個 TestBean 的實現介面 ITest

public interface ITest {
   void test();
   void subTest();
}

然後修改 TestBean,重點是 test() 方法內部呼叫了 subTest() 方法。

public class TestBean implements ITest{
   private String testStr = "testStr";
   public String getTestStr() {
      return testStr;
   }
   public void setTestStr(String testStr) {
      this.testStr = testStr;
   }

   @Override
   public void test() {
      System.out.println("test");
      subTest();
   }

   @Override
   public void subTest() {
      System.out.println("subTest");
   }
}

對兩個方法定義增強,我們增加了一個切點,同時給 test() 方法進行前置和後置通知,給 subTest() 方法設定了環繞通知。

@Aspect
public class AspectJTest {

   @Pointcut("execution(* guo.ping.aop.usedemo.TestBean.test(..))")
   public void test() {
   }

   @Pointcut("execution(* guo.ping.aop.usedemo.TestBean.subTest(..))")
   public void subTest() {
   }

   @Before("test()")
   public void beforeTest() {
      System.out.println("beforeTest");
   }

   @After("test()")
   public void afterTest() {
      System.out.println("afterTest");
   }

   @Around("subTest()")
   public Object aroundTest(ProceedingJoinPoint proceedingJoinPoint) {
      System.out.println("=========beforeSubTest==========");
      Object o = null;
      try {
         o = proceedingJoinPoint.proceed();
      } catch (Throwable throwable) {
         throwable.printStackTrace();
      }
      System.out.println("=========afterSubTest==========");
      return o;
   }
}

執行測試程式碼,出現以下結果: 測試結果 好像沒什麼問題,test() 方法增強成功了。但是,事情沒有這麼簡單,當這兩個方法都是需要開啟事務的,結果 subTest() 方法沒有開啟,僅開啟了 test() 方法的,這是有問題的。那麼如何解決呢?這裡就可以依靠 expose-proxy 屬性,將代理物件暴露出去,其實是暴露至 ThreadLocal 中,可以通過 AopContext.currentProxy() 獲取到代理物件,這在後文會細說。

我們測試一下。修改配置檔案 expose-proxy 的屬性為 true

<?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.xsd
                     http://www.springframework.org/schema/aop    http://www.springframework.org/schema/aop/spring-aop.xsd">

   <aop:aspectj-autoproxy expose-proxy="true"/>

   <bean id="test" class="guo.ping.aop.usedemo.TestBean" />
   <bean class="guo.ping.aop.usedemo.AspectJTest" />

</beans>

修改 test() 方法的呼叫方式,先獲取代理物件,然後呼叫 subTest() 方法。

@Override
public void test() {
   System.out.println("test");
   ITest proxy = (ITest) AopContext.currentProxy();
   proxy.subTest();
}

測試結果如下,完全OK。

測試結果

③ 註冊元件並通知

最後一步,是將前兩步精心構造的 beanDefinition 進行元件的註冊並通知,便於監聽器進一步處理。

/**
 * 註冊元件並通知,便於監聽器進一步處理
 * @param beanDefinition
 * @param parserContext
 */
private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
   if (beanDefinition != null) {
      BeanComponentDefinition componentDefinition =
            new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
      parserContext.registerComponent(componentDefinition);
   }
}

首先用 BeanComponentDefinition 包裝了 beanDefinitionbeanName,然後向 eventListener 註冊 <