目錄
Spring AOP中的基本概念:
SpringAOP入門案例:
Spring的五大通知類型:
SpringAOP的注解使用方式:
Spring AOP中的基本概念:
連接配接點(Joinpoint):在程式執行過程中某個特定的點,比如某方法調用的時候或者處理異常的時候。在Spring AOP中,一個連接配接點總是表示一個方法的執行。
解釋:
層于層之間調用的過程中,目标層中可供調用的方法,就稱之為連接配接點。
切入點(Pointcut):比對連接配接點的斷言。通知和一個切入點表達式關聯,并在滿足這個切入點的連接配接點上運作(例如,當執行某個特定名稱的方法時)。切入點表達式如何和連接配接點比對是AOP的核心:Spring預設使用AspectJ切入點文法。
解釋:
在連接配接點的基礎上 增加上切入規則 選擇出需要進行增強的切入點 這些基于切入規則選出來的連接配接點 就稱之為切入點。
切面(Aspect):一個關注點的子產品化,這個關注點可能會橫切多個對象。事務管理是J2EE應用中一個關于橫切關注點的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式來實作。
解釋:
狹義上就是 當spring攔截下切入點後 将這些切入點 交給 處理類 進行功能的增強,這個處理類就稱之為切面。
廣義上來講 将spring底層的代理 切入點 和 處理類 加在一起 實作的 對層與層之間調用過程進行增強的機制 稱之為切面。
通知(Advice):在切面的某個特定的連接配接點上執行的動作。其中包括了“around”、“before”和“after”等不同類型的通知(通知的類型将在後面部分進行讨論)。許多AOP架構(包括Spring)都是以攔截器做通知模型,并維護一個以連接配接點為中心的攔截器鍊。
解釋:
在spring底層的代理攔截下切入點後,将切入點交給切面類,切面類中就要有處理這些切入點的方法,這些方法就稱之為通知(也叫增強 增強方法)。針對于切入點執行的過程,通知還分為不同的類型,分别關注切入點在執行過程中的不同的時機。
目标對象(Target Object): 被一個或者多個切面所通知的對象。也被稱做被通知(advised)對象。 既然Spring AOP是通過運作時代理實作的,這個對象永遠是一個被代理(proxied)對象。
解釋:
就是真正希望被通路到的對象。spring底層的動态代理對他進行了代理,具體能不能真的通路到目标對象,或在目标對象真正執行之前和之後是否做一些額外的操作,取決于切面。
切入規則:制定一系列規則,進行連接配接點的篩選,篩選出切入點。
SpringAOP入門案例:
建立切面類,并定義切面方法(通知):
@Component
public class Aspect {
public void before(){
System.out.println("FBB經紀人打開了話筒");
}
}
在applicationContext.xml檔案中配置切面:
<!-- 配置 切入點和切入規則-->
<aop:config>
<!-- expression: 配置切入規則 id:定義切入點名稱-->
<aop:pointcut expression="within(com.spring.aop.Fbb)" id="f"/>
<!-- 配置切面 ref:切面類的bean id-->
<aop:aspect ref="aspect">
<!-- method:切面類中方法的名稱 pointcut-ref:需要綁定的切入點-->
<aop:before method="before" pointcut-ref="f"/>
</aop:aspect>
</aop:config>
擷取被調用者bean,執行相應的方法:
@Service
public class Fbb {
public void song(int x){
System.out.println("FBB在唱歌!!"+x);
}
}
@Test
public void test1(){
ApplicationContext context=new ClassPathXmlApplicationContext("applictionContext.xml");
Fbb f=(Fbb)context.getBean("fbb");
f.song(1);
}
輸出結果:
FBB經紀人打開了話筒
FBB在唱歌!!1
在入門案例中使用到了切入點表達式:--->詳細會在後面章節仔細介紹
目前先知道within()為通過全路徑名進行比對包,和包中的類,是其進行AOP。
SpringAOP的原理(建議了解代理模式,可先行檢視本部落格代理模式介紹):
Spring會在使用者擷取對象時,生成目标對象的代理對象,之後根據切入點規則,比對使用者連接配接點,得到切入點,當切入點被調用時,不會直接去找目标對象,而是通過代理對象攔截之後交由切面中的指定的通知執行來進行攔截增強。
Spring生成代理對象規則,預設情況下,如果目标對象實作過接口,則采用java的動态代理,如果目标對象沒有實作過接口,則采用JDK動态代理。開發者可以在Spring中進行配置,要求無論目标對象是否實作過接口,都強制使用cglib動态代理。
配置方式:<aop:config proxy-target-class="true"></aop:config>
Spring的五大通知類型:
前置通知
在目标方法執行之前執行的通知:
前置通知方法,可以沒有參數,也可以額外接受一個JoinPoint,spring會自動将給對象傳入,代表目前的連接配接點,通過該對象可以擷取目标對象和目标方法相關的資訊。
<!-- 配置 切入點和切入規則-->
<aop:config>
<!-- expression: 配置切入規則 id:定義切入點名稱-->
<aop:pointcut expression="within(com.spring.aop.Fbb)" id="f"/>
<!-- 配置切面 ref:切面類的bean id-->
<aop:aspect ref="aspect">
<!-- method:切面類中方法的名稱 pointcut-ref:需要綁定的切入點-->
<aop:before method="before" pointcut-ref="f"/>
</aop:aspect>
</aop:config>
//前置通知
//JoinPoint連接配接點對象
public void before(JoinPoint jp){
System.out.println("FBB經紀人打開了話筒,前置通知");
//擷取執行方法名
String name=jp.getSignature().getName();
//擷取執行類資訊
Class class1 = jp.getTarget().getClass();
System.out.println("...."+name+","+class1);
}
注:如果接受JoinPoint,必須保證其為方法的第一個參數,否則報錯
環繞通知:
在目标方法執行之前和之後都可以執行額外代碼的通知。
在環繞通知中必須顯式的調用目标方法,目标方法才會執行,這個顯式調用時通過ProceedingJoinPoint來實作的,可以在環繞通知中接收一個此類型的形參,spring容器會自動将該對象傳入,注意這個參數必須處在環繞通知的第一個形參位置。
**要注意,隻有環繞通知可以接收ProceedingJoinPoint,而其他通知隻能接收JoinPoint。
環繞通知需要傳回傳回值,否則真正調用者将拿不到傳回值,隻能得到一個null。
<aop:around method="around" pointcut-ref="f"/>
//環繞通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("FBB經紀人将電話遞給巧巧..around");
//擷取目标方法的參數集合
Object[] args = pjp.getArgs();
System.out.println("參數:"+Arrays.toString(args));
Object o=pjp.proceed();
System.out.println("放下了話筒!around...");
return o;
}
環繞通知有 :
控制目标方法是否執行、有控制是否傳回值、甚至改變傳回值的能力
環繞通知雖然有這樣的能力,但一定要慎用,不是技術上不可行,而是要小心不要破壞了軟體分層的“高内聚 低耦合”的目标。
後置通知
在目标方法執行之後執行的通知。
在後置通知中也可以選擇性的接收一個JoinPoint來擷取連接配接點的額外資訊,但是這個參數必須處在參數清單的第一個。
在後置通知中,還可以通過配置擷取傳回值
<aop:after-returning method="after" returning="msg" pointcut-ref="f"/>
//後置通知
public void after(JoinPoint jp,Object msg){
System.out.println(msg);
System.out.println("這個是後置通知");
}
異常通知
在目标方法抛出異常執行的通知,可以配置傳入就收目标方法抛出的異常對象
配置方法:
抛出異常:
public void song(int x){
System.out.println("FBB在唱歌!!"+x);
x=x/0;
}
<aop:after-throwing method="throwing" pointcut-ref="f" throwing="e"/>
//異常通知
public void throwing(JoinPoint jp,Throwable e){
System.out.println(e.getMessage());
}
最終通知
是在目标方法執行之後執行的通知。和後置通知不同之處在于,後置通知是在方法正常傳回後執行的通知,如果方法沒有正常傳回例如抛出異常,則後置通知不會執行。而最終通知無論如何都會在目标方法調用過後執行,即使目标方法沒有正常執行完成。另外後置通知可以通過配置得到傳回值,而最終通知無法得到。
最終通知也可以額外接收一個JoinPoint參數,來擷取目标對象和目标方法相關資訊,但一定要保證必須是第一個參數。
<aop:after method="afterz" pointcut-ref="f"/>
//最終通知
public void afterz(JoinPoint jp){
System.out.println("最終通知。。。。");
}
以上五種通知的執行順序
1.在目标方法沒有抛出異常的情況下:
前置通知
環繞通知的調用目标方法之前的代碼
目标方法
環繞通知的調用目标方法之後的代碼
後置通知
最終通知
2.在目标方法抛出異常的情況下:
前置通知
環繞通知的調用目标方法之前的代碼
目标方法 抛出異常 異常通知
最終通知
3.如果存在多個切面:
多切面執行時,采用了責任鍊設計模式。
切面的配置順序決定了切面的執行順序,多個切面執行的過程,類似于方法調用的過程,在環繞通知的proceed()執行時,去執行下一個切面或如果沒有下一個切面執行目标方法,進而達成了如下的執行過程:
如果目标方法抛出異常:
五種通知的常見使用場景:
前置通知 | 記錄日志(方法将被調用) |
環繞通知 | 控制事務 權限控制 |
後置通知 | 記錄日志(方法已經成功調用) |
異常通知 | 異常處理 控制事務 |
最終通知 | 記錄日志(方法已經調用,但不一定成功) |
SpringAOP的注解使用方式:
Spring也支援注解實作AOP,使實作方式更加簡單:
<!-- 開啟aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
使用@Aspect标志該類是一個切面類。
利用注釋配置相應的切入點規則:
- 前置通知 @Before
- 環繞通知@Around
- 後置通知@AfterReturning
- 異常通知@AfterThrowing
- 最終通知@After
如果一個切面中多個通知使用同一個切入點表達式,則可以将該切入點表達式單獨定義,來調用。
注:在目前切面中通過注解定義的切入點隻在目前切面中器作用,器它切面看不到。
在後置通知的注解中,也可以額外配置一個returning屬性,來指定一個參數名接受目标方法執行後的傳回值
相同在異常通知的注解中也可以接受異常對象的名字
舉例:
public class Aspect1 {
//配置切入點方法
@Pointcut("within(com.spring.aop.Fbb)")
public void pointcut(){}
//前置通知
//JoinPoint連接配接點對象
@Before("pointcut()")
public void before(JoinPoint jp){
System.out.println("FBB經紀人打開了話筒,前置通知");
//擷取執行方法名
String name=jp.getSignature().getName();
//擷取執行類資訊
Class class1 = jp.getTarget().getClass();
System.out.println("...."+name+","+class1);
}
//環繞通知
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("FBB經紀人将電話遞給巧巧..around");
//擷取目标方法的參數集合
Object[] args = pjp.getArgs();
System.out.println("參數:"+Arrays.toString(args));
Object o=pjp.proceed();
System.out.println("放下了話筒!around...");
return o;
}
//後置通知
@AfterReturning(value="pointcut()",returning="msg")
public void after(JoinPoint jp,Object msg){
System.out.println(msg);
System.out.println("這個是後置通知");
}
//異常通知
@AfterThrowing(value="pointcut()",throwing="e")
public void throwing(JoinPoint jp,Throwable e){
System.out.println(e.getMessage());
}
//最終通知
public void afterz(JoinPoint jp){
System.out.println("最終通知。。。。");
}
}