Spring的IOC和AOP稱之爲Spring框架的兩個核心。AOP是什麼?AOP原理是什麼?本章節開始,咱們就來看看SpringAOP究竟是怎麼玩轉起來的?node
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。
瞭解到上面的知識後,咱們經過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刪除用戶信息------------
環繞通知以後
返回通知
後置通知
複製代碼
不知諸位能否還有印象,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是一個切面。切面裏面包含切入點和通知。引入類型先略過不表。
獲取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中。
剛纔在解析advice已經解析了pointcut,這裏又有一個呢?advice裏的pointcut是獨立使用的,只能做用於當前的advice。可是在aspect裏面也能夠單獨定義pointcut,能夠做用於全部的advice。解析過程是同樣的,再也不贅述。
advisor的解析
advisor能夠理解爲是隻有一個通知和一個切入點的切面。它的解析也比較簡單。建立一個DefaultBeanFactoryPointcutAdvisor類實例的BeanDefinition的對象,把通知的BeanName和Order放入propertyValues,再把這個BeanDefinition對象註冊到容器中。而後解析pointcut,過程同樣。