一、AOP的入口
上一節我們在分析解析AOP標籤的時候,第一步就是註冊了一個類AspectJAwareAdvisorAutoProxyCreator
,我們說它是AOP的入口類。為什麼這樣說呢?
來看它父類的父類AbstractAutoProxyCreator
,它繼承了BeanPostProcessor介面。
那麼,有兩個方法肯定要被呼叫到postProcessBeforeInitialization、postProcessAfterInitialization
。一個在依賴注入完成之前呼叫,一個在之後呼叫。
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
複製程式碼
wrapIfNecessary方法則是真正產生代理的地方,我們先看下它的內部實現。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// Create proxy if we have advice.
//先看它的註釋,大意說:如果有通知,就建立代理。
//其實就是在bean.getClass()找到所有的通知和advisor
//這裡面其實又分為兩個步驟:
//第一,在Bean工廠找到所有的Advisor 第二,根據beanClass和Pointcut去做匹配
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//真正建立代理並返回,這時候返回的就是代理類了,把真實的bean替換掉
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;
}
複製程式碼
看到上面的原始碼,整個過程就分為了兩步,匹配和建立返回。這樣就完成了代理類的替換,是不是有點偷樑換柱的感覺。
1、匹配
先是找到所有的Advisor,這個比較簡單。因為我們知道,在解析的時候就把配置的通知封裝成advisor註冊了進去。首先拿到beanDefinitionNames容器所有beanName,迴圈判斷bean的型別是不是advisor介面的型別,符合條件返回。
public List<Advisor> findAdvisorBeans() {
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = null;
//先拿到beanName
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
List<Advisor> advisors = new LinkedList<Advisor>();
for (String name : advisorNames) {
if (isEligibleBean(name)) {
try {
//再從Bean工廠中拿bean的例項
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
}
}
//返回的advisors就是配置檔案中所有的advice和advisor
return advisors;
}
複製程式碼
找到advisors並未結束,還要跟pointcut做匹配,看這些advisor符不符合表示式條件。它最終呼叫到Pointcut的match方法。這個方法巢狀的太深,就不貼程式碼了,核心思想就是判斷class和class對應的method是否與pointcut表示式匹配。
2、建立代理
經過上面查詢匹配後,確定當前的bean確實需要代理,就呼叫createProxy方法。
- 設定代理工廠
protected Object createProxy(Class<?> beanClass,
String beanName, Object[] specificInterceptors, TargetSource targetSource) {
//建立代理工廠
ProxyFactory proxyFactory = new ProxyFactory();
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
proxyFactory.copyFrom(this);
//將beanClass上的介面設定到代理工廠
evaluateProxyInterfaces(beanClass, proxyFactory);
//設定Advisor到代理工廠
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
}
//設定目標物件
proxyFactory.setTargetSource(targetSource);
return proxyFactory.getProxy(this.proxyClassLoader);
}
複製程式碼
- 建立
建立代理分為JDK的代理和Cglib的代理,這裡我們先關注JDK的代理。getProxy方法就呼叫到JdkDynamicAopProxy
類的方法。這個類還實現了InvocationHandler介面,說明它同時還是呼叫處理程式。即在呼叫代理類的invoke方法時,實際上就會呼叫到JdkDynamicAopProxy.invoke()
。
public Object getProxy(ClassLoader classLoader) {
//這裡又給代理工廠加了兩個介面 SpringProxy和Advised
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
//這個方法就比較熟悉了,正是JDK動態代理的方法
//這裡的this就是JdkDynamicAopProxy例項。
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
複製程式碼
二、代理類的呼叫
在例項化Bean和完成依賴注入後,會判斷當前的Bean是否需要代理,如果需要,就生成代理類把原始類替換掉。在業務方法裡面呼叫的時候,就會呼叫到JdkDynamicAopProxy.invoke()
。
在執行invoke的時候,我們又可以分為兩個步驟...-_-||,是不是跟二很有緣,每次都是兩個步驟。。
1、獲取方法的攔截鏈
首先從代理工廠中拿到所有的advisor,然後判斷是不是PointcutAdvisor型別,其次先matches一下targetClass,再matches一下method,證明這個類的方法在pointcut範圍內,加入interceptorList,最後返回。
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class<?> targetClass) {
List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
//config就是代理工廠的例項
for (Advisor advisor : config.getAdvisors()) {
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
return interceptorList;
}
複製程式碼
2、呼叫
拿到方法的攔截鏈,然後呼叫。呼叫的時候有個地方比較有意思。它先建立了一個物件ReflectiveMethodInvocation
。這個物件有兩個引數
currentInterceptorIndex //當前呼叫的攔截器的索引,預設值-1
interceptorsAndDynamicMethodMatchers //攔截器的列表
複製程式碼
然後看它的呼叫方法。
public Object proceed() throws Throwable {
//如果倆個變數相等,說明已經呼叫完了所有的攔截器
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
//執行被代理方法
return invokeJoinpoint();
}
//從-1開始,每次累加1。interceptorOrInterceptionAdvice就是對應的每一個通知
//比如before、after、after-returning...
//對應的例項類分別是:
//MethodBeforeAdviceInterceptor、AspectJAfterAdvice、AfterReturningAdviceInterceptor
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
//在呼叫具體通知的invoke方法時,把當前物件當引數傳了過去。
//因為在具體的通知執行之後還要回撥回來,執行當前物件的proceed().
//這樣就形成了一個通知呼叫鏈,當所有的通知執行完畢,呼叫上面的invokeJoinpoint()
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
複製程式碼
看完它的呼叫流程,我們還有一個疑問。通知一共有5種型別,前置通知、後置通知、方法返回後通知、環繞通知、異常通知。那麼,它是怎麼保證呼叫順序的呢? 我們來看兩個通知類裡面具體的實現。
前置通知
public Object invoke(MethodInvocation mi) throws Throwable {
//這個比較簡單,執行完before就回撥
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
return mi.proceed();
}
複製程式碼
後置通知
public Object invoke(MethodInvocation mi) throws Throwable {
//這個就比較有趣了,它先執行回撥
//通過finally機制再執行自己的通知方法
try {
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
複製程式碼
環繞通知
//環繞通知會呼叫到切面類的自定義方法
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("環繞通知之前");
//可以自己判斷還要不要回撥回去。
//回撥回去的話,接著迴圈呼叫鏈
//如果不再回撥,就要看鏈的順序了,在它之後的就不再執行
Object result = joinPoint.proceed();
System.out.println("環繞通知之後");
return result;
}
複製程式碼
三、基於註解的AOP
如果想使用註解AOP,需要開啟一個配置。<aop:aspectj-autoproxy />
。Spring解析配置標籤的時候,呼叫到AspectJAutoProxyBeanDefinitionParser.parse()
。
這個方法主要就幹了一件事:註冊入口類AnnotationAwareAspectJAutoProxyCreator
。
XML配置方式的AOP,Spring註冊的入口類叫做AspectJAwareAdvisorAutoProxyCreator
。
它們的呼叫流程是一樣的,都是在例項化之後呼叫爺爺類的AbstractAutoProxyCreator.postProcessAfterInitialization()
。回憶一下,wrapIfNecessary方法是真正產生代理的地方。它先獲取所有的通知並與當前的bean class匹配,如果有,就說明當前的Bean需要代理,則產生代理類。我們知道,在XML配置方式的AOP中,Spring把配置的通知都封裝成advisor註冊到容器裡,所以在獲取的時候,直接在Bean工廠中匹配Advisor型別的Bean就行。
但是,在解析註解AOP的時候,我們看到它只是註冊了一個入口類而已呀,並沒有註冊advisor,那麼,在這裡怎麼獲取呢?
目光回到查詢advisor的方法。
protected List<Advisor> findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
// 這個是查詢XML配置方式的advoisor
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
// 這個就是查詢註解方式的advisor
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
return advisors;
}
複製程式碼
在buildAspectJAdvisors
方法查詢advisor的時候,它大致可以分3個步驟。
1、從Bean工廠獲取所有的beanName
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
2、迴圈beanNames,判斷bean是否包含Aspect註解
for (String beanName : beanNames) {
Class<?> beanType = this.beanFactory.getType(beanName);
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
......
}
}
複製程式碼
3、獲取Advisors
先拿到Class物件上的所有Method物件,根據Method物件的Annotation型別,返回不同的Advice物件。這個跟XML方式返回的Advice物件是一樣的。
switch (aspectJAnnotation.getAnnotationType()) {
case AtBefore:
springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif);
break;
case AtAfter:
springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif);
break;
case AtAfterReturning:
springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
case AtAfterThrowing:
springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
case AtAround:
springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif);
break;
case AtPointcut:
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
return null;
default:
throw new UnsupportedOperationException(
"Unsupported advice type on method " + candidateAdviceMethod);
}
複製程式碼
最後將advice和pointcut封裝成InstantiationModelAwarePointcutAdvisorImpl物件返回。返回之後,建立代理類進行替換。
由此看來,註解方式的AOP,是在查詢Advisors的時候才去解析並生成的,其他的與XML的配置方式處理流程都是一樣的。
四、 總結
本章節我們重點闡述了3個問題。即怎樣產生代理類、代理類的執行過程、AnnotationAOP的處理流程。結合本章節和上一章節的內容,可能使我們對Spring AOP的內部處理流程加深了印象。
-
怎樣產生代理類? 通過迴圈beanNames例項化Bean物件,判斷此物件是否與pointcut表示式匹配。如果匹配就根據advice生成不同的advisor物件,然後呼叫JDK或者CGLIB的方法生成代理類返回。
-
代理類執行 呼叫JDK或者CGLIB的invoke方法,查詢advisor的呼叫鏈。鏈式呼叫,根據通知型別呼叫不同的advice實現增強。