1. 程式人生 > >曹工說Spring Boot原始碼(22)-- 你說我Spring Aop依賴AspectJ,我依賴它什麼了

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

# 寫在前面的話 相關背景及資源: [曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享](https://www.cnblogs.com/grey-wolf/p/12044199.html) [曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解](https://www.cnblogs.com/grey-wolf/p/12051957.html ) [曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下](https://www.cnblogs.com/grey-wolf/p/12070377.html) [曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?](https://www.cnblogs.com/grey-wolf/p/12078673.html) [曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean](https://www.cnblogs.com/grey-wolf/p/12093929.html) [曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的](https://www.cnblogs.com/grey-wolf/p/12114604.html ) [曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)](https://www.cnblogs.com/grey-wolf/p/12151809.html) [曹工說Spring Boot原始碼(8)-- Spring解析xml檔案,到底從中得到了什麼(util名稱空間)](https://www.cnblogs.com/grey-wolf/p/12158935.html) [曹工說Spring Boot原始碼(9)-- Spring解析xml檔案,到底從中得到了什麼(context名稱空間上)](https://www.cnblogs.com/grey-wolf/p/12189842.html) [曹工說Spring Boot原始碼(10)-- Spring解析xml檔案,到底從中得到了什麼(context:annotation-config 解析)](https://www.cnblogs.com/grey-wolf/p/12199334.html) [曹工說Spring Boot原始碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)](https://www.cnblogs.com/grey-wolf/p/12203743.html) [曹工說Spring Boot原始碼(12)-- Spring解析xml檔案,到底從中得到了什麼(context:component-scan完整解析)](https://www.cnblogs.com/grey-wolf/p/12214408.html) [曹工說Spring Boot原始碼(13)-- AspectJ的執行時織入(Load-Time-Weaving),基本內容是講清楚了(附原始碼)](https://www.cnblogs.com/grey-wolf/p/12228958.html) [曹工說Spring Boot原始碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎麼和Spring Instrumentation整合](https://www.cnblogs.com/grey-wolf/p/12283544.html) [曹工說Spring Boot原始碼(15)-- Spring從xml檔案裡到底得到了什麼(context:load-time-weaver 完整解析)](https://www.cnblogs.com/grey-wolf/p/12288391.html) [曹工說Spring Boot原始碼(16)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【上】)](https://www.cnblogs.com/grey-wolf/p/12314954.html) [曹工說Spring Boot原始碼(17)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【中】)](https://www.cnblogs.com/grey-wolf/p/12317612.html) [曹工說Spring Boot原始碼(18)-- Spring AOP原始碼分析三部曲,終於快講完了 (aop:config完整解析【下】)](https://www.cnblogs.com/grey-wolf/p/12322587.html) [曹工說Spring Boot原始碼(19)-- Spring 帶給我們的工具利器,建立代理不用愁(ProxyFactory)](https://www.cnblogs.com/grey-wolf/p/12359963.html) [曹工說Spring Boot原始碼(20)-- 碼網恢恢,疏而不漏,如何記錄Spring RedisTemplate每次操作日誌](https://www.cnblogs.com/grey-wolf/p/12375656.html) [曹工說Spring Boot原始碼(21)-- 為了讓大家理解Spring Aop利器ProxyFactory,我已經拼了](https://www.cnblogs.com/grey-wolf/p/12384356.html) [工程程式碼地址](https://gitee.com/ckl111/spring-boot-first-version-learn ) [思維導圖地址](https://www.processon.com/view/link/5deeefdee4b0e2c298aa5596) 工程結構圖: ![](https://img2018.cnblogs.com/blog/519126/201912/519126-20191215144930717-1919774390.png) # 概要 本講,主要講講,spring aop和aspectJ到底啥關係,如果說spring aop依賴aspectJ,那麼,到底是哪兒依賴它了? 得講證據啊,對不對? 其實,我可以先說下結論。spring aop是基於代理的,有介面的時候,就是基於jdk 動態代理,jdk動態代理是隻能對方法進行代理的,因為在Proxy.newInstance建立代理時,傳入的第三個引數為java.lang.reflect.InvocationHandler,該介面只有一個方法: ```java 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檔案,裡面清楚看到了 ```xml 4.0.0 org.springframework spring-aop 5.1.9.RELEASE Spring AOP ... org.springframework spring-beans 5.1.9.RELEASE compile org.springframework spring-core 5.1.9.RELEASE compile com.jamonapi jamon 2.81
compile true
org.apache.commons commons-pool2 2.6.0 compile true // 就是這裡 org.aspectj aspectjweaver 1.9.4 compile true
``` 所以,大家看到,spring aop依賴了aspectjweaver。到底為什麼依賴它,就是我們本節的主題。 在此之前,我們先簡單瞭解下AspectJ。 # AspectJ如何比較切點是否匹配目標Class 假設我有如下類: ```java package foo; public interface Perform { public void sing(); } ``` 然後,我們再用AspectJ的方式來定義一個切點: ```java execution(public * *.Perform.sing(..)) ``` 大家一看,肯定知道,這個切點是可以匹配這個Perform類的sing方法的,但是,如果讓你用程式實現呢?你怎麼做? 我聽說Spring最早的時候,是不依賴AspectJ的,自己寫正則來完成上面的判斷是否匹配切點的邏輯,但後來,不知道為啥,就變成了AspectJ了。 如果我們要用AspectJ來判斷,有幾步? ## 引入依賴 maven的pom裡,只需要引入如下依賴: ```xml org.aspectj
aspectjweaver 1.8.2
``` ## 定義切點解析器 ```java private static final Set SUPPORTED_PRIMITIVES = new HashSet(); 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 我們定義了一個集合,集合裡塞了一堆集合,這些集合是什麼呢?我簡單摘抄了幾個: ```java 位於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的呢? ```java 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例項。 ```java 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的引用,導致沒法垃圾回收。 ## 使用切點解析器,解析切點表示式 ```java /** * 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型別。 ## 測試 ```java 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如何比較切點是否匹配目標方法 方法匹配的程式碼也很簡單,如下: ```java 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). * */ 其有如下幾個方法: ```java 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). *

If alwaysMatches is true, then maybeMatches is always true.

*/ 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 有興趣的小夥伴可以看下,方法很長,以下只是一部分。 ```java 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原始碼在: # 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有個型別欄位,分別有如下幾種列舉值: ```java /** * 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. *

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}. *

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語法的切點表示式即可。 ```java 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完整解析【上】)](https://www.cnblogs.com/grey-wolf/p/12314954.html) ##解析xml或註解,獲取AspectJExpressionPointcut 在aop解析xml或者@Aspect時,最終切點是用AspectJExpressionPointcut 型別來表示的,且被註冊到了ioc容器,後續可以通過getBean直接獲取該切點 ##AspectJAwareAdvisorAutoProxyCreator 後置處理器,判斷切點是否匹配,來生成代理 在AspectJAwareAdvisorAutoProxyCreator 這個BeanPostProcessor對target進行處理時,會先判斷該target是否需要生成代理,此時,就會使用到我們前面講解的東西。 判斷該target是否匹配切點,如果匹配,則生成代理;否則不生成。 ```java 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: ```java @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 findEligibleAdvisors(Class beanClass, String beanName) { // 講解點1 List candidateAdvisors = findCandidateAdvisors(); // 講解點2 List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); return eligibleAdvisors; } ``` 講解點1,獲取全部的切面集合; 講解點2,過濾出能夠匹配target bean的切面集合 ```java protected List findAdvisorsThatCanApply( List candidateAdvisors, Class beanClass, String beanName) { ProxyCreationContext.setCurrentProxiedBeanName(beanName); try { return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); } finally { ProxyCreationContext.setCurrentProxiedBeanName(null); } } ``` ```java public static List findAdvisorsThatCanApply(List 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方法: ```java 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()獲取了切點,然後呼叫瞭如下方法: ```java 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 classes = new HashSet(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,判斷是否匹配切點 所以,匹配切點的工作,落在了 ```java methodMatcher.matches(method, targetClass) ``` 因為,AspectJExpressionPointcut 這個類,自己實現了MethodMatcher,所以,上面的`methodMatcher.matches(method, targetClass)`實現邏輯,其實就在: `org.springframework.aop.aspectj.AspectJExpressionPointcut#matches` ![](https://img2020.cnblogs.com/blog/519126/202003/519126-20200305085941786-46915195.png) 我們只要看它怎麼來實現matches方法即可。 ```java 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類如下: ```java final class JdkDynamicAopProxy implements AopProxy, InvocationHandler ``` 其invoke方法內: ```java 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 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的攔截器鏈。 ```java public List getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class targetClass) { List interceptorList = new ArrayList(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.Poi