代理模式

如此而已~~~發表於2024-06-30

Java代理模式

參考地址:https://javaguide.cn/java/basis/proxy.html#_1-代理模式

​ 簡單來說,就是給原本的類書寫一個代理類,我們透過代理類來操作原類。可以在不修改原目標物件的前提下,提供額外的功能操作,擴充套件目標物件的功能。

一、靜態代理

​ 靜態代理中,我們對目標物件的每個方法的增強都是手動完成的,非常不靈活且麻煩。具體步驟:

​ 定義一個介面及其實現類;

​ 建立一個代理類同樣實現這個介面

​ 將目標物件注入進代理類,然後在代理類的對應方法呼叫目標類中的對應方法。這樣的話,我們就可以透過代理類遮蔽對目標物件的訪問,並且可以在目標方法執行前後做一些自己想做的事情。

1、建立一個介面
public interface SmsService {
    String send(String msg);
}
2、定義一個實現類(這就是我們被代理的物件)
public class SmsServiceImpl implements SmsService {
    @Override
    public String send(String msg) {
        System.out.println("我傳送了訊息:" + msg);
        return msg;
    }
}
3、定義一個代理類

​ 其實這裡我們可以思考一下,代理類由於和原本類實現了同一個介面,所以原類必須實現的規則代理類也必須實現。而且在此基礎上由於維護了一個原類的物件的介面屬性,可以透過有參構造方法傳遞需要代理的類並進行補充和增強。

public class SmsProxy implements SmsService {

    private final SmsService smsService;    // 維護一個常量物件

    public SmsProxy(SmsService smsService) {    //類初始化的時候賦值
        this.smsService = smsService;
    }

    @Override
    public String send(String msg) {
        System.out.println("在傳送之前");
        String m = this.smsService.send(msg);
        System.out.println("在傳送之後");
        return m;
    }
}
4、測試
public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("你好世界!");
    }
}

對應的輸出結果(這裡其實感覺到了一點Spring AOP的影子了是吧):

在傳送之前
我傳送了訊息:你好世界!
在傳送之後

二、動態代理

JDK動態代理機制

流程:

​ 定義一個介面及其實現類;

​ 自定義 InvocationHandler 並重寫invoke方法,在 invoke 方法中我們會呼叫原生方法(被代理類的方法)並自定義一些處理邏輯;

​ 透過 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法建立代理物件;

1、建立一個介面
public interface SmsService {
    String send(String message);
}
2、定義一個實現類
public class SmsServiceImpl implements SmsService {

    @Override
    public String send(String message) {
        System.out.println("傳送了訊息:" + message);
        return message;
    }
}
3、定義一個 InvocationHandler 的實現類並重寫 invoke 方法,沒有自己針對每一個需要代理的類都寫一個代理類

​ 這裡可以想一下,我們可以對一些方法進行增強,當然也可以透過判斷一些特殊的方法進行一定的特殊處理。

public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理類中的真實物件
     */
    private final Object target;

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

    /**
     * 反射的感覺
     * invoke() 方法: 當我們的動態代理物件呼叫原生方法的時候,
     * 最終實際上呼叫到的是 invoke() 方法,
     * 然後 invoke() 方法代替我們去呼叫了被代理物件的原生方法
     * @param proxy 動態生成的代理類
     * @param method 與代理類物件呼叫的方法相對應
     * @param args 當前 method 方法的引數
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //呼叫方法之前,我們可以新增自己的操作
        System.out.println("在方法呼叫之前");
        Object result = method.invoke(target, args);
        //呼叫方法之後,我們同樣可以新增自己的操作
        System.out.println("在方法呼叫之後");
        return result;
    }
}
4、定義簡單工廠類獲取代理物件

​ 這裡的Proxy類的 newProxyInstance 方法用於生成一個代理類的例項,然後會去呼叫傳入的自定義的 InvocationHandler 的實現類中的 invoke 方法。

public class JdkProxyFactory {
    /**
    	傳入的引數是被代理的物件,返回的是其代理物件
    */
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目標類的類載入器,可以去了解一下JVM類載入過程
                target.getClass().getInterfaces(),  // 代理需要實現的介面,可指定多個
                new DebugInvocationHandler(target)   // 代理物件對應的自定義 InvocationHandler
        );
    }
}
5、測試
public class Main {
    public static void main(String[] args) {
        SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
        smsService.send("java");
    }
}

對應輸出結果:

在方法呼叫之前
傳送了訊息:java
在方法呼叫之後
CGLIB 動態代理機制

關鍵在於,這個方式不需要被代理的類實現某一個介面。

流程

​ 定義一個類;

​ 自定義 MethodInterceptor 並重寫 intercept 方法,intercept 用於攔截增強被代理類的方法,和 JDK 動態代理中的 invoke 方法類似;

​ 透過 Enhancer 類的 create()建立代理類;

1、建立一個maven專案

引入依賴:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
2、定義一個類
public class SmsService {

    public String send(String msg) {
        System.out.println("傳送訊息:" + msg);
        return msg;
    }
}
3、定義一個 MethodInterceptor 的實現類 並重寫其中的 intercept 方法
public class DebugMethodInterceptor implements MethodInterceptor {

    /**
     * @param o           被代理的物件(需要增強的物件)
     * @param method      被攔截的方法(需要增強的方法)
     * @param args        方法入參
     * @param methodProxy 用於呼叫原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //呼叫方法之前,我們可以新增自己的操作
        System.out.println("在方法呼叫之前");
        Object object = methodProxy.invokeSuper(o, args);
        //呼叫方法之後,我們同樣可以新增自己的操作
        System.out.println("在方法呼叫之後");
        return object;
    }

}
4、定義簡單工廠類獲取代理物件

​ 這裡的Enhancer類的 create() 方法用於生成一個代理類的例項,使用前設定對應的幾個引數。

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 建立動態代理增強類
        Enhancer enhancer = new Enhancer();
        // 設定類載入器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 設定被代理類
        enhancer.setSuperclass(clazz);
        // 設定方法攔截器,這裡是你自實現的攔截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 建立代理類
        return enhancer.create();
    }
}
5、測試
public class Main {
    public static void main(String[] args) {
        SmsService aliSmsService = (SmsService) CglibProxyFactory.getProxy(SmsService.class);
        aliSmsService.send("你好世界!");
    }
}

對應輸出結果:

在方法呼叫之前
傳送訊息:你好世界!
在方法呼叫之後

相關文章