曹工說Spring Boot源碼(22)– 你說我Spring Aop依賴AspectJ,我依賴它什麼了
- 2020 年 3 月 5 日
- 筆記
寫在前面的話
相關背景及資源:
曹工說Spring Boot源碼(1)– Bean Definition到底是什麼,附spring思維導圖分享
曹工說Spring Boot源碼(2)– Bean Definition到底是什麼,咱們對著介面,逐個方法講解
曹工說Spring Boot源碼(3)– 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下
曹工說Spring Boot源碼(4)– 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?
曹工說Spring Boot源碼(5)– 怎麼從properties文件讀取bean
曹工說Spring Boot源碼(6)– Spring怎麼從xml文件里解析bean的
曹工說Spring Boot源碼(7)– Spring解析xml文件,到底從中得到了什麼(上)
曹工說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源碼在:
Spring aop如何依賴AspectJ
前面為什麼要講AspectJ如何進行切點匹配呢?
因為,就我所知的,就有好幾處Spring Aop依賴AspectJ的例子:
-
spring 實現的ltw,org.springframework.context.weaving.AspectJWeavingEnabler裡面依賴了org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter,這個是ltw的範疇,和今天的講解其實關係不大,有興趣可以去翻本系列的ltw相關的幾篇;
-
org.springframework.aop.aspectj.AspectJExpressionPointcut,這個是重頭,目前的spring aop,我們寫的切點表達式,最後就是在內部用該數據結構來保存;
-
大家如果仔細看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。