1. 程式人生 > >Spring中的增強型別

Spring中的增強型別

Spring通過增強型別定義橫切邏輯,同時由於Spring只支援方法連線點,增強還包括了在方法的哪一點加入橫切程式碼的方位資訊,所以增強既包括橫切邏輯,還包括部分連線點的資訊。

增強包括以下幾類:

  1. 前置增強:org.springframework.aop.BeforeAdvice代表前置增強,表示在目標方法整形前實施增強

  2. 後置增強:org.springframework.aop.AfterReturningAdvice代表後置增強,表示在目標方法執行後實施增強

  3. 環繞增強:org.springframework.aop.MethodInterceptor代表環繞增強,表示在目標方法執行前後實施增強

  4. 異常丟擲增強 :org.springframework.aop.ThrowsAdvice代表丟擲異常增強,表示在目標方法丟擲異常後實施增強

  5. 引介增強:org.springframework.aop.IntroductionInterceptor代表引介增強,表示在目標類中新增一些新的方法和屬性

前置增強

下面通過一個服務生服務前的禮貌用語來舉例增強:

Waiter.java

public interface Waiter {
   void greetTo(String name);
   void serveTo(String name);
}

未新增前置增強前:
NaiveWaiter.java

public class NaiveWaiter implements Waiter {

    public void greetTo(String name) {
        System.out.println("greet to "+name+"...");
    }

    public void serveTo(String name){
        System.out.println("serving "+name+"...");
    }
}

前置增強類如下:
GreetingBeforeAdvice.java

public class GreetingBeforeAdvice
implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object obj) throws Throwable { String clientName = (String)args[0]; System.out.println("How are you!Mr."+clientName+"."); } }

MethodBeforeAdvice是前置增強BeforeAdvice介面的子類。其中僅有唯一的方法:before(Method method, Object[] args, Object obj) throws Throwable,method為目標類的方法,args為目標類方法的入參,obj為目標類的例項,當該方法發生異常時,將會阻止目標類方法的執行。

前置增強應用如下:
TestBeforeAdvice.java

public class TestBeforeAdvice {
    public static void main(String[] args) {
        Waiter target = new NaiveWaiter();
        //建立增強
        BeforeAdvice  advice = new GreetingBeforeAdvice();
        //Spring提供的代理工廠
        ProxyFactory pf = new ProxyFactory();
        //指定對介面進行代理
        pf.setInterfaces(target.getClass().getInterfaces());
        //啟用優化
        pf.setOptimize(true);
        //設定代理目標
        pf.setTarget(target);
        //為代理目標新增增強
        pf.addAdvice(advice);
        //生成代理例項
        Waiter proxy = (Waiter)pf.getProxy(); 
        proxy.greetTo("John");
        proxy.serveTo("Tom");
    }
}

執行結果如下:可以發現每個方法執行前都引入了前置增強的語句

這裡寫圖片描述

在上面的代理工廠中就是通過JDK代理或CGLib代理的技術,如果通過setInterfaces方法指定對介面進行代理就會使用JdkDynamicAopProxy,如果是針對類的代理,則使用Cglib2AopProxy。同時,如上如果啟動了優化代理的方式,針對介面的代理也會使用Cglib2AopProxy。

在上面的原始碼中可以通過ProxyFactory的addAdvice()方法新增一個增強,使用者可以新增多個增強,多個增強的呼叫順序和新增順序一致,也可以通過addAdvice(int Advice)將增強新增到增強鏈的具體位置(第一個位置為0)

Spring中配置如下:

<bean id="greetingBefore" class="com.baobaotao.advice.GreetingBeforeAdvice" />

<bean id="target" class="com.baobaotao.advice.NaiveWaiter" />
<bean id="waiter"
        class="org.springframework.aop.framework.ProxyFactoryBean"
        p:proxyInterfaces="com.baobaotao.advice.Waiter" p:target-ref="target"
        p:interceptorNames="greetingAdvice"/>

ProxyFactoryBean的常用屬性如下:

  • target:代理的目標物件

  • proxyInterfaces:代理所要實現的介面,可以是多個介面。屬性的另一個別名interfaces

  • interceptorNames:需要植入目標的Bean列表,採用Bean的名稱指定,指定使用的增強。這些Bean必須是實現了org.aopalliance.intercept.MethodInterceptor或org.springframework.aop.Advisor的Bean,即就是一個增強

  • singleton:返回的代理是否是單例項,預設為單例項

  • optimize:設定為true時,強制使用CGLib代理,對於singleton的代理,推薦使用CGLib,因為CGLib建立代理速度慢,而創建出的物件執行效率高,JDK代理的正好相反

  • proxyTargetClass:是否對類進行代理,設定為true時,使用CGLib代理

測試增強的程式碼如下:

public class TestAdvice1 {
    public static void main(String[] args) {
        String configPath = "com/baobaotao/advice/beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        Waiter waiter = (Waiter)ctx.getBean("waiter");
        waiter.greetTo("John");
    }
}

後置增強

後置增強在目標類方法呼叫後執行如下新建一個後置增強

GreetingAfterAdvice.java

public class GreetingAfterAdvice implements AfterReturningAdvice {

    //在目標方法呼叫後執行
    public void afterReturning(Object returnObj, Method method, Object[] args,
            Object obj) throws Throwable {
        System.out.println("Please enjoy yourself!");
    }
}

後置增強需要實現AfterReturningAdvice來定義後置增強的邏輯,AfterReturningAdvice介面定義了唯一的方法afterReturning(Object returnObj, Method method, Object[] args, Object obj) throws Throwable,returnObj為目標類例項。如果在後置增強中丟擲異常,若該異常是目標方法生命的異常,則該異常歸併到目標方法中;如果不是目標方法所生命的異常,則將其作為執行期異常丟擲

環繞增強

環繞增強綜合實現了前置、後置增強兩者的功能。

如下示例:
GreetingInterceptor.java

public class GreetingInterceptor implements MethodInterceptor {
    //用來截獲目標方法的執行,並在前後新增橫切邏輯
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        String clientName = (String)args[0];
        //目標方法執行前的呼叫
        System.out.println("How are you!Mr."+clientName+".");
        //通過反射機制呼叫目標方法
        Object obj = invocation.proceed();
        //目標方法執行後的呼叫        
        System.out.println("Please enjoy yourself!");

        return obj;
    }
}

如上原始碼所示,MethodInterceptor作為環繞增強的介面。該介面擁有唯一的介面方法Object invoke(MethodInvocation var1) throws Throwable;

MethodInvocation封裝了目標方法、入引數組以及目標方法所在的例項物件,通過getArguments()可以獲得目標方法的入引數組,通過proceed()反射呼叫目標例項相應的方法。

異常丟擲增強

異常丟擲增強適合事務管理的場景,這裡用一個模擬出錯丟擲異常,來使用異常丟擲增強的例子:

ForumService.java

public class ForumService {
    public void removeForum(int forumId) {
        // do sth...
        throw new RuntimeException("執行異常。");
    }
    public void updateForum(Forum forum) throws Exception{
        // do sth...
        throw new SQLException("資料更新操作異常。");

    }
}

如下定義一個異常丟擲增強:
TransactionManager.java

public class TransactionManager implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target,
            Exception ex) throws Throwable {
        System.out.println("-----------");
        System.out.println("method:" + method.getName());
        System.out.println("丟擲異常:" + ex.getMessage());
        System.out.println("成功回滾事務。");
    }
}

afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable

ThrowsAdvice 異常增強介面並無任何方法,是一個標識介面,在執行期間使用反射機制自行判斷,採用方法名為afterThrowing的方法定義增強方法。方法入參中前三個是可選的(一起選或不選),最後一個引數為Throwable或其子類,可以在一個異常丟擲增強中同時定義多個afterThrowing(), Spring會選擇最匹配的增強方法,即就是類的繼承關係越近相似度越高,匹配度就越高。

例如,若定義了afterThrowing(SQLException e)和afterThrowing(Throwable e);而丟擲了SQLException時就會匹配afterThrowing(SQLException e)。

引介增強

引介增強並非在目標方法周圍織入增強,而是為目標類建立新的方法和屬性,所以引介增強的連線點是類級別的,而非方法級別的,通過引介增強,可以為目標類新增一個介面的實現。如下面這個例子:

Monitorable.java

public interface Monitorable {
   void setMonitorActive(boolean active);
}

ControllablePerformaceMonitor.java

public class ControllablePerformaceMonitor
        extends
            DelegatingIntroductionInterceptor implements Monitorable {
    private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
    public void setMonitorActive(boolean active) {
        MonitorStatusMap.set(active);
    }
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object obj = null;
        if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
            PerformanceMonitor.begin(mi.getClass().getName() + "."
                    + mi.getMethod().getName());
            obj = super.invoke(mi);
            PerformanceMonitor.end();
        } else {
            obj = super.invoke(mi);
        }
        return obj;
    }
}

第一段原始碼定義了一個介面,第二段原始碼定義了一個引介增強,繼承了DelegatingIntroductionInterceptor類,實現了Monitorable介面,在類中實現了setMonitorActive方法,該方法設定了類中的一個變數,這個變數用來控制是否開啟效能監控功能。

實現介面的方法配合攔截的方法就可以支援效能監視可控代理。

引介增強的配置如下:

    <bean id="pmonitor" class="com.baobaotao.introduce.ControllablePerformaceMonitor" />
    <bean id="forumServiceTarget" class="com.baobaotao.introduce.ForumService" />
    <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interfaces="com.baobaotao.introduce.Monitorable" 
        p:target-ref="forumServiceTarget"
        p:interceptorNames="pmonitor" 
        p:proxyTargetClass="true" />

引介增強的配置需要制定引介增強所實現的介面,其次需要將proxyTargetClass設定為true。

其他類的程式碼和本文開頭的類一致,引介增強測試類如下:
TestIntroduce.java

public class TestIntroduce {
    public static void main(String[] args) {
        String configPath = "beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        ForumService forumService = (ForumService)ctx.getBean("forumService");

        forumService.removeForum(10);
        forumService.removeTopic(1022);

        Monitorable moniterable = (Monitorable)forumService;
        moniterable.setMonitorActive(true);
        forumService.removeForum(10);
        forumService.removeTopic(1022); 
    }
}

執行結果如下:

這裡寫圖片描述

從執行結果可以看出在未開啟效能監控時是按照正常的邏輯執行的,開啟效能監控之後就橫切入了增強的邏輯。