Spring源碼分析(六)SpringAOP實例及標籤的解析

1、前言

Spring的IOC和AOP稱之爲Spring框架的兩個核心。AOP是什麼?AOP原理是什麼?本章節開始,咱們就來看看SpringAOP究竟是怎麼玩轉起來的?node

2、AOP是什麼?

一、定義

Aspect Oriented Programming,面向切面編程,是一種編程範例,旨在經過分離橫切關注點來增長模塊性,它經過在不修改代碼自己的狀況下向現有代碼添加其餘行爲來實現。動態的將代碼切入到類的指定方法或指定位置上的編程思想,就是面向切面編程。spring

二、使用

在系統中,確定存在一些公共邏輯模塊。好比日誌的記錄,事務的管理,請求的校驗等。若是把這種邏輯模塊的代碼收到寫到業務模塊中,代碼重複度就很是之高。這還不是惟一的問題,關鍵若是公共邏輯模塊的代碼要修改,必需要所有修改。這個根本不符合碼農的科學發展觀。AOP,能夠幫助咱們解決這些問題。express

三、實現

AOP自己並不能解決這些問題,AOP就是一種思想,而解決問題依靠的是AOP具體的實現,也就是咱們本章節所說的Spring AOP。不過,值得注意的是,在Spring2.0以後,開始集成aspectj。因此,咱們所說的Spring AOP,其實就是Spring加Aspectj這種方式。編程

四、概念性知識

要熟悉Spring AOP,裏面有些概念必定要先搞搞清楚才行。bash

  • Aspect 切面,將橫切關注點設計爲獨立可重用的對象,這些對象稱爲切面。實際上就是一些功能加強的類或者對象的表明,好比:日誌管理、事務管理、異常控制等。框架

  • Joinpoint 鏈接點,切面在應用程序執行時加入對象的業務流程中的特定點,稱爲鏈接點。它用來定義在目標程序的哪裏經過AOP加入新的邏輯。通俗講,就是對應的具體的被代理的方法 ,好比saveUser()。Joinpoint跟咱們具體的被代理的方法一一對應函數

  • Pointcut 切點,匹配鏈接點的斷言。通知和一個切入點表達式關聯,並在知足這個切入點的鏈接點上運行。它是joinpoint的集合。源碼分析

  • Advice 通知/加強,在切面的某個特定的鏈接點上執行的動做。能夠理解爲它是一段程序代碼,在代理類上的上面或者下面增長一些代碼來實現加強。好比事務管理AOP,通知/加強對應的就是開啓事務、關閉事務這些具體代碼上的操做。測試

  • Advisor Advice和Pointcut組成的獨立的單元,用來定義只有一個通知和一個切入點的切面。再通俗點來講,它是將Advice注入到程序中的Pointcut位置。Spring中的事務管理使用的就是advisor。ui

  • Introduction 引入,經過引入,能夠在一個對象中加入新的方法和屬性,而不用修改它的程序。這種方式不多用,基本也不太推薦用。本身定義的通知必需要實現MethodInterceptor。

3、實例

瞭解到上面的知識後,咱們經過XML的配置方式具體來看一下Spring AOP的應用。

首先,定義一個切面的類。

public class UserAspect {

	public void beforeAdvice() {
		System.out.println("前置通知");
	}
	public void afterAdvice() {
		System.out.println("後置通知");
	}
	public void afterReturnAdvice() {
		System.out.println("返回通知");
	}
	public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {	
		System.out.println("環繞通知以前");
		Object result = joinPoint.proceed();
		System.out.println("環繞通知以後");
		return result;
	}
}
複製代碼

其次,在Spring配置文件中先將這個類註冊成Bean。再經過AOP的標籤關聯到一塊兒。

<bean id="userAspect" class="com.viewscenes.netsupervisor.aspect.UserAspect"></bean>
	
<aop:config>
	<aop:aspect id="userAspect" ref="userAspect">
		<aop:pointcut id="userPointcut" expression="(execution(* com.viewscenes.netsupervisor.service..*.*(..)))" />
		<aop:before method="beforeAdvice" pointcut-ref="userPointcut"/>
		<aop:after method="afterAdvice"  pointcut-ref="userPointcut"/>
		<aop:after-returning method="afterReturnAdvice" pointcut-ref="userPointcut"/>
		<aop:around method="aroundAdvice" pointcut-ref="userPointcut"/> 
	</aop:aspect>
</aop:config>
複製代碼

最後,咱們經過調用UserService中的方法來測試一下。

前置通知
環繞通知以前
----------根據ID刪除用戶信息------------
環繞通知以後
返回通知
後置通知
複製代碼

4、XML標籤的解析

不知諸位能否還有印象,Spring是怎麼解析配置文件中的標籤的呢?若是不記得,能夠到Spring源碼分析(一)Spring的初始化和XML解析回顧一下。

這裏,咱們直接來到ConfigBeanDefinitionParser.parse()方法。它位於org.springframework.aop.config包。大概能夠分爲兩個步驟,註冊入口類和解析子節點。

一、註冊入口類

parse方法的開始就註冊了一個類,AspectJAwareAdvisorAutoProxyCreator。這個類至關重要,它是AOP的入口類。註冊的過程就是把它封裝成BeanDefinition對象,添加到beanDefinitionNames容器中。這個容器,咱們已經很熟悉了,就是循環它來進行實例化和依賴注入。

//cls就是AspectJAwareAdvisorAutoProxyCreator.class
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, 
								BeanDefinitionRegistry registry, Object source) {
	RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
	beanDefinition.setSource(source);
	beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
	beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
	
	//註冊beanDefinition 將beanName加入到beanDefinitionNames容器中
	registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
	return beanDefinition;
}
複製代碼

二、解析子節點

接下來是解析配置文件標籤的地方,獲取<aop:config>下的子標籤。它的子標籤只有三類:<aop:pointcut>、<aop:advisor>、<aop:aspect>。下面的源碼也正對應這三種類型。

List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts) {
	String localName = parserContext.getDelegate().getLocalName(elt);
	if (POINTCUT.equals(localName)) {
		parsePointcut(elt, parserContext);
	}
	else if (ADVISOR.equals(localName)) {
		parseAdvisor(elt, parserContext);
	}
	else if (ASPECT.equals(localName)) {
		parseAspect(elt, parserContext);
	}
}
複製代碼

pointcut的解析

pointcut解析其實很簡單,把id和expression拿到,封裝成BeanDefinition對象,它的類是AspectJExpressionPointcut,把表達式放入beanDefinition對象的propertyValues屬性,最後一樣是註冊到beanDefinitionNames容器中。

private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
	String id = pointcutElement.getAttribute(ID);
	String expression = pointcutElement.getAttribute(EXPRESSION);
	AbstractBeanDefinition pointcutDefinition = null;
	try {
		pointcutDefinition = createPointcutDefinition(expression);
		String pointcutBeanName = id;
		if (StringUtils.hasText(pointcutBeanName)) {
			//註冊到beanDefinitionNames容器,id爲beanName
			parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
		}
	}
	return pointcutDefinition;
}

protected AbstractBeanDefinition createPointcutDefinition(String expression) {
	RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
	beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
	beanDefinition.setSynthetic(true);
	beanDefinition.getPropertyValues().add(EXPRESSION, expression);
	return beanDefinition;
}
複製代碼

aspect的解析

aspect是一個切面。切面裏面包含切入點和通知。引入類型先略過不表。

  • advice

獲取aspect節點下的全部子節點,先過濾advice節點。而後解析生成AspectJPointcutAdvisor類的BeanDefinition對象。

//獲取aspect節點的子節點 
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++) {
	Node node = nodeList.item(i);
	//判斷是否是advice節點。
	if (isAdviceNode(node, parserContext)) {
		if (!adviceFoundAlready) {
			adviceFoundAlready = true;
			//aspectName就切面的ref,Bean的名字
			beanReferences.add(new RuntimeBeanReference(aspectName));
		}
		//解析advice 生成AspectJPointcutAdvisor類的BeanDefinition對象。
		AbstractBeanDefinition advisorDefinition = parseAdvice(
				aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
		beanDefinitions.add(advisorDefinition);
	}
}

private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
	String name = parserContext.getDelegate().getLocalName(aNode);
	return (BEFORE.equals(name) || AFTER.equals(name) || 
		AFTER_RETURNING_ELEMENT.equals(name) ||
		AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
}
複製代碼

parseAdvice方法註冊不少類,最後串聯到一塊來,一個一個來看。

首先,建立了方法工廠bean。註冊了MethodLocatingFactoryBean類,往propertyValues中添加了兩個屬性,targetBeanName切面的Bean、methodName通知的方法名。

RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
//aspectName切面類的Bean  methodName方法名稱 好比before
methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
methodDefinition.setSynthetic(true);
複製代碼

而後,建立實例工廠的定義。註冊了SimpleBeanFactoryAwareAspectInstanceFactory類,這個類實現了BeanFactoryAware接口。這樣的話,在實例化的時候會調用到setBeanFactory方法,能夠拿到BeanFactory。有個getAspectInstance方法,根據切面名字就能夠拿到切面類的實例。

//註冊SimpleBeanFactoryAwareAspectInstanceFactory實例的BeanDefinition
RootBeanDefinition aspectFactoryDef =
		new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);


//類的屬性和方法
public class SimpleBeanFactoryAwareAspectInstanceFactory implements 
								AspectInstanceFactory, BeanFactoryAware {
	
	private String aspectBeanName;
	private BeanFactory beanFactory;
	
	public void setAspectBeanName(String aspectBeanName) {
		this.aspectBeanName = aspectBeanName;
	}

	public void setBeanFactory(BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
		if (!StringUtils.hasText(this.aspectBeanName)) {
			throw new IllegalArgumentException("'aspectBeanName' is required");
		}
	}
	public Object getAspectInstance() {
		return this.beanFactory.getBean(this.aspectBeanName);
	}
}
複製代碼

其次,註冊切入點。它把上面這兩個BeanDefinition當作參數傳了過去,最後放入新建的BeanDefinition對象中。這個新建的BeanDefinition對象,是根據advice類型而建立的,固然了,也是五個類型,對應五個類的實例。下面還有三個步驟:設置propertyValues、解析advcie裏的pointcut屬性、設置bean的參數列表。

private AbstractBeanDefinition createAdviceDefinition(
	Element adviceElement, ParserContext parserContext, String aspectName, int order,
			RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
			List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

	//getAdviceClass 根據advice的類型建立不一樣類型的BeanDefinition
	//BEFORE前置通知 					AspectJMethodBeforeAdvice.class
	//AFTER後置通知 					AspectJAfterAdvice.class
	//AFTER_RETURNING_ELEMENT返回後通知 AspectJAfterReturningAdvice.class
	//AFTER_THROWING_ELEMENT異常通知  	AspectJAfterThrowingAdvice.class
	//AROUND環繞通知					AspectJAroundAdvice.class

	RootBeanDefinition adviceDefinition = new RootBeanDefinition(
							   getAdviceClass(adviceElement, parserContext));
	adviceDefinition.setSource(parserContext.extractSource(adviceElement));
	//一、設置propertyValues
	adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
	adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);

	//二、解析advcie裏的pointcut屬性
	//pointcut分爲兩種。一種是pointcut-ref引用類型,一種是pointcut表達式類型
	//若是是引用類型,返回字符串  
	//若是是表達式類型,則建立AspectJExpressionPointcut類型的Bean,將表達式放入propertyValues屬性。
	Object pointcut = parsePointcutProperty(adviceElement, parserContext);
	if (pointcut instanceof BeanDefinition) {
		cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
		beanDefinitions.add((BeanDefinition) pointcut);
	}
	else if (pointcut instanceof String) {
		RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
		cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
		beanReferences.add(pointcutRef);
	}

	//三、設置bean的參數列表。adviceDefinition對象有一個構造函數參數值,放入了三個屬性
	ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
	cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);
	cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
	cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);
        
	return adviceDefinition;
}
複製代碼

最後,配置advisor。建立AspectJPointcutAdvisor類實例的BeanDefinition對象,仍是那個構造函數參數值,把上一步返回的adviceDefinition當作參數放入genericArgumentValues。

RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
//構造函數參數值 adviceDef就是上一步返回的adviceDefinition
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
複製代碼

最後的最後,註冊advisorDefinition到容器中並返回。

parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
return advisorDefinition;
複製代碼

必定要記得,這一系列操做都是在循環體裏完成的。因此,有幾個通知的類型,就會生成幾個advisorDefinition對象。處理完,添加到循環體開頭定義的List中。

  • pointcut

剛纔在解析advice已經解析了pointcut,這裏又有一個呢?advice裏的pointcut是獨立使用的,只能做用於當前的advice。可是在aspect裏面也能夠單獨定義pointcut,能夠做用於全部的advice。解析過程是同樣的,再也不贅述。

advisor的解析

advisor能夠理解爲是隻有一個通知和一個切入點的切面。它的解析也比較簡單。建立一個DefaultBeanFactoryPointcutAdvisor類實例的BeanDefinition的對象,把通知的BeanName和Order放入propertyValues,再把這個BeanDefinition對象註冊到容器中。而後解析pointcut,過程同樣。

相關文章
相關標籤/搜索