AOP(面向切面編程)
概念
把多個業務流程的公共部分抽取成一個獨立的切面,進行統一管理,在合適的時機橫向的把切面切入到業務流程的指定位置當中。
舉例說明
在一些項目當中,有很多的業務流程都要用戶登錄之後才能使用。
使用OOP想要解決就是把登錄驗證模塊插入到每一個業務流程當中,並且每一個業務流程需要的登錄驗證還可能是不統一的。
使用AOP只需要把登錄驗證模塊單獨抽取出來統一管理,在需要用到的位置聲明需要用到登錄驗證模塊,把登錄驗證的切面插入到這一切點當中。
AOP術語
- 切面:橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象,即把公共功能獨立出來的特殊對象
- 通知:切面需要完成的工作,即想要實現的功能,切面裏的每一個方法都稱作爲一個通知
- 連接點:程序執行的某個位置,可以認爲是Spring允許使用通知的地方,如方法執行前、執行後、拋出異常
- 切點:AOP 通過切點定位到特定的連接點。類比連接點相當於數據庫中的記錄,切點相當於查詢條件。切點和連接點不是一對一的關係,一個切點匹配多個連接點
- 代理:向目標對象通知後生成的對象
基於註解
使用條件
Spring本身也有自己的AOP,但是普遍都在使用AspectJ,反正基本上都在用。
-
導入Spring-AOP及AspectJ依賴jar包
略
-
xml配置文件當中引入aop命名空間
-
在
ApplicationContext.xml
當中加入<aop:aspcetj-autoproxy>
自動代理 -
每一個切面都是一個IoC容器當中的bean, 都需要用``@Component`等註解標註裝配到容器當中
聲明切面
@Aspect @Component public class TestAspect{ // }
通知
上文當中已經提到了通知的含義,需要知道所有的通知都是在切面對象當中的方法
在除環繞通知外的所有通知當中都可以指定一個JoinPoint對象用來獲得目標方法的信息(非必須),而環繞通知必須包含ProceedingJoinPoint參數,如果不包含這個參數將無法調用目標方法,Object爲返回值
前置通知
前置通知使用``@Before註解,
@Before中的參數爲切點表達式,**可以使用
*`佔位符**
@Before("execution(* com.nond.demo01.Calc.*(int,int))")
public void beforeAdvice(JoinPoint point) {
System.out.println("Method:"+point.getSignature().getName()+" begin,With:"+Arrays.asList(point.getArgs()));
}
後置通知
需要注意後置通知是無論代碼是否拋出異常都會執行後置通知的代碼,也即後置通知是在連接點完成的會後執行的,但是在我實際的測試當中,後置通知的打印語句是先於返回通知和異常通知執行的
前置通知可以獲取到方法的入參值,但是後置通知不能獲取到該方法執行完後的結果,需要在返回通知裏面去獲取結果值
@After("execution(* com.nond.demo01.Calc.*(int,int))")
public void afterAdvice(JoinPoint point) {
System.out.println("Method:"+point.getSignature().getName()+" end,With:"+Arrays.asList(point.getArgs()));
}
返回通知
在返回通知中,註解需要指定兩個參數
- value:切點表達式
- returning:目標方法的返回值名稱,指定該名稱之後可以在通知方法的參數列表當中設置相同名稱的參數。該參數的值就是目標方法的返回值
@AfterReturning(value="execution(* com.nond.demo01.Calc.*(int,int))",returning="result")
public void returningAdvice(JoinPoint point,Object result) {
System.out.println("Method:"+point.getSignature().getName()+" returning,With:"+Arrays.asList(point.getArgs())+",Result="+result);
}
異常通知
在異常通知中,註解需要指定兩個參數
- value:切點表達式
- throwing:在目標方法當中捕捉到的異常對象,指定該名稱之後可以在通知方法的參數列表當中設置相同名稱的參數。該參數就是捕捉到的異常對象
@AfterThrowing(value="execution(* com.nond.demo01.Calc.*(int,int))",throwing="e")
public void throwingAdvice(JoinPoint point,Throwable e) {
System.out.println("Method:"+point.getSignature().getName()+" returning,With:"+Arrays.asList(point.getArgs())+",Throwing="+t);
}
環繞通知
- 環繞通知用
@Around
註解標註,註解內的參數爲切點表達式 - catch捕捉
Throwable
,即point.proceed()
拋出java.lang.Throwable
- 環繞通知就是一套完整的動態代理,在環繞通知內能夠加入前置通知、後置通知等所有的通知
@Around("execution(...)")
public Object arroundAdvice(ProceedingJoinPoint point)throws Throwable{
Object result = null;
try{
System.out.println("前置通知");
result = point.proceed();
System.out.println("返回通知,返回值:"+result);
}catch(Throwable e){
System.out.println("異常通知,異常:"+e);
throw e;
}
System.out.println("後置通知");
return result;
}
切面優先級
當有多個切面同時作用於同一個連接點時,如果沒有指定切面的優先級會出現優先級混亂的情況,即哪一個切面優先是不確定的
可以使用兩種方法給切面指定優先級
- 實現Ordered接口,其中
getOrder()
方法返回值越小則切片的優先級越高 - 使用
@Ordered
,指定優先級,參數越小則切片優先級越高
切點表達式複用
在一個切面當中多個通知共同使用一個切點時,可以吧切點表達式單獨抽取出來複用
@Aspect
@Component
public class TestAspect{
@PointCut("execution(* com.nond.demo01.Calc.*(int,int)))
public publicPointCut(){}
@Before("publicPointCut()")
public beforeAdvice(JoinPoint point){
...
}
@After("publicPointCut()")
public afterAdvice(JoinPoint point){
...
}
}
基於XML
使用XML配置AOP的思想與使用註解配合基本相同
使用註解方式需要配置的內容在xml方式都要配置
裝配切面bean
<!--切面bean-->
<bean name="loggerAspect" class="com.nond.demo01.LoggerAspect"</bean>
<!--目標對象-->
<bean name="calc" class="com.nond.demo01.Calc"></bean>
配置aop:cofig
在<aop:config>
內配置切點、連接點和通知
<aop:config>
<!--配置切點,指定切點表達式和id-->
<aop:pointcut expression="execution(* com.nond.demo01.Calc.*(int,int))" id="pointcut"></aop:pointcut>
<!--配置連接點及通知
其中使用<aop-aspect>的order屬性能夠指定相應切面的優先級
-->
<aop:aspect ref="loggerAspect" order="1">
<aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
<aop:after method="afterAdvice" pointcut-ref="pointcut"/>
<aop:after-returning method="returningAdvice" returning="result" pointcut-ref="pointcut"/>
<aop:after-throwing method="throwingAdvice" throwing="t" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>