核心邏輯
上文中提到了 AOP 建立代理等等的具體操作都是在 AnnotationAwareAspectAutoProxyCreator 類中來成的,通過上文的自動注冊,下面讓我們看 AnnotationAwareAspectAutoProxyCreator 是如何工作的,首先是 AnnotationAwareAspectAutoProxyCreator 的繼承關系圖:
然後是 AnnotationAwareAspectAutoProxyCreator 的層次結構圖:
這裡需要特别注意的是 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 的時序圖:
結合時序圖,我們知道真正建立代理地方是從 getAdvicesAndAdvisorsForBean 開始的。雖然看起來很簡單,但其實每一步都有大量複雜的邏輯。但在分析源碼前,我們必須先對增強以及其具體邏輯有所了解。
設計的基石
增強
Advice(也翻作 通知)定義了連接配接點做什麼,為切面增強提供了織入的接口。在 Spring AOP 中,它主要描述 Spring AOP 圍繞方法調用而注入的切面行為。Advice 是 AOP 聯盟定義的一個接口,具體的接口定義在 org.aopalliance.aop.Advice 中。在 Spring AOP 的實作中,使用了這個統一接口,并通過這個接口,為 AOP 切面增強的注入功能做了更多的細化和擴充,比如前面提到的具體通知類型,如BeforeAdvice、AfterAdvice、ThrowsAdvice等。作為 Spring AOP 定義的接口類,具體的切面增強可以通過這些接口內建到 AOP 架構中去發揮作用。對于這些接口類,下面是他的主要接口繼承圖:
下面是 Advice 接口繼承的層次圖:
看一個程式的具體設計思路沒有比看接口來的更直接的了,下面我們就從第一個 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 的層次結構圖:
下面是 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 接口的層次繼承圖:
這是一個 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)。