本文摘自pandonix的部落格。
此前對于aop的使用僅限于聲明式事務,除此之外在實際開發中也沒有遇到過與之相關的問題。最近項目中遇到了以下幾點需求,采用aop來解決。一方面是為了以更加靈活的方式來解決問題,另一方面是借此機會深入學習spring aop相關的内容。本文是權當本人的自己aop學習筆記,以下需求不用aop肯定也能解決,至于是否牽強附會,仁者見仁智者見智。
面對需求:
①對部分函數的調用進行日志記錄,用于觀察特定問題在運作過程中的函數調用情況。
②監控部分重要函數,若抛出指定的異常,需要以短信或郵件方式通知相關人員。
③監控部分重要函數的執行時間。
不采用aop解決過程中的問題:
①需要列印日志的函數分散在各個包中,隻能找到所有的函數體,手動添加日志。然而這些日志都是臨時的,待問題解決之後應該需要清除列印日志的代碼,隻能再次手動清除。
②類似1的情況,需要捕獲異常的地方太多,如果手動添加時想到很可能明天又要手動清除。該需求相對比較固定,屬于長期監控的範疇,并不需求臨時添加後再清除。然而,客戶某天要求,把其中20%的異常改為短信提醒,剩下的80%改用郵件提醒。兩天後,客戶抱怨短信太多,全部改成郵件提醒。
③該需求通常用于監控某些函數的執行時間,用以判斷系統執行慢的瓶頸所在。瓶頸被解決之後,煩惱同情況1。
下面我們采用aop來解決:
切面類testaspect
package com.spring.aop;
public class testaspect {
public void doafter(joinpoint jp) {
system.out.println("log ending method: "
+ jp.gettarget().getclass().getname() + "."
+ jp.getsignature().getname());
}
public object doaround(proceedingjoinpoint pjp) throws throwable {
long time = system.currenttimemillis();
object retval = pjp.proceed();
time = system.currenttimemillis() - time;
system.out.println("process time: " + time + " ms");
return retval;
public void dobefore(joinpoint jp) {
system.out.println("log begining method: "
public void dothrowing(joinpoint jp, throwable ex) {
system.out.println("method " + jp.gettarget().getclass().getname()
+ "." + jp.getsignature().getname() + " throw exception");
system.out.println(ex.getmessage());
private void sendex(string ex) {
//todo 發送短信或郵件提醒
}
接口a
package com.spring.service;
public interface aservice {
public void fooa(string _msg);
public void bara();
接口a的實作類
public class aserviceimpl implements aservice {
public void bara() {
system.out.println("aserviceimpl.bara()");
public void fooa(string _msg) {
system.out.println("aserviceimpl.fooa(msg:"+_msg+")");
service類b
public class bserviceimpl {
public void barb(string _msg, int _type) {
system.out.println("bserviceimpl.barb(msg:"+_msg+" type:"+_type+")");
if(_type == 1)
throw new illegalargumentexception("測試異常");
public void foob() {
system.out.println("bserviceimpl.foob()");
applicationcontext.xml
<?xml version="1.0" encoding="utf-8"?>
xsi:schemalocation="
default-autowire="autodetect">
<aop:config>
<aop:aspect id="testaspect" ref="aspectbean">
<!--配置com.spring.service包下所有類或接口的所有方法-->
<aop:pointcut id="businessservice"
expression="execution(* com.spring.service.*.*(..))" />
<aop:before pointcut-ref="businessservice" method="dobefore"/>
<aop:after pointcut-ref="businessservice" method="doafter"/>
<aop:around pointcut-ref="businessservice" method="doaround"/>
<aop:after-throwing pointcut-ref="businessservice" method="dothrowing" throwing="ex"/>
</aop:aspect>
</aop:config>
<bean id="aspectbean" class="com.spring.aop.testaspect" />
<bean id="aservice" class="com.spring.service.aserviceimpl"></bean>
<bean id="bservice" class="com.spring.service.bserviceimpl"></bean>
</beans>
測試類aoptest
public class aoptest extends abstractdependencyinjectionspringcontexttests {
private aservice aservice;
private bserviceimpl bservice;
protected string[] getconfiglocations() {
string[] configs = new string[] { "/applicationcontext.xml"};
return configs;
}
/**
* 測試正常調用
*/
public void testcall()
{
system.out.println("springtest junit test");
aservice.fooa("junit test fooa");
aservice.bara();
bservice.foob();
bservice.barb("junit test barb",0);
* 測試after-throwing
public void testthrow()
try {
bservice.barb("junit call barb",1);
}
catch (illegalargumentexception e) {
}
public void setaservice(aservice service) {
aservice = service;
public void setbservice(bserviceimpl service) {
bservice = service;
運作結果如下:
log begining method: com.spring.service.aserviceimpl.fooa
aserviceimpl.fooa(msg:junit test fooa)
log ending method: com.spring.service.aserviceimpl.fooa
process time: 0 ms
log begining method: com.spring.service.aserviceimpl.bara
aserviceimpl.bara()
log ending method: com.spring.service.aserviceimpl.bara
log begining method: com.spring.service.bserviceimpl.foob
bserviceimpl.foob()
log ending method: com.spring.service.bserviceimpl.foob
log begining method: com.spring.service.bserviceimpl.barb
bserviceimpl.barb(msg:junit test barb type:0)
log ending method: com.spring.service.bserviceimpl.barb
bserviceimpl.barb(msg:junit call barb type:1)
method com.spring.service.bserviceimpl.barb throw exception
測試異常
《spring參考手冊》中定義了以下幾個aop的重要概念,結合以上代碼分析如下:
切面aspect:官方的抽象定義為“一個關注點的子產品化,這個關注點可能會橫切多個對象”。例如aserviceimpl.bara()的調用就是切面testaspect所關注的行為之一。切面在applicationcontext中<aop:aspect>來配置。
連接配接點joinpoint:程式執行過程中的某一行為,例如aserviceimpl.bara()的調用或者bserviceimpl.barb(string _msg, int _type)抛出異常等行為。
通知advice:切面對于某個連接配接點所産生的動作,例如,testaspect中對com.spring.service包下所有類的方法進行日志記錄的動作就是一個advice。其中,一個“切面”可以包含多個advice,例如testaspect。
切入點pointcut:比對連接配接點的斷言,在aop中通知和一個切入點表達式關聯。例如testaspect中的所有通知所關注的連接配接點,都由切入點表達式execution(* com.spring.service.*.*(..))來決定。
目标對象target object:被一個或者多個切面所通知的對象。例如,aservcieimpl和bserviceimpl,當然在實際運作時,spring aop采用代理實作,實際aop操作的是targetobject的代理對象。
aop代理aop proxy在spring aop中有兩種代理方式,jdk動态代理和cglib代理。預設情況下targetobject實作了接口時,則采用jdk動态代理例如aserviceimpl。反之采用cglib代理,例如bserviceimpl。強制使用cglib代理需要将<aop:config>的proxy-target-class 屬性設為true。
通知(advice)類型
前置通知(before advice):在某連接配接點joinpoint之前執行的通知,但這個通知不能阻止連接配接點前的執行。applicationcontext中在<aop:aspect>裡面使用<aop:before>元素進行聲明。例如,testaspect中的dobefore方法。
後置通知(after advice):當某連接配接點退出的時候執行的通知(不論是正常傳回還是異常退出)。applicationcontext中在<aop:aspect>裡面使用<aop:after>元素進行聲明。例如testaspect中的doafter方法,是以aoptest中調用bserviceimpl.barb抛出異常時,doafter方法仍然執行。
傳回後通知(after return advice):在某連接配接點正常完成後執行的通知,不包括抛出異常的情況。applicationcontext中在<aop:aspect>裡面使用<after-returning>元素進行聲明。
環繞通知(around advice):包圍一個連接配接點的通知,類似web中servlet規範中的filter的dofilter方法。可以在方法的調用前後完成自定義的行為,也可以選擇不執行。applicationcontext中在<aop:aspect>裡面使用<aop:around>元素進行聲明。例如testaspect中的doaround方法。
抛出異常後通知(after throwing advice):在方法抛出異常退出時執行的通知。applicationcontext中在<aop:aspect>裡面使用<aop:after-throwing>元素進行聲明。例如testaspect中的dothrowing方法。
切入點表達式
通常情況下,表達式中使用execution就可以滿足大部分的要求。表達式格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的操作權限
ret-type-pattern:傳回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:參數名
throws-pattern:異常
其中,除ret-type-pattern和name-pattern之外,其他都是可選的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,傳回值為任意類型;方法名任意;參數不作限制的所有方法。
通知參數
可以通過args來綁定參數,這樣就可以在通知(advice)中通路具體參數了。例如<aop:aspect>配置如下
<aop:config>
<aop:aspect id="testaspect" ref="aspectbean">
<aop:pointcut id="businessservice"
expression="execution(* com.spring.service.*.*(string,..)) and args(msg,..)" />
<aop:after pointcut-ref="businessservice" method="doafter"/>
</aop:aspect>
</aop:config>
testaspect的doafter方法中就可以通路msg參數,但這樣以來aservice中的bara()和bserviceimpl中的barb()就不再是連接配接點,因為execution(* com.spring.service.*.*(string,..))隻配置第一個參數為string類型的方法。其中,doafter方法定義如下:
public void doafter(joinpoint jp,string msg)
通路目前的連接配接點
任何通知(advice)方法可以将第一個參數定義為org.aspectj.lang.joinpoint類型。joinpoint接口提供了一系列有用的方法,比如getargs() 傳回方法參數,getthis()傳回代理對象,gettarget()傳回目标,getsignature()傳回正在被通知的方法相關資訊和tostring()列印出正在被通知的方法的有用資訊。