天天看點

Spring 源碼分析(三) —— AOP(三)實作思路

核心邏輯  

        上文中提到了 AOP 建立代理等等的具體操作都是在 AnnotationAwareAspectAutoProxyCreator 類中來成的,通過上文的自動注冊,下面讓我們看 AnnotationAwareAspectAutoProxyCreator 是如何工作的,首先是 AnnotationAwareAspectAutoProxyCreator 的繼承關系圖:

Spring 源碼分析(三) —— AOP(三)實作思路

        然後是 AnnotationAwareAspectAutoProxyCreator 的層次結構圖:

Spring 源碼分析(三) —— AOP(三)實作思路

        這裡需要特别注意的是 BeanPostProcessor 接口,我們知道實際運用中,如果你需要對項目中的 Bean 進行代理,在 Spring 的 xml 的配置一個 BeanPostProcessor 就行。由此,我們可以知道 AnnotationAwareAspectAutoProxyCreator 實作代理也是通過 BeanPostProcessor 接口來完成的,是以我們對于 AOP 邏輯分析也是由 BeanPostProcessor 執行個體化前的 postProcessAfterInitialization 方法開始,而 AnnotationAwareAspectAutoProxyCreator 的 postProcessAfterInitialization 具體實作是在其父類 AbstractAutoProxyCreator 中完成的。我們對 AOP 邏輯的分析也由此開始。

AbstractAutoProxyCreator.java

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   if (bean != null) {
      // 根據給定的 bean 的 class 和 name 建構出個 key,格式:beanClassName_beanName
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (!this.earlyProxyReferences.contains(cacheKey)) {
         // 如果它适合被代理,則需要封裝指定 bean。
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   // 是否已經處理過
   if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
   // 無需增強
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
   // 給定的 bean 類是否代表一個基礎設施類,基礎設施類不應代理,或者配置了指定 bean 不需要自動代理
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 如果存在增強方法則建立代理
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
   // 如果擷取到了增強則需要針對增強建立代理
   if (specificInterceptors != DO_NOT_PROXY) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
      // 建立代理
      Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
   }

   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}
           

        以上兩個方法構成了建立代理的雛形,當然,開始前還有些判斷工作。而建立代理的核心邏輯部分是在 AbstractAutoProxyCreator 類中完成的,而建立代理前的準備工作主要分為兩步:(1)擷取增強方法或者增強器。(2)根據擷取的增強進行代理。下面是 AbstractAutoProxyCreator 的時序圖:

Spring 源碼分析(三) —— AOP(三)實作思路

    結合時序圖,我們知道真正建立代理地方是從 getAdvicesAndAdvisorsForBean 開始的。雖然看起來很簡單,但其實每一步都有大量複雜的邏輯。但在分析源碼前,我們必須先對增強以及其具體邏輯有所了解。

設計的基石

增強

        Advice(也翻作 通知)定義了連接配接點做什麼,為切面增強提供了織入的接口。在 Spring AOP 中,它主要描述 Spring AOP 圍繞方法調用而注入的切面行為。Advice 是 AOP 聯盟定義的一個接口,具體的接口定義在 org.aopalliance.aop.Advice 中。在 Spring AOP 的實作中,使用了這個統一接口,并通過這個接口,為 AOP 切面增強的注入功能做了更多的細化和擴充,比如前面提到的具體通知類型,如BeforeAdvice、AfterAdvice、ThrowsAdvice等。作為 Spring AOP 定義的接口類,具體的切面增強可以通過這些接口內建到 AOP 架構中去發揮作用。對于這些接口類,下面是他的主要接口繼承圖:

Spring 源碼分析(三) —— AOP(三)實作思路

        下面是 Advice 接口繼承的層次圖:

Spring 源碼分析(三) —— AOP(三)實作思路

        看一個程式的具體設計思路沒有比看接口來的更直接的了,下面我們就從第一個 BeforeAdvice 的繼承接口 MethodBeforeAdvice 開始(BeforeAdvice 裡沒有任何東西)

MthodInterceptor.java

public interface MethodInterceptor extends Interceptor {
    void before(Method method, Object[] args, Object target) throws Throwable;
}
           

        明顯能夠看出這是一個回調函數,他的具體參數有:Method 對象,這個參數是目标方法的反射對象,Object[]對象數組,這個對象數組中包含輸入參數。而我們根據繼承關系看他的具體實作類 AspectJMethodBeforeAdvice。

AspectJMethodBeforeAdvice.java 

@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
    invokeAdviceMethod(getJoinPointMatch(), null, null);
}
           

AbstractAspectJAdvice.java

protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {
   return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}

protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
   Object[] actualArgs = args;
   if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {
      actualArgs = null;
   }
   try {
      ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
      // 激活增強
      return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
   }
   catch (IllegalArgumentException ex) {
      throw new AopInvocationException("Mismatch on arguments to advice method [" +
            this.aspectJAdviceMethod + "]; pointcut expression [" +
            this.pointcut.getPointcutExpression() + "]", ex);
   }
   catch (InvocationTargetException ex) {
      throw ex.getTargetException();
   }
}
           

        invokeAdviceMethodWithGivenArgs 方法中的 aspectJAdviceMethod 正是對于前置增強的方法,在這裡實行了調用。下面是 AfterAdvice 的繼承接口 AfterReturningAdvice 接口:

AfterReturningAdvice.java 

public interface AfterReturningAdvice extends AfterAdvice {
   void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
           

        而 AfterReturningAdvice 接口的核心邏輯是在其實作父類 AspectJAfterReturningAdvice 中完成的。

AspectJAfterReturningAdvice.java 

@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
   if (shouldInvokeOnReturnValueOf(method, returnValue)) {
      invokeAdviceMethod(getJoinPointMatch(), returnValue, null);
   }
}
           

      後面的和 BeforeAdvice 一樣。根據分析我們可以知道 BeforeAdvice 和 AfterAdvice。盡管,這兩個增強行為一緻,旦因為它實作的 AOP 通知不同,是以就被 AOP 編織到不同的調用場合中了。而其他的增強基本思路都是如此,這裡就不展開了。 

切點

        Pointcut(關注點,也稱 切點)用于決定 Advice 增強作用于哪個連接配接點,也就是說通過 Pointcut 來定義需要增強的方法集合,而這些集合的選取可以通過一定的規則來完成,例如:這些需要增強的地方可以由某個正規表達式來進行辨別,或根據某個方法名來進行比對等。下面是 Pointcut 的層次結構圖:

Spring 源碼分析(三) —— AOP(三)實作思路

        下面是 Pointcut 接口:

public interface Pointcut {
   ClassFilter getClassFilter();
   MethodMatcher getMethodMatcher();
   Pointcut TRUE = TruePointcut.INSTANCE;
}
           

        通過 Pointcut 接口的基本定義我們可以看到,需要傳回一個 MethodMatcher。對于 Point 的比對判斷功能,具體是由這個傳回的 MethodMatcher 來完成的,也就是說,有這個 MethodMatcher 來判斷是否需要對目前方法調用進行增強或者配置應用。我接着對 MethodMatcher 接口進行分析:

MethodMatcher.java

public interface MethodMatcher {
   boolean matches(Method method, Class<?> targetClass);
   boolean isRuntime();
   boolean matches(Method method, Class<?> targetClass, Object[] args);
   MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
           

       通過閱讀注釋我們知道了 Pointcut 的核心邏輯是在 matches 方法中完成的,我就以通過方法名比對的 NameMatchMetchMethodPointcut 類的 matches 方法來說明:

NameMatchMetchMethodPointcut.java 

@Override
public boolean matches(Method method, Class<?> targetClass) {
   for (String mappedName : this.mappedNames) {
      if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {
         return true;
      }
   }
   return false;
}
           

        從源碼我們看到他的實作是非常簡單的,比對條件就是方法名相同或者方法名比對。

  通知器

        Advisor(通知器)用一個對象将對目标方法的切面增強設計(Advice)和關注點的設計(Pointcut)結合起來。下面是 Advisor 接口的層次繼承圖:

Spring 源碼分析(三) —— AOP(三)實作思路

        這是一個 Advisor 接口的設計,我們從中可以看出通過 Advisor,可以定義應該使用哪個增強并且在哪個關注點使用它,也就是通過 Advisor,把 Advice 和 Pointcut 結合起來,這個結合為使用 IOC 容器配置 AOP應用,提供了便利。下面是 Advisor 接口:

Advisor.java

public interface Advisor {
   Advice getAdvice();
   boolean isPerInstance();
}
           

        下面我們以一個 Advisor 的實作(DefaultPointcutAdvisor)為例,進而了解 Advisor 的工作原理。

DefaultPointcutAdvisor.java

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

   private Pointcut pointcut = Pointcut.TRUE;

   public DefaultPointcutAdvisor() {
   }

   public DefaultPointcutAdvisor(Advice advice) {
      this(Pointcut.TRUE, advice);
   }

   public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
      this.pointcut = pointcut;
      setAdvice(advice);
   }

   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() + "]";
   }
}
           

         這個類主要職責是 Pointcut 的設定或者擷取,在 DefaultPointcutAdvisor 中,Pointcut 預設被設定為 Pointcut.True,這個 Pointcut.True 接口被定義為 Pointcut True = TruePointcut.INSTANCE。而關于 Advice 部分的設定與擷取是由其父類 AbstractGenericPointcutAdvisor 來完成的。

指定Bean的增強方法

       下面就讓我們回到源碼分析,前面已經提到了通知器,這裡就不冗述了,他的主要作用是用來整合切面增強設計(Advice)和切入點設計(Pointcut)。而對于指定 bean 的增強方法的擷取,一般包含擷取所有增強以及尋找所有增強中适合于 bean 的增強并應用這兩個步驟。

AnnotationAwareAspectAutoProxyCreator.java

@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
   List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
   if (advisors.isEmpty()) {
      return DO_NOT_PROXY;
   }
   return advisors.toArray();
}

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
   List<Advisor> candidateAdvisors = findCandidateAdvisors();
   List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
   extendAdvisors(eligibleAdvisors);
   if (!eligibleAdvisors.isEmpty()) {
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
   }
   return eligibleAdvisors;
}
           

        從源碼看,這兩個步驟一定是由 findCandidateAdvisors 和 findAdvisorsThatCanApply 來完成。還有值得注意的是,如果無法找到對應的通知器便會傳回 DO_NOT_PROXY(null)。