說說 Spring AOP 的底層實(shí)現(xiàn)技術(shù)(JDK 與 CGLib 動(dòng)態(tài)代理)

Spring AOP 使用了兩種代理機(jī)制:

  • 基于 JDK 的動(dòng)態(tài)代理(接口代理)。
  • 基于 CGLib 的動(dòng)態(tài)代理(類代理)。

1 訂單服務(wù)實(shí)例

假設(shè)有這樣的一個(gè)訂單服務(wù),它提供新增訂單的方法,我們需要對(duì)這個(gè)方法的性能做監(jiān)控。

OrderService:

public interface OrderService {

    void add();
}

OrderServiceImpl:

public class OrderServiceImpl implements OrderService {

    /**
     * 新增
     */
    public void add() {
        PerformanceMonitor.begin("net.deniro.spring4.aop.add");//開啟監(jiān)視

        System.out.println("模擬新增訂單");
        try {
            Thread.currentThread().sleep(50);
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }
        PerformanceMonitor.end();//結(jié)束監(jiān)視

    }
}

PerformanceMonitor(性能監(jiān)控器):

public class PerformanceMonitor {

    //通過 ThreadLocal,保存與調(diào)用線程相關(guān)的性能監(jiān)視信息
    private static ThreadLocal<PerformanceRecord> record=new
            ThreadLocal<PerformanceRecord>();

    /**
     * 開啟監(jiān)視
     * @param method 需要監(jiān)視的方法
     */
    public static void begin(String method) {
        System.out.println("開啟監(jiān)視...");
        record.set(new PerformanceRecord(method));
    }

    /**
     * 結(jié)束監(jiān)視
     */
    public static void end() {
        System.out.println("結(jié)束監(jiān)視...");
        record.get().print();

    }
}

ThreadLocal 可以將非線程安全的類改造為線程安全的類。

PerformanceRecord(性能記錄):

public class PerformanceRecord {

    private final String methodName;//方法名稱
    private final long begin;//開始時(shí)間

    public PerformanceRecord(String method) {
        this.methodName = method;
        this.begin = System.currentTimeMillis();
    }

    /**
     * 打印性能信息
     */
    public void print() {
        long end = System.currentTimeMillis();
        long elapse = end - begin;
        System.out.println(methodName + " 耗費(fèi)時(shí)間:" + elapse + " 毫秒");
    }
}

輸出結(jié)果:

開啟監(jiān)視...
模擬新增訂單
結(jié)束監(jiān)視...
net.deniro.spring4.aop.add 耗費(fèi)時(shí)間:50 毫秒

雖然方法添加了性能監(jiān)視功能,但這些性能監(jiān)視代碼侵入了業(yè)務(wù)邏輯。我們可以通過 JDK 與 CGLib 動(dòng)態(tài)代理技術(shù)把這些具有橫切性質(zhì)的代碼動(dòng)態(tài)織入目標(biāo)方法。

2 JDK 動(dòng)態(tài)代理

Java1.3 以后, JAVA 實(shí)現(xiàn)了動(dòng)態(tài)代理技術(shù),允許開發(fā)者在運(yùn)行期創(chuàng)建接口的代理實(shí)例 。

JDK 的動(dòng)態(tài)代理主要涉及 java.lang.reflect 包中的兩個(gè)類: Proxy 和 InvocationHandler。

  • InvocationHandler 是接口,可以通過實(shí)現(xiàn)該接口來定義橫切邏輯,并通過反射機(jī)制調(diào)用目標(biāo)類的代碼,動(dòng)態(tài)地將橫切邏輯和業(yè)務(wù)邏輯編織在一起 。
  • Proxy 利用 InvocationHandler 動(dòng)態(tài)創(chuàng)建出一個(gè)實(shí)現(xiàn)某一接口的實(shí)例,來生成目標(biāo)類的代理對(duì)象 。

在 OrderServiceImpl 中,新建一個(gè)只包含業(yè)務(wù)邏輯的方法 addByJDKProxy:

/**
 * 新增(JDK 代理)
 */
public void addByJDKProxy() {
    addOrder();
}

/**
 * 新增訂單
 */
private void addOrder() {
    System.out.println("模擬新增訂單");
    try {
        Thread.currentThread().sleep(50);
    } catch (InterruptedException e) {
        throw new RuntimeException();
    }
}

接著創(chuàng)建動(dòng)態(tài)代理類 PerformanceHandler:

public class PerformanceHandler implements InvocationHandler {

    private final Object target;//目標(biāo)類

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        PerformanceMonitor.begin(target.getClass().getName()+"."+method.getName());
        Object obj=method.invoke(target,args);//調(diào)用業(yè)務(wù)類的目標(biāo)方法
        PerformanceMonitor.end();
        return obj;
    }
}

InvocationHandler 接口只定義了一個(gè)方法:public Object invoke(Object proxy, Method method, Object[] args),各參數(shù)說明如下:

參數(shù) 說明
proxy 最終生成的代理實(shí)例,很少用到。
method 被代理目標(biāo)實(shí)例的某個(gè)具體方法,通過它可以發(fā)起目標(biāo)實(shí)例方法的反射調(diào)用。
args 被代理實(shí)例某個(gè)方法的入?yún)ⅲ诜椒ǚ瓷湔{(diào)用時(shí)使用。

在構(gòu)造函數(shù)中,我們通過 target 參數(shù)傳入希望被代理的目標(biāo)對(duì)象 。

單元測(cè)試:

//編織目標(biāo)業(yè)務(wù)類與橫切代碼
PerformanceHandler handler=new PerformanceHandler(orderService);

//創(chuàng)建代理實(shí)例
OrderService proxy=(OrderService) Proxy.newProxyInstance(orderService.getClass()
        .getClassLoader(),orderService.getClass().getInterfaces(),handler);

proxy.addByJDKProxy();

這里通過 Proxy 的 newProxyInstance() 的靜態(tài)方法為編織了業(yè)務(wù)邏輯和性能監(jiān)視邏輯的 handler 創(chuàng)建了一個(gè)符合 OrderService 接口的代理實(shí)例 。 這個(gè)方法的各參數(shù)說明如下:

參數(shù) 類型 說明
loader ClassLoader 類加載器。
interfaces Class<?>[] 創(chuàng)建代理實(shí)例所需要實(shí)現(xiàn)的一組接口。
h InvocationHandler 整合了業(yè)務(wù)邏輯和橫切邏輯的編織器對(duì)象。
JDK 動(dòng)態(tài)代理時(shí)序圖

3 CGLib 動(dòng)態(tài)代理

JDK 創(chuàng)建代理時(shí)只能為接口創(chuàng)建代理實(shí)例 。 Proxy 的 newProxyInstance 方法定義為:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

第二個(gè)參數(shù) interfaces 就是需要代理實(shí)例實(shí)現(xiàn)的接口列表 。

所以對(duì)于沒有通用接口定義業(yè)務(wù)方法的類,可以 CGLib 實(shí)現(xiàn)動(dòng)態(tài)代理。

CGLib 采用底層的字節(jié)碼技術(shù),可以為一個(gè)類創(chuàng)建子類,在子類中采用方法攔截技術(shù)來攔截所有父類方法的調(diào)用并順勢(shì)織入橫切邏輯 。

代理類:

public class PerformanceProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);//設(shè)置父類
        enhancer.setCallback(this);
        return enhancer.create();//使用字節(jié)碼技術(shù)動(dòng)態(tài)創(chuàng)建子類的實(shí)例
    }

    /**
     * 攔截父類所有方法的調(diào)用
     *
     * @param o           目標(biāo)類實(shí)例
     * @param method      目標(biāo)類方法的反射對(duì)象
     * @param args        方法的動(dòng)態(tài)入?yún)?     * @param methodProxy 代理類實(shí)例
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName());
        Object result = methodProxy.invokeSuper(o, args);
        PerformanceMonitor.end();
        return result;
    }
}

單元測(cè)試:

PerformanceProxy proxy = new PerformanceProxy();
OrderServiceImpl orderService = (OrderServiceImpl) proxy.getProxy(OrderServiceImpl.class);
orderService.addOrder();

輸出結(jié)果:

開啟監(jiān)視...
模擬新增訂單
結(jié)束監(jiān)視...
net.deniro.spring4.aop.OrderServiceImpl$$EnhancerByCGLIB$$aea908ed.addOrder 耗費(fèi)時(shí)間:83 毫秒

代理類名變?yōu)?OrderServiceImpl$$EnhancerByCGLIB$$aea908ed.addOrder 這個(gè)特殊的類就是 CGLib 為 OrderServiceImpl 動(dòng)態(tài)創(chuàng)建的子類 。


Spring AOP 通過切點(diǎn)(Pointcut)來決定需要在哪些類的哪些方法上織入橫切邏輯;接著通過增強(qiáng)(Advice)來描述橫切邏輯和方法的具體織入點(diǎn);然后通過切面(Advisor)把切點(diǎn)與增強(qiáng)裝配起來;最后,利用 JDK 或 CGLib 的動(dòng)態(tài)代理技術(shù)為目標(biāo)類創(chuàng)建已織入切面的代理對(duì)象。

所創(chuàng)建的動(dòng)態(tài)代理對(duì)象的性能,CGLib 是 JDK 的 10 倍;而創(chuàng)建動(dòng)態(tài)代理對(duì)象所花費(fèi)的時(shí)間上,CGLib 卻比 JDK 多花 8 倍的時(shí)間。所以,對(duì)于單例模式或者具有實(shí)例池的代理類,適合采用 CGLib 技術(shù);反之,則適合采用 JDK 技術(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容