寫在前面的話
相關背景及資源:
曹工說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。