1. 程式人生 > >【轉載】Spring AOP詳解 、 JDK動態代理、CGLib動態代理

【轉載】Spring AOP詳解 、 JDK動態代理、CGLib動態代理

rto 工廠 第一個 lec 僅支持 sel clas sleep gpo

原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html

AOP是Aspect Oriented Programing的簡稱,面向切面編程。AOP適合於那些具有橫切邏輯的應用:如性能監測,訪問控制,事務管理以及日誌記錄。AOP將這些分散在各個業務邏輯中的代碼通過橫向切割的方式抽取到一個獨立的模塊中。

一、AOP術語

1.連接點(Joinpoint)

程序執行的某個特定位置:如類開始初始化之前、類初始化之後、類某個方法調用前、調用後等;一個類或一段程序代碼擁有一些具有邊界性質的特定點,這些代碼中的特定點就成為“連接點”,Spring僅支持方法的連接點,即僅能在方法調用前、方法調用後以及方法調用前後的這些程序執行點織入增強。比如:黑客攻擊系統需要找到突破口,沒有突破口就沒有辦法攻擊,從某種程度上來說,AOP就是一個黑客,連接點就是AOP向目標類攻擊的候選點。

連接點有兩個信息確定:第一是用方法表示的程序執行點;第二是用相對點表示的方位;如在Test.foo()方法執行前的連接點,執行點為Test.foo,方位為該方法執行前的位置。Spring使用切點對執行點進行定位,而方位則在增強類型中定義。

2.切點(Pointcut)

每個程序類都擁有許多連接點,如一個擁有兩個方法的類,這兩個方法都是連接點,即連接點是程序類中客觀存在的事物。但在為數眾多的連接點鐘,如何定位到某個連接點上呢?AOP通過切點定位特定連接點。通過數據庫查詢的概念來理解切點和連接點:連接點相當於數據庫表中的記錄,而切點相當於查詢條件。連接點和切點不是一一對應的關系,一個切點可以匹配多一個連接點。

在Spring中,切點通過org.springframework.aop.Pointcut接口進行描述,它使用類和方法作為連接點的查詢條件,Spring AOP的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連接點;其實確切的說應該是執行點而非連接點,因為連接點是方法執行前、執行後等包括方位信息的具體程序執行點,而切點只定位到某個方法上,所以如果希望定位到具體連接點上,還需要提供方位信息。

3.增強(Advice)

增強是織入到目標類連接點上的一段程序代碼(好比AOP以黑客的身份往業務類中裝入木馬),增強還擁有一個和連接點相關的信息,這邊是執行點的方位。結合執行點方位信息和切點信息,我們就可以找到特定的連接點了,所以Spring提供的增強接口都是帶方位名的:BefortAdvice、AfterReturningAdvice、ThrowsAdvice等。(有些將Advice翻譯為通知,但通知就是把某個消息傳達給被通知者,並沒有為被通知者做任何事情,而Spring的Advice必須嵌入到某個類的連接點上,並完成了一段附加的應用邏輯;)

4.目標對象(Target)

增強邏輯的織入目標類,如果沒有AOP,目標業務類需要自己實現所有邏輯,在AOP的幫助下,目標類只實現那些非橫切邏輯的程序邏輯,而其他監測代碼則可以使用AOP動態織入到特定的連接點上。

5.引介(Introduction)

引介是一種特殊的增強,它為類添加一些屬性和方法,這樣即使一個業務類原本沒有實現某個接口,通過AOP的引介功能,我們可以動態的為該業務類添加接口的實現邏輯,讓這個業務類成為這個接口的實現類。

6.織入(Weaving)

織入是將增強添加到目標類具體連接點上的過程,AOP就像一臺織布機,將目標類、增強或者引介編織到一起,AOP有三種織入的方式:

a.編譯期間織入,這要求使用特殊的java編譯器;

b.類裝載期織入,這要求使用特殊的類裝載器;

c.動態代理織入,在運行期為目標類添加增強生成子類的方式。

Spring采用動態代理織入,而AspectJ采用編譯器織入和類裝載期織入。

7.代理(Proxy)

一個類被AOP織入增強後,就產生出了一個結果類,它是融合了原類和增強邏輯的代理類。

8.切面(Aspect)

切面由切點和增強組成,它既包括了橫切邏輯的定義,也包括了連接點的定義,Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯織入到切面所指定的連接點中。

總結:AOP的工作重點就是如何將增強應用於目標對象的連接點上,這裏首先包括兩個工作:第一,如何通過切點和增強定位到連接點;第二,如何在增強中編寫切面的代碼。

二、AOP實例(通過Proxy代理模式)

Spring AOP使用純java實現,不需要專門的編譯過程和類裝載器,它在運行期間通過代理方式向目標類織入增強代碼,它更側重於提供一種和Spring IoC容器整合的AOP實現,在Spring中,我們可以無縫的將AOP,IoC,AspectJ整合在一起。

Spring AOP使用了兩種代理機制:一種是基於JDK的動態代理,一種是基於CGLib的動態代理;

JDK1.3以後,java提供了動態代理技術,允許開發者在運行期間動態的創建接口的代理實例,JDK的動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy和InvocationHandler,其中InvocationHandler是一個接口,可以通過實現該接口定義橫切邏輯,並通過反射機制調用目標類的代碼,動態的將橫切邏輯和業務邏輯編織在一起。

下面我們來看一個JDK動態代理的例子:

1.業務接口UserService.java

package spring.aop.demo1;

public interface UserService {
    void removeUser(int userId);
}

2.橫切邏輯代理監視代碼PerformanceMonitor.java

技術分享圖片
package spring.aop.demo1;

public class MethodPerformance {

    private long begin;

    private long end;

    private String serviceMethod;

    public MethodPerformance(String serviceMethod) {
        this.serviceMethod = serviceMethod;
        this.begin = System.currentTimeMillis();
    }

    public void printPerformance() {
        this.end = System.currentTimeMillis();
        long elapse = end - begin;

        System.out.println(serviceMethod + "花費" + elapse + "毫秒");
    }

}
技術分享圖片 技術分享圖片
package spring.aop.demo1;

public class PerformanceMonitor {

    // 通過一個ThreadLocal保存調用線程相關的性能監視信息
    private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();

    // 啟動對一目標方法的性能監視
    public static void begin(String method) {
        System.out.println("begin monitor...");
        MethodPerformance mp = new MethodPerformance(method);
        performanceRecord.set(mp);
    }

    public static void end() {
        System.out.println("end monitor...");
        MethodPerformance mp = performanceRecord.get();
        mp.printPerformance();
    }

}
技術分享圖片

3.橫切邏輯代理代碼PerformanceHandler.java

技術分享圖片
package spring.aop.demo1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PerformanceHandler implements InvocationHandler {

    private Object target;

    public PerformanceHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object arg0, Method arg1, Object[] arg2)
            throws Throwable {
        PerformanceMonitor.begin(target.getClass().getName() + "."
                + arg1.getName());
        Object obj = arg1.invoke(target, arg2);// 通過反射機制調用目標對象的方法
        PerformanceMonitor.end();
        return obj;
    }

}
技術分享圖片

首先,我們實現InvocationHandler接口,該接口定義了一個invoke方法,proxy最是最終生成的一個代理實例,一般不會用到,參數arg1是被代理目標實例的某個具體的方法,通過它可以發起目標實例方法的反射調用;參數arg2是通過被代理實例某一個方法的入參,在方法反射調用時候使用,通過代理將橫切邏輯代碼和業務類的代碼編織到了一起。

我們在構造函數裏通過target傳入希望被代理的目標對象,將目標實例產地個method.inoke(),調用目標實例的方法。

4.通過Proxy結合PerformanceHandler創建UserService接口的代理實例:

技術分享圖片
package spring.aop.demo1;

import java.lang.reflect.Proxy;

public class UserServiceImpl implements UserService {

    @Override
    public void removeUser(int userId) {
        System.out.println("模擬刪除用戶:" + userId);
        try {
            Thread.currentThread().sleep(200);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        // 將目標業務類和橫切代碼編織到一起
        PerformanceHandler handler = new PerformanceHandler(userService);

        // 根據編織了目標業務類邏輯和性能監視橫切邏輯的InvocationHandler實例創建代理實例
        UserService proxy = (UserService) Proxy.newProxyInstance(userService
                .getClass().getClassLoader(), userService.getClass()
                .getInterfaces(), handler);
        
        proxy.removeUser(3);

    }
}
技術分享圖片

輸出:

begin monitor...
模擬刪除用戶:3
end monitor...
spring.aop.demo1.UserServiceImpl.removeUser花費203毫秒

說明:上面的代碼完成業務類代碼和橫切代碼的編制工作,並生成了代理實例,newProxyInstance方法的第一個參數為類加載器,第二個參數為目標類所實現的一組接口,第三個參數是整合了業務邏輯和橫切邏輯的編織器對象。使用JDK代理模式有一個限制,即它只能為接口創建代理實例,這一點我們可以從Proxy.newProxyInstance的方法簽名中就可以看的很清楚,第二個參數interfaces就是需要代理實例實現的接口列表。

CGLib采用非常底層的字節碼技術,可以為一個類創建子類,並在子類中采用方法攔截的結束攔截所有父類方法的調用,並順勢織入橫切邏輯。我們采用CGLib技術可以編寫一個可以為任何類創建織入橫切邏輯代理對象的代理創建器,下面看一個使用CGLib代理技術實現橫切的一個例子:

1.CglibProxy.java

技術分享圖片
package spring.aop.demo2;

import java.lang.reflect.Method;

import spring.aop.demo1.PerformanceMonitor;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

    // private static CglibProxy proxy = new CglibProxy();
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);// 設置需要創建子類的類
        enhancer.setCallback(this);
        return enhancer.create();// 通過字節碼技術動態創建子類實例
    }

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2,
            MethodProxy arg3) throws Throwable {
        PerformanceMonitor.begin(arg0.getClass().getName() + "."
                + arg1.getName());
        Object result = arg3.invokeSuper(arg0, arg2);
        PerformanceMonitor.end();
        return result;
    }

}
技術分享圖片

2.UserServiceImpl.java

技術分享圖片
package spring.aop.demo2;

public class UserServiceImpl{

    public void removeUser(int userId) {
        System.out.println("模擬刪除用戶:" + userId);
    }

    public void addUser(int userId) {
        // TODO Auto-generated method stub
    }

    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        UserServiceImpl userService =(UserServiceImpl)proxy.getProxy(UserServiceImpl.class);
        userService.removeUser(7);
    }
}
技術分享圖片

輸出:

begin monitor...
模擬刪除用戶:7
end monitor...
spring.aop.demo2.UserServiceImpl$$EnhancerByCGLIB$$4d9bdf63.removeUser花費15毫秒

總結:用戶通過getProxy(Class clazz)為一個類創建動態代理對象,該代理對象通過擴展clazz創建代理對象,在這個代理對象中,我們織入橫切邏輯代碼。intercept是CGLib定義的Interceptor接口的方法,它攔截所有目標方法的調用,參數arg0表示目標類的實例;參數arg1表示目標類方法的反射對象;arg2表示目標類方法的參數的反射對象;arg3表示代理類實例;

我們看到輸出spring.aop.demo2.UserServiceImpl$$EnhancerByCGLIB$$4d9bdf63.removeUser,這個特殊的類就是CGLib為UserService動態創建的子類。

Spring AOP的底層就是通過代理(JDK動態代理或CGlib代理)來實現AOP的,但是這種實現方式存在三個明顯需要改進的地方:

a.目標類的所有方法都添加了橫切邏輯,而有時,這並不是我們所期望的,我們可能只希望對業務類中的某些特定的方法添加橫切邏輯;

b.我們通過硬編碼的方式制定了織入橫切邏輯的織入點,即在目標業務方法的開始和結束前織入代碼;

c.我們手工編寫代理實例的創建過程,為不同類創建代理時,需要分別編寫相應的創建代碼,無法做到通用;

CGLib所創建的動態代理對象的性能比JDK的高大概10倍,但CGLib在創建代理對象的時間比JDK大概多8倍,所以對於singleton的代理對象或者具有實例池的代理,因為無需重復的創建代理對象,所以比較適合CGLib動態代理技術,反之選擇JDK代理。值得一提的是由於CGLib采用動態創建子類的方式生成代理對象,所以不能對目標類中final的方法進行代理。

三、創建增強類

Spring使用增強定義橫切邏輯,同時由於Spring只支持方法連接點,增強還包括了在方法的哪一點加入橫切代碼的方位信息,所以增強既包含橫切邏輯,還包含部分連接點的信息。

前置增強:org.springframework.aop.BeforeAdvice 代表前置增強,因為Spring只支持方法級的增強,所以MethodBeforeAdvice是目前可用的前置增強,表示在目標方法執行前實施增強,而BeforeAdvice是為了將來版本擴展需要而定義的,下面我們看一個前置通知的小例子:

1.Waiter.java

技術分享圖片
package spring.aop.beforeadvicedemo;

public interface Waiter {
    void greetTo(String name);
    void serverTo(String name);
}
技術分享圖片

2.GreetingBeforeAdvice.java 實現前置增強接口的橫切邏輯

技術分享圖片
package spring.aop.beforeadvicedemo;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class GreetingBeforeAdvice implements MethodBeforeAdvice {

    //arg0是目標類的方法,arg1是目標類方法的參數,arg2是目標類的實例
    @Override
    public void before(Method arg0, Object[] arg1, Object arg2)
            throws Throwable {
        String clientName = (String)arg1[0];
        System.out.println("How are you! Mr." + clientName);
    }

}
技術分享圖片

3.目標類NaiveWaiter.java和測試代碼

技術分享圖片
package spring.aop.beforeadvicedemo;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;

public class NaiveWaiter implements Waiter {

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

    @Override
    public void serverTo(String name) {
        System.out.println("serving "+ name);
    }
    
    public static void main(String[] args) {
        
        BeforeAdvice advice = new GreetingBeforeAdvice();
        Waiter waiter  = new NaiveWaiter();
        
        //Spring提供的代理工廠
        ProxyFactory pf = new ProxyFactory();
        
        //設置代理目標
        pf.setTarget(waiter);
        
        //為代理目標添加增強
        pf.addAdvice(advice);
        
        //生成代理實例
        Waiter waiterProxy = (Waiter)pf.getProxy();
        waiterProxy.greetTo("nicholaslee");
        waiterProxy.serverTo("nicholaslee");
    }

}
技術分享圖片

輸出:

How are you! Mr.nicholaslee
great to nicholaslee
How are you! Mr.nicholaslee
serving nicholaslee

說明:在測試代碼中,我們用到了org.springframework.aop.framework.ProxyFactory,這個內部就是使用了我們之前的JDK代理或者CGLib代理的技術,將增強應用到目標類中。Spring定義了org.springframework.aop.framework.AopProxy接口,並提供了兩個final的實現類,其中:

Cglib2AopProxy使用CGLib代理技術創建代理,而JdkDynamicAopProxy使用JDK代理技術創建代理;

如果通過ProxyFactory的setInterfaces(Class[] interfaces)指定針對接口進行代理,ProxyFactory就使用JdkDynamicAopProxy;

如果是通過類的代理則使用Cglib2AopProxy,另外也可以通過ProxyFactory的setOptimize(true)方法,讓ProxyFactory啟動優化代理模式,這樣針對接口的代理也會使用Cglib2AopProxy。

技術分享圖片
        BeforeAdvice advice = new GreetingBeforeAdvice();
        Waiter waiter  = new NaiveWaiter();
        
        //Spring提供的代理工廠
        ProxyFactory pf = new ProxyFactory();
        pf.setInterfaces(waiter.getClass().getInterfaces());
技術分享圖片

  以上代碼就指定了JdkDynamicAopProxy進行代理;

技術分享圖片
BeforeAdvice advice = new GreetingBeforeAdvice();
        Waiter waiter  = new NaiveWaiter();
        
        //Spring提供的代理工廠
        ProxyFactory pf = new ProxyFactory();
        pf.setInterfaces(waiter.getClass().getInterfaces());
        pf.setOptimize(true);
技術分享圖片

以上代碼雖然指定了代理的接口,但由於setOptimize(true),所以還是使用了Cglib2AopProxy代理;

我們使用了addAdvice來添加一個增強,用戶可以用該方法添加多個增強,形成一個增強鏈,調用順序和添加順序一致,下標從0開始:

技術分享圖片
package spring.aop.beforeadvicedemo;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class GreetingBeforeAdvice2 implements MethodBeforeAdvice {

    @Override
    public void before(Method arg0, Object[] arg1, Object arg2)
            throws Throwable {
        System.out.println( "我是第一個橫切邏輯");
    }

}
技術分享圖片 技術分享圖片
public static void main(String[] args) {
        
        BeforeAdvice advice = new GreetingBeforeAdvice();
        BeforeAdvice advice2 = new GreetingBeforeAdvice2();
        Waiter waiter  = new NaiveWaiter();
        
        //Spring提供的代理工廠
        ProxyFactory pf = new ProxyFactory();
        pf.setInterfaces(waiter.getClass().getInterfaces());
        pf.setOptimize(true);
        
        //設置代理目標
        pf.setTarget(waiter);
        
        //為代理目標添加增強
        pf.addAdvice(0,advice2);
        pf.addAdvice(1,advice);
        
        //生成代理實例
        Waiter waiterProxy = (Waiter)pf.getProxy();
        waiterProxy.greetTo("nicholaslee");
        waiterProxy.serverTo("nicholaslee");
    }
技術分享圖片

輸出:

我是第一個橫切邏輯
How are you! Mr.nicholaslee
great to nicholaslee
我是第一個橫切邏輯
How are you! Mr.nicholaslee
serving nicholaslee

我們還可以將以上代碼更加優化一下,可以通過依賴註入來實例化:

技術分享圖片
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:target-ref="targetWaiter"
        p:proxyTargetClass="true">
        <property name="interceptorNames">
            <list>
                <value>greetingAdvice</value>
                <value>greetingAdvice2</value>
            </list>
        </property>
    </bean>
技術分享圖片
public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Waiter waiter = (Waiter)ctx.getBean("waiter",Waiter.class);
        waiter.greetTo("nicholaslee");
    }

輸出:

How are you! Mr.nicholaslee
我是第一個橫切邏輯
great to nicholaslee

參數說明:

target:代理的目標對象;

proxyInterfaces:代理索要實現的接口,可以是多個接口,另一個別名屬性是interfaces;

interceptorNames:需要織入目標對象的Bean列表,必須是實現了MethodInterceptor或者aop.Advisor的Bean,配置的順序對應調用順序;

singleton:返回的代理是否單實例,默認為單實例;

optimize:當設置為true的時候,強制使用CGLib代理;

proxyTargetClass:是否對類進行代理(而不是針對接口進行代理),設置為true後,使用CGLib代理;

這個時候我們使用了JDK代理技術,如果我們想使用CGLib代理,則可以更改參數:

p:proxyTargetClass="true"

因為CGLib代理創建代理慢,但是創建的代理對象效率非常高,所以比較適合singleton的代理;

下面我們看一個後置增強,org.springframework.aop.AfterReturningAdvice,表示在目標方法執行後試試增強:

技術分享圖片
package spring.aop.afteradvicedemo;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class GreetingAfterAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object arg0, Method arg1, Object[] arg2,
            Object arg3) throws Throwable {
        System.out.println("Please enjoy youself~");
    }

}
技術分享圖片

下面看一下環繞增強,org.aopalliance.intercept.MethodInterceptor,表示在目標方法執行前後實施增強:

技術分享圖片
package spring.aop.interceptoradvicedemo;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class GreetingInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation arg0) throws Throwable {
        Object[] args = arg0.getArguments();// 獲取目標方法參數
        String clientName = (String) args[0];
        System.out.println("How are you:" + clientName);
        Object obj = arg0.proceed();
        System.out.println("just enjoy yourself");
        return obj;
    }
}
技術分享圖片

在環繞增強時,arg0.proceed()通過proceed反射調用目標實例相應的方法;

下面是異常拋出增強:org.springframework.aop.ThrowsAdvice,表示在目標方法拋出異常後實施增強;異常拋出增強最適合的應用場景是事務管理,當參與事務的某個Dao發生異常時,事務管理器就必須去回滾事務,下面看一個模擬的例子:

技術分享圖片
package spring.aop.throwsadvicedemo;

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

public class TransactionManager implements ThrowsAdvice {
    
    //ThrowsAdvice異常拋出增強接口沒有定義任何方法,它只是一個標示接口
    //在運行期Spring使用反射的機制自行判斷,我們必須采用以下簽名的增強方法
    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("成功回滾事務。");
    }

}
技術分享圖片 技術分享圖片
package spring.aop.throwsadvicedemo;

import java.sql.SQLException;

import org.springframework.aop.framework.ProxyFactory;

public class UserServiceImpl {
    
    public void removeUser(int userId) {
        System.out.println("模擬刪除用戶:" + userId);
        throw new RuntimeException("運行異常。");
    }
    
    public void addUser(int userId) {
        System.out.println("添加用戶" + userId);
        throw new RuntimeException("數據庫插入異常。");
    }
    
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        TransactionManager tran = new TransactionManager();
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(userService);
        pf.addAdvice(tran);
        UserServiceImpl user = (UserServiceImpl)pf.getProxy();
        user.removeUser(0);
        user.addUser(1);
    }
}
技術分享圖片

輸出:

模擬刪除用戶:0
---------------
method:removeUser
拋出異常:運行異常。
成功回滾事務。

添加用戶1
---------------
method:addUser
拋出異常:數據庫插入異常。
成功回滾事務。

也可以配置註入方式:

技術分享圖片
<bean id="throwsManager" class="spring.aop.throwsadvicedemo.TransactionManager" />
    <bean id="throwsTarget" class="spring.aop.throwsadvicedemo.UserServiceImpl" />
    <bean id="throwsAdvice" class="org.springframework.aop.framework.ProxyFactoryBean"
    p:target-ref="throwsTarget"
        p:proxyTargetClass="true" 
        p:singleton="false" >
        <property name="interceptorNames">
            <list>
                <value>throwsManager</value>
            </list>
        </property>
</bean>
技術分享圖片

最後來看一下引介增強:org.springframework.aop.IntroductionInterceptor,表示在目標類中添加一些新的方法和屬性;引介增強是一種比較特殊的增強類型,它不是在目標方法周圍織入增強,而是為目標類創建新的方法和屬性,所以引介增強的連接點是類級別的,而非方法級別的。通過引介增強,我們可以為目標類添加一個接口的實現,即原來目標類未實現某個接口,通過引介可以為目標類創建實現某接口的代理。

技術分享圖片
package spring.aop.introductionadvicedemo;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

import spring.aop.demo1.PerformanceMonitor;

public class ControllablePerformanceMonitor extends
        DelegatingIntroductionInterceptor implements Monitorable {

    /**
     * 
     */
    private static final long serialVersionUID = -5983845636084465442L;

    private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();

    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;
    }

    @Override
    public void setMonitorActive(boolean active) {
        MonitorStatusMap.set(active);
    }

}
技術分享圖片 技術分享圖片
package spring.aop.introductionadvicedemo;

public interface Monitorable {
    
    void setMonitorActive(boolean active);

}
技術分享圖片 技術分享圖片
package spring.aop.introductionadvicedemo;

import org.springframework.aop.framework.ProxyFactory;

public class ForumService {
    
    public void removeUser(int userId) {
        System.out.println("模擬刪除用戶:" + userId);
    }
    
    public void addUser(int userId) {
        System.out.println("添加用戶" + userId);
    }
    
    public static void main(String[] args) {
        ForumService forumService = new ForumService();
        ControllablePerformanceMonitor advice = new ControllablePerformanceMonitor();
        ProxyFactory pf = new ProxyFactory();
        pf.setInterfaces(Monitorable.class.getInterfaces());
        pf.setTarget(forumService);
        pf.setOptimize(true);
        pf.addAdvice(advice);
        ForumService forum = (ForumService)pf.getProxy();
        Monitorable monitorAble =(Monitorable)forum;
        monitorAble.setMonitorActive(true);
        forum.removeUser(1);
        
    }

}
技術分享圖片

輸出:

begin monitor...
模擬刪除用戶:1
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeUser花費32毫秒

說明:

1. Monitorable monitorAble =(Monitorable)forum; 我們可以這麽轉換,說明返回的代理實例確實引入了Monitorable接口方法的實現;

2. pf.setInterfaces(Monitorable.class.getInterfaces()); 引介增強需要制定所實現的接口;

3. pf.setOptimize(true); 由於只能通過為目標類創建子類的方式生成音節增強的代理,所以必須選擇CGLib代理;

【轉載】Spring AOP詳解 、 JDK動態代理、CGLib動態代理