曹工說Spring Boot源碼(22)-- 你說我Spring Aop依賴AspectJ,我依賴它什麼了

寫在前面的話

相關背景及資源:html

曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java

曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git

曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下spring

曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?express

曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanapache

曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的json

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)api

曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)緩存

曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中獲得了什麼(context命名空間上)數據結構

曹工說Spring Boot源碼(10)-- Spring解析xml文件,到底從中獲得了什麼(context:annotation-config 解析)

曹工說Spring Boot源碼(11)-- context:component-scan,你真的會用嗎(此次來講說它的奇技淫巧)

曹工說Spring Boot源碼(12)-- Spring解析xml文件,到底從中獲得了什麼(context:component-scan完整解析)

曹工說Spring Boot源碼(13)-- AspectJ的運行時織入(Load-Time-Weaving),基本內容是講清楚了(附源碼)

曹工說Spring Boot源碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎麼和Spring Instrumentation集成

曹工說Spring Boot源碼(15)-- Spring從xml文件裏到底獲得了什麼(context:load-time-weaver 完整解析)

曹工說Spring Boot源碼(16)-- Spring從xml文件裏到底獲得了什麼(aop:config完整解析【上】)

曹工說Spring Boot源碼(17)-- Spring從xml文件裏到底獲得了什麼(aop:config完整解析【中】)

曹工說Spring Boot源碼(18)-- Spring AOP源碼分析三部曲,終於快講完了 (aop:config完整解析【下】)

曹工說Spring Boot源碼(19)-- Spring 帶給咱們的工具利器,建立代理不用愁(ProxyFactory)

曹工說Spring Boot源碼(20)-- 碼網恢恢,疏而不漏,如何記錄Spring RedisTemplate每次操做日誌

曹工說Spring Boot源碼(21)-- 爲了讓你們理解Spring Aop利器ProxyFactory,我已經拼了

工程代碼地址 思惟導圖地址

工程結構圖:

概要

本講,主要講講,spring aop和aspectJ到底啥關係,若是說spring aop依賴aspectJ,那麼,究竟是哪兒依賴它了?

得講證據啊,對不對?

其實,我能夠先說下結論。spring aop是基於代理的,有接口的時候,就是基於jdk 動態代理,jdk動態代理是隻能對方法進行代理的,由於在Proxy.newInstance建立代理時,傳入的第三個參數爲java.lang.reflect.InvocationHandler,該接口只有一個方法:

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

這裏面的method,就是被調用的方法,因此,jdk動態代理,是隻能對方法進行代理。

而aspectJ就要強大多了,能夠對field、constructor的訪問進行攔截;並且,spring aop的採用運行期間去生成目標對象的代理對象來實現,致使其只能在運行期工做。

而咱們知道,AspectJ是能夠在編譯期經過特殊的編譯期,就把切面邏輯,織入到class中,並且能夠嵌入切面邏輯到任意地方,好比constructor、靜態初始化塊、field的set/get等;

另外,AspectJ也支持LTW,前面幾講咱們講過這個東西,即在jvm加載class的時候,去修改class字節碼。

AspectJ也無心去搞運行期織入,Spring aop也無心去搞編譯期和類加載期織入說了半天,spring aop看起來和AspectJ沒半點交集啊,可是,他們真的毫無關係嗎?

我打開了ide裏,spring-aop-5.1.9.RELEASE的pom文件,裏面清楚看到了

<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.1.9.RELEASE</version>
  <name>Spring AOP</name>
  ...
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.1.9.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.1.9.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.jamonapi</groupId>
      <artifactId>jamon</artifactId>
      <version>2.81</version>
      <scope>compile</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
      <version>2.6.0</version>
      <scope>compile</scope>
      <optional>true</optional>
    </dependency>
    // 就是這裏
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
      <scope>compile</scope>
      <optional>true</optional>
    </dependency>
  </dependencies>
</project>

因此,你們看到,spring aop依賴了aspectjweaver。到底爲何依賴它,就是咱們本節的主題。

在此以前,咱們先簡單瞭解下AspectJ。

AspectJ如何比較切點是否匹配目標Class

假設我有以下類:

package foo;

public interface Perform {
    public void sing();
}

而後,咱們再用AspectJ的方式來定義一個切點:

execution(public * *.Perform.sing(..))

你們一看,確定知道,這個切點是能夠匹配這個Perform類的sing方法的,可是,若是讓你用程序實現呢?你怎麼作?

我據說Spring最先的時候,是不依賴AspectJ的,本身寫正則來完成上面的判斷是否匹配切點的邏輯,但後來,不知道爲啥,就變成了AspectJ了。

若是咱們要用AspectJ來判斷,有幾步?

引入依賴

maven的pom裏,只須要引入以下依賴:

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.8.2</version>
</dependency>

定義切點解析器

private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();

static {
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
}

#下面這個方法,就是來獲取切點解析器的,cl是一個classloader類型的實例
/**
 * Initialize the underlying AspectJ pointcut parser.
 */
private static PointcutParser initializePointcutParser(ClassLoader cl) {
        PointcutParser parser = PointcutParser
                .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
                        SUPPORTED_PRIMITIVES, cl);
        return parser;
}

你們能夠看到,要得到PointcutParser的實例,只須要調用其一個靜態方法,這個靜態方法雖然很長,但仍是很好讀的,讀完基本知道方法啥意思了:獲取一個利用指定classloader、支持指定的原語集合的切點解析器。

###參數1:SUPPORTED_PRIMITIVES

咱們定義了一個集合,集合裏塞了一堆集合,這些集合是什麼呢?我簡單摘抄了幾個:

位於org.aspectj.weaver.tools.PointcutPrimitive類:

public static final PointcutPrimitive CALL = new PointcutPrimitive("call",1);
public static final PointcutPrimitive EXECUTION = new PointcutPrimitive("execution",2);
public static final PointcutPrimitive GET = new PointcutPrimitive("get",3);
public static final PointcutPrimitive SET = new PointcutPrimitive("set",4);
public static final PointcutPrimitive INITIALIZATION = new PointcutPrimitive("initialization",5);

其實,這些就是表明了切點中的一些語法原語,SUPPORTED_PRIMITIVES這個集合,就是加了一堆原語,從SUPPORTED_PRIMITIVES的名字能夠看出,就是說:我支持解析哪些切點。

###參數2:ClassLoader cl

你們知道,切點表達式裏是以下結構:public/private 返回值 包名.類名.方法名(參數...);這裏面的類名部分,若是明確指定了,是須要去加載這個class的。這個cl就是用於加載切點中的類型部分。

原註釋以下:

* When resolving types in pointcut expressions, the given classloader is used to find types.

這裏有個比較有意思的部分,在生成的PointcutParser實例中,是怎麼保存這個classloader的呢?

private WeakClassLoaderReference classLoaderReference;
/**
 * Set the classloader that this parser should use for type resolution.
 * 
 * @param aLoader
 */
protected void setClassLoader(ClassLoader aLoader) {
   this.classLoaderReference = new WeakClassLoaderReference(aLoader);
   world = new ReflectionWorld(this.classLoaderReference.getClassLoader());
}

能夠看到,進來的classloader,做爲構造器參數,new了一個WeakClassLoaderReference實例。

public class WeakClassLoaderReference{

   protected final int hashcode;
   //1. 重點關注處
   private final WeakReference loaderRef;

   public WeakClassLoaderReference(ClassLoader loader) {
      loaderRef = new WeakReference(loader);
      if(loader == null){
         // Bug: 363962 
         // Check that ClassLoader is not null, for instance when loaded from BootStrapClassLoader
         hashcode = System.identityHashCode(this);
      }else{
         hashcode = loader.hashCode() * 37;
      }
   }

   public ClassLoader getClassLoader() {
      ClassLoader instance = (ClassLoader) loaderRef.get();
      // Assert instance!=null
      return instance;
   }

}

上面的講解點1,你們看到,使用了弱引用來保存,我說下緣由,主要是爲了不在應用上層已經銷燬了該classloader加載的全部實例、全部Class,準備回收該classloader的時候,卻由於PointcutParser長期持有該classloader的引用,致使無法垃圾回收。

使用切點解析器,解析切點表達式

/**
 * Build the underlying AspectJ pointcut expression.
 */
private static PointcutExpression buildPointcutExpression(ClassLoader classLoader, String expression) {
    PointcutParser parser = initializePointcutParser(classLoader);
	// 講解點1
    return parser.parsePointcutExpression(expression);
}

講解點1,就是目前所在位置。咱們拿到切點表達式後,利用parser.parsePointcutExpression(expression)解析,返回的對象爲PointcutExpression類型。

測試

public static void main(String[] args) throws NoSuchMethodException {
		boolean b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Performer.class);
        System.out.println(b);


        b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Main.class);
        System.out.println(b);

        b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Perform.class);
        System.out.println(b);

}


    /**
     * 測試class匹配
     * @param expression
     * @param clazzToBeTest
     * @return
     */
    public static boolean testClassMatchExpression(String expression, Class<?> clazzToBeTest) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        PointcutExpression pointcutExpression = buildPointcutExpression(classLoader, expression);
        boolean b = pointcutExpression.couldMatchJoinPointsInType(clazzToBeTest);
        return b;
    }

輸出以下:

true Performer實現了Perform接口,全部匹配 false Main類,固然不能匹配 true 徹底匹配

說完了class匹配,下面咱們看看怎麼實現方法匹配。

AspectJ如何比較切點是否匹配目標方法

方法匹配的代碼也很簡單,以下:

public static void main(String[] args) throws NoSuchMethodException {
    boolean b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Performer.class);
    System.out.println(b);


    b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Main.class);
    System.out.println(b);

    b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Perform.class);
    System.out.println(b);

    Method sing = Perform.class.getMethod("sing");
    b = testMethodMatchExpression("execution(public * *.*.sing(..))",sing);
    System.out.println(b);
}

	/**
     * 測試方法匹配
     * @param expression
     * @return
     */
    public static boolean testMethodMatchExpression(String expression, Method targetMethod) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        PointcutExpression pointcutExpression = buildPointcutExpression(classLoader, expression);
        ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(targetMethod);
        if (shadowMatch.alwaysMatches()) {
            return true;
        } else if (shadowMatch.neverMatches()) {
            return false;
        } else if (shadowMatch.maybeMatches()) {
            System.out.println("可能匹配");
        }

        return false;
    }

主要是這個方法:

ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(targetMethod);

返回的shadowMatch類型實例,這個是個接口,專門用來表示:切點匹配後的結果。其註釋以下:

/** * The result of asking a PointcutExpression to match at a shadow (method execution, * handler, constructor call, and so on). * */

其有以下幾個方法:

public interface ShadowMatch {

   /**
    * True iff the pointcut expression will match any join point at this
    * shadow (for example, any call to the given method).
    */
   boolean alwaysMatches();
   
   /**
    * True if the pointcut expression may match some join points at this
    * shadow (for example, some calls to the given method may match, depending
    * on the type of the caller).
    * <p>If alwaysMatches is true, then maybeMatches is always true.</p>
    */
   boolean maybeMatches();
   
   /**
    * True iff the pointcut expression can never match any join point at this
    * shadow (for example, the pointcut will never match a call to the given
    * method).
    */
   boolean neverMatches();
   
   ...
}

這個接口就是告訴你,匹配了切點後,你能夠找它拿結果,結果多是:老是匹配;老是不匹配;可能匹配。

什麼狀況下,會返回可能匹配,我目前還沒試驗出來。

我跟過AspectJ的代碼,發現解析處主要在如下方法:

org.aspectj.weaver.patterns.SignaturePattern#matchesExactlyMethod

有興趣的小夥伴能夠看下,方法很長,如下只是一部分。

private FuzzyBoolean matchesExactlyMethod(JoinPointSignature aMethod, World world, boolean subjectMatch) {
		if (parametersCannotMatch(aMethod)) {
			// System.err.println("Parameter types pattern " + parameterTypes + " pcount: " + aMethod.getParameterTypes().length);
			return FuzzyBoolean.NO;
		}
		// OPTIMIZE only for exact match do the pattern match now? Otherwise defer it until other fast checks complete?
		if (!name.matches(aMethod.getName())) {
			return FuzzyBoolean.NO;
		}
		// Check the throws pattern
		if (subjectMatch && !throwsPattern.matches(aMethod.getExceptions(), world)) {
			return FuzzyBoolean.NO;
		}

		// '*' trivially matches everything, no need to check further
		if (!declaringType.isStar()) {
			if (!declaringType.matchesStatically(aMethod.getDeclaringType().resolve(world))) {
				return FuzzyBoolean.MAYBE;
			}
		}
    ...
}

這兩部分,代碼就講到這裏了。個人demo源碼在:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/aspectj-pointcut-matcher-demo

Spring aop如何依賴AspectJ

前面爲何要講AspectJ如何進行切點匹配呢?

由於,就我所知的,就有好幾處Spring Aop依賴AspectJ的例子:

  1. spring 實現的ltw,org.springframework.context.weaving.AspectJWeavingEnabler裏面依賴了org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter,這個是ltw的範疇,和今天的講解其實關係不大,有興趣能夠去翻本系列的ltw相關的幾篇;

  2. org.springframework.aop.aspectj.AspectJExpressionPointcut,這個是重頭,目前的spring aop,咱們寫的切點表達式,最後就是在內部用該數據結構來保存;

  3. 你們若是仔細看ComponentScan註解,裏面有個filter字段,可讓你自定義要掃描哪些類,filter有個類型字段,分別有以下幾種枚舉值:

    /**
    	 * Specifies which types are eligible for component scanning.
    	 */
    	Filter[] includeFilters() default {};
    
    	/**
    	 * Specifies which types are not eligible for component scanning.
    	 * @see #resourcePattern
    	 */
    	Filter[] excludeFilters() default {};
    
    	/**
    	 * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
    	 * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
    	 */
    	@Retention(RetentionPolicy.RUNTIME)
    	@Target({})
    	@interface Filter {
    
    		/**
    		 * The type of filter to use.
    		 * <p>Default is {@link FilterType#ANNOTATION}.
    		 * @see #classes
    		 * @see #pattern
    		 */
            // 講解點1
    		FilterType type() default FilterType.ANNOTATION;
    
    		...
    		/**
    		 * The pattern (or patterns) to use for the filter, as an alternative
    		 * to specifying a Class {@link #value}.
    		 * <p>If {@link #type} is set to {@link FilterType#ASPECTJ ASPECTJ},
    		 * this is an AspectJ type pattern expression. If {@link #type} is
    		 * set to {@link FilterType#REGEX REGEX}, this is a regex pattern
    		 * for the fully-qualified class names to match.
    		 * @see #type
    		 * @see #classes
    		 */
    		String[] pattern() default {};
    
    	}

    其中,講解點1,能夠看到,裏面默認是ANNOTATION類型,實際還有其餘類型;

    講解點2,若是type選擇ASPECTJ,則這裏寫AspectJ語法的切點表達式便可。

    public enum FilterType {
    
       /**
        * Filter candidates marked with a given annotation.
        * @see org.springframework.core.type.filter.AnnotationTypeFilter
        */
       ANNOTATION,
    
       /**
        * Filter candidates assignable to a given type.
        * @see org.springframework.core.type.filter.AssignableTypeFilter
        */
       ASSIGNABLE_TYPE,
    
       /**
        * 講解點1
        * Filter candidates matching a given AspectJ type pattern expression.
        * @see org.springframework.core.type.filter.AspectJTypeFilter
        */
       ASPECTJ,
    
       /**
        * Filter candidates matching a given regex pattern.
        * @see org.springframework.core.type.filter.RegexPatternTypeFilter
        */
       REGEX,
    
       /** Filter candidates using a given custom
        * {@link org.springframework.core.type.filter.TypeFilter} implementation.
        */
       CUSTOM
    
    }

縱觀以上幾點,能夠發現,Spring Aop集成AspectJ,只是把切點這一套語法、@Aspect這類註解、切點的解析,都直接使用AspectJ的,沒有本身另起爐竈。可是核心呢,是沒有使用AspectJ的編譯期注入和ltw的。

下面咱們仔細講解,上面的第二點,這也是最重要的一點。

Spring Aop是在實現aop時(上面第二點),如何集成AspectJ

這裏不會講aop的實現流程,你們能夠去翻前面幾篇,從這篇往下的幾篇。

曹工說Spring Boot源碼(16)-- Spring從xml文件裏到底獲得了什麼(aop:config完整解析【上】)

##解析xml或註解,獲取AspectJExpressionPointcut

在aop解析xml或者@Aspect時,最終切點是用AspectJExpressionPointcut 類型來表示的,且被註冊到了ioc容器,後續能夠經過getBean直接獲取該切點

##AspectJAwareAdvisorAutoProxyCreator 後置處理器,判斷切點是否匹配,來生成代理

在AspectJAwareAdvisorAutoProxyCreator 這個BeanPostProcessor對target進行處理時,會先判斷該target是否須要生成代理,此時,就會使用到咱們前面講解的東西。

判斷該target是否匹配切點,若是匹配,則生成代理;不然不生成。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   ...
   // 獲取可以匹配該target bean的攔截器,即aspect切面
   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;
}

咱們主要看getAdvicesAndAdvisorsForBean:

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

protected List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) {
    	// 講解點1 
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		// 講解點2
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);

		return eligibleAdvisors;
	}

講解點1,獲取所有的切面集合;

講解點2,過濾出可以匹配target bean的切面集合

protected List<Advisor> findAdvisorsThatCanApply(
      List<Advisor> candidateAdvisors, Class beanClass, String beanName) {

   ProxyCreationContext.setCurrentProxiedBeanName(beanName);
   try {
      return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
   }
   finally {
      ProxyCreationContext.setCurrentProxiedBeanName(null);
   }
}
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
   if (candidateAdvisors.isEmpty()) {
      return candidateAdvisors;
   }
   
   for (Advisor candidate : candidateAdvisors) {
      // canApply就是判斷切面和target的class是否匹配
      if (canApply(candidate, clazz)) {
         eligibleAdvisors.add(candidate);
      }
   }
   return eligibleAdvisors;
}

因此,重點就來到了canApply方法:

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
   if (advisor instanceof PointcutAdvisor) {
      PointcutAdvisor pca = (PointcutAdvisor) advisor;
      //講解點1
      return canApply(pca.getPointcut(), targetClass);
   }
   else {
      // It doesn't have a pointcut so we assume it applies.
      return true;
   }
}

講解點1,就是首先pca.getPointcut()獲取了切點,而後調用了以下方法:

org.springframework.aop.support.AopUtils#canApply
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
   //講解點1
   if (!pc.getClassFilter().matches(targetClass)) {
      return false;
   }

   MethodMatcher methodMatcher = pc.getMethodMatcher();

   // 講解點2
   Set<Class> classes = new HashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
   classes.add(targetClass);
    
   for (Class<?> clazz : classes) {
      Method[] methods = clazz.getMethods();
      for (Method method : methods) {
         // 講解點3
         if (methodMatcher.matches(method, targetClass)) {
            return true;
         }
      }
   }

   return false;
}

這裏,其實就是使用Pointcut來匹配target class了。具體兩個過程:

  • 講解點1,使用PointCut的classFilter,直接過濾掉不匹配的target Class
  • 講解點2,這裏是獲取target類實現的全部接口
  • 講解點3,在2的基礎上,獲取每一個class的每一個method,判斷是否匹配切點

因此,匹配切點的工做,落在了

methodMatcher.matches(method, targetClass)

由於,AspectJExpressionPointcut 這個類,本身實現了MethodMatcher,因此,上面的methodMatcher.matches(method, targetClass)實現邏輯,其實就在:

org.springframework.aop.aspectj.AspectJExpressionPointcut#matches

咱們只要看它怎麼來實現matches方法便可。

public boolean matches(Method method, Class targetClass, boolean beanHasIntroductions) {
   checkReadyToMatch();
   Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
   ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);

   if (shadowMatch.alwaysMatches()) {
      return true;
   }
   else if (shadowMatch.neverMatches()) {
      return false;
   }
   else {
      // the maybe case
      return (beanHasIntroductions || matchesIgnoringSubtypes(shadowMatch) || matchesTarget(shadowMatch, targetClass));
   }
}


	private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
         // 講解點1
		ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod);
		if (shadowMatch == null) {
			synchronized (this.shadowMatchCache) {
				// Not found - now check again with full lock...
				Method methodToMatch = targetMethod;
				shadowMatch = this.shadowMatchCache.get(methodToMatch);
				if (shadowMatch == null) {
                      // 講解點2
					shadowMatch = this.pointcutExpression.matchesMethodExecution(targetMethod);
					if (shadowMatch.maybeMatches() && fallbackPointcutExpression!=null) {
						shadowMatch = new DefensiveShadowMatch(shadowMatch,
								fallbackPointcutExpression.matchesMethodExecution(methodToMatch));
					}
                      //講解點3
					this.shadowMatchCache.put(targetMethod, shadowMatch);
				}
			}
		}
		return shadowMatch;
	}

這裏三個講解點。

  • 1,判斷是否有該method的結果緩存,沒有則,進入講解點2
  • 2,使用pointcutExpression.matchesMethodExecution(targetMethod)匹配,返回值爲shadowMatch,這個和咱們最前面講的AspectJ的切點匹配,已經串起來了。
  • 3,放進緩存,方便後續使用。

至於其pointcutExpression的生成,這個和AspectJ的相似,就不說了。

若是生成代理,對代理調用目標方法時,還會進行一次切點匹配

假設,通過上述步驟,咱們生成了代理,這裏假設爲jdk動態代理類型,其最終的動態代理對象的invocationHandler類以下:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler

其invoke方法內:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   MethodInvocation invocation;
   Object oldProxy = null;
   boolean setProxyContext = false;

   TargetSource targetSource = this.advised.targetSource;
   Class targetClass = null;
   Object target = null;
   ...
       
   try {

      Object retVal;
      target = targetSource.getTarget();
      // 講解點1
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

      // Check whether we have any advice. If we don't, we can fallback on direct
      // reflective invocation of the target, and avoid creating a MethodInvocation.
      if (chain.isEmpty()) {
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
      }
      else {
         // We need to create a method invocation...
         invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
         // Proceed to the joinpoint through the interceptor chain.
         retVal = invocation.proceed();
      }

      
      return retVal;
   }
}

咱們只關注講解點,這裏講解點1:獲取匹配目標方法和class的攔截器鏈。

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
      Advised config, Method method, Class targetClass) {

   List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
   boolean hasIntroductions = hasMatchingIntroductions(config, targetClass);
   AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
   for (Advisor advisor : config.getAdvisors()) {
      if (advisor instanceof PointcutAdvisor) {
         // Add it conditionally.
         PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
         // 講解點1
         if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
            MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
            //講解點2
            MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
            //講解點3
            if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
               if (mm.isRuntime()) {
                  ...
               }
               else {
                  interceptorList.addAll(Arrays.asList(interceptors));
               }
            }
         }
      }
   }
   return interceptorList;
}

三個講解點。

  • 1,判斷切點的classfilter是否不匹配目標class,若是是,直接跳過
  • 2,獲取切點的methodMatcher,這裏和前面講解的串起來了,最終拿到的就是AspectJExpressionPointcut
  • 3,判斷methodMatcher是否匹配目標method。由於前面已經緩存過了,因此這裏會很快。

總結

但願個人講解,讓你們看明白了,若有不明白之處,可留言,我會繼續改進。

總的來講,spring aop就是把aspectJ當個工具來用,切點語法、切點解析、還有你們經常使用的註解定義切面@Aspect、@Pointcut等等,都是aspectJ的:

org.aspectj.lang.annotation.Aspect

org.aspectj.lang.annotation.Pointcut。

原文出處:https://www.cnblogs.com/grey-wolf/p/12418425.html

相關文章
相關標籤/搜索