天天看點

Spring中的AOP詳解

本文摘自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()列印出正在被通知的方法的有用資訊。