SpringAOP的實現之代理模式

要想了解Spring的AOP的實現,首先必須要瞭解代理模式。

在這裏插入圖片描述
如果只是簡單代理一個對象,那麼我們自己就可以很好的實現。
聲明一個抽象主題,支付接口:

public interface ToCPayment {
    void pay();
}

被代理的實現類

public class ToCPaymentImpl implements ToCPayment {
    @Override
    public void pay() {
        System.out.println("以用戶的名義進行支付");
    }
}

代理角色(持有被代理角色)

public class AliToC implements ToCPayment {
    private ToCPayment toCPayment;
    public AliToC(ToCPayment toCPayment){
        this.toCPayment = toCPayment;
    }
    @Override
    public void pay() {
        beforePay();
        toCPayment.pay();
        afterPay();
    }

    private void afterPay() {
        System.out.println("支付給收錢方");
    }

    private void beforePay() {
        System.out.println("從銀行卡取款");
    }
}

這樣在調用接口的時候,直接就可以調用代理對象。

public class ProxyDemo {
    public static void main(String[] args) {
        ToCPayment toCProxy = new AliToC( new ToCPaymentImpl());
        toCProxy.pay();
    }
}

在這裏插入圖片描述
,但是這樣的實現有一個弊端,以上只是針對ToC的代理,如果此時增加一個ToB豈不是又要自己手寫一個被代理的類?這種靜態代理的方式無疑在代理多的情況下維護成本指數級別增加。

JDK的動態代理

上面的思路是沒有問題的。只是相關的實現方式需要改進。
我們知道,類是通過類加載器加載的。
1.通過findClass獲取到二進制字節流。
2.根據讀取到的字節流,將其代表的靜態數據結構轉換成方法區運行時的數據結構。有了數據結構,才能讓外界通過class對象取訪問。
3.生成一個代表該類的class對象,作爲方法區該類的數據訪問入口。

改進的切入點
根據一定的規則取改動或者生成新的字節流,將切面邏輯織入進去。
這就是動態代理,在程序運行時動態生成類的字節碼,並加載到JVM中。
她要求被代理的類必須實現接口(唯一約束),否則不能使用JDK動態代理。

其中關鍵的有一個核心類和一個接口。

public class Proxy implements java.io.Serializable
public interface InvocationHandler

只有實現了InvocationHandler的類,才具有代理功能。相關的切面邏輯就存在於此接口的實現類裏面。此接口只有一個方法。

 public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

當我們銅鼓動態代理對象調用一個方法的時候 ,這個方法的調用就會被轉發到實現了這個接口的本方法裏面。
此方法接受三個參數。

//Object proxy 代理對象(不是被代理對象)
// Method method 調用的目標對象的方法示例
//方法的參數
 public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

Proxy類

創建代理類。
裏面有一個很重要的方法

//ClassLoader loader 聲明哪個加載器來加載class\
//爲代理對象要加上的接口
// InvocationHandler  用來 表示動態代理的方法會關聯到哪個 InvocationHandler 的實現類
//返回的時一個被改造的類 但是 繼承自這些接口 所以可以偷天換日
 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

JDK動態代理例子

下載我們將增強邏輯直接寫在InvocationHandler的實現類裏面。

public class AliPayInvocationHandler implements InvocationHandler {
    //用來保存被代理的對象
    private Object targetObject;
    public AliPayInvocationHandler(Object targetObject){
        this.targetObject = targetObject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforePay();
        //目標類 方法參
        Object result = method.invoke(targetObject, args);
        afterPay();
        return result;
    }
    //增強邏輯
    private void afterPay() {
        System.out.println("支付給收錢方");
    }
    //增強邏輯
    private void beforePay() {
        System.out.println("從銀行卡取款");
    }
}
public class JdkUtil {
    public static <T> T newProxyInstance(T targetObject, InvocationHandler handler){
        ClassLoader classLoader = targetObject.getClass().getClassLoader();
        Class<?>[] interfaces = targetObject.getClass().getInterfaces();
        return (T) Proxy.newProxyInstance(classLoader,interfaces,handler);
    }
}
public class ProxyDemo {
    public static void main(String[] args) {
        //被代理的對象
        ToCPayment toCPayment = new ToCPaymentImpl();
        AliPayInvocationHandler handler = new AliPayInvocationHandler(toCPayment);
        ToCPayment payment = JdkUtil.newProxyInstance(toCPayment, handler);
        payment.pay();
    }
}

運行:
在這裏插入圖片描述
通過動態代理,可以複用增強的邏輯 ,代理對象是自動生成的,不用顯式的去寫一個類。

CGLIB

JDK動態代理要求對象必須實現接口,否則不能使用動態代理。這樣在於在接口調用方便偷天換日。
CGLIB全程 Code Generation Libary.不要求目標必須實現接口,內部封裝了ASM字節碼操作框架。
缺點在於:上手難度高 ,對JVM不熟悉直接修改字節碼,會發生莫名其妙的問題。
簡單來說CGLIB只是動態生成目標類的子類,覆蓋非final的方法,綁定鉤子回調自定義攔截器 。

使用cglib先導包。

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.9</version>
        </dependency>

主要由三個部分組成。
CallBack接口

```clike
public interface Callback {
}

`一個空接口,沒有方法,僅僅是標誌作用。

MethodInterceptor接口


public interface MethodInterceptor extends Callback {
//參數1 動態代理對象
//被代理的方法
// 方法參數 
//代理方法
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
我們可以通過實現這個接口,構造出通用的邏輯。

Enhancer類
幫助我們創建出動態代理實例出來。

CgLib使用示例

定義通用切面

public class AliPayMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        beforePay();
        //執行被代理方法
        Object invoke = methodProxy.invokeSuper(o, objects);
        afterPay();
        return invoke;
    }
    private void afterPay() {
        System.out.println("支付給收錢方");
    }

    private void beforePay() {
        System.out.println("從銀行卡取款");
    }
}

創建動態代理類

public class CgLibUtil {
    public static  <T> T  createProxy(T targetObject , MethodInterceptor interceptor){
       return (T) Enhancer.create(targetObject.getClass(), interceptor);
    }
}

創建被代理對象,(無接口)

public class CommonPay {
    public void pay(){
        System.out.println("一個通用的支付通道");
    }
}

Demo

public class CgLibDemo {
    public static void main(String[] args) {
    //無接口的類
        CommonPay commonPay = new CommonPay();
        AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
        CommonPay commonPayProxy = CgLibUtil.createProxy(commonPay,interceptor);
        commonPayProxy.pay();
    }
}

運行結果
在這裏插入圖片描述
CGLIB方式 不僅僅支持無接口的對象代理,也支持有接口的對象代理。

public class CgLibDemo {
    public static void main(String[] args) {
       /* CommonPay commonPay = new CommonPay();
        AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
        CommonPay commonPayProxy = CgLibUtil.createProxy(commonPay,interceptor);
        commonPayProxy.pay();*/
        //tocPayMent接口的實現類
        ToCPayment toCPayment = new ToCPaymentImpl();
        AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
        ToCPayment proxy = CgLibUtil.createProxy(toCPayment, interceptor);
        proxy.pay();
    }
}

但是JDK動態代理模式,如果強行代理無接口的類,就會拋出異常。

public class CgLibDemo {
    public static void main(String[] args) {
       /* CommonPay commonPay = new CommonPay();
        AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
        CommonPay commonPayProxy = CgLibUtil.createProxy(commonPay,interceptor);
        commonPayProxy.pay();*/
        //tocPayMent的實現類
       /* ToCPayment toCPayment = new ToCPaymentImpl();
        AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
        ToCPayment proxy = CgLibUtil.createProxy(toCPayment, interceptor);
        proxy.pay();*/
        //嘗試使用JDK動態代理
        CommonPay commonPay = new CommonPay();
        AliPayInvocationHandler handler = new AliPayInvocationHandler(commonPay);
        CommonPay newProxyInstance = JdkUtil.newProxyInstance(commonPay, handler);
        newProxyInstance.pay();
    }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章