Spring原始碼分析(七)SpringAOP生成代理類及執行的過程

清幽之地發表於2019-03-03

一、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實現增強。

相關文章