設計模式 - 代理模式詳解

農夫三拳有點疼~發表於2020-04-16

代理模式介紹

代理模式提供了對目標物件額外的訪問方式,即通過代理物件訪問目標物件,這樣可以在不修改原目標物件的前提下,提供額外的功能操作,擴充套件目標物件的功能。

代理模式分為三類:

  • 靜態代理
  • 動態代理
  • Cglib 代理

靜態代理(不推薦)

介紹

要求目標物件和代理物件實現同一個介面,呼叫的時候呼叫代理物件的方法,從而達到增強的效果

優點:

可以在不修改目標物件的前提下,增強目標物件方法的功能(所有代理模式都可以實現,因此不推薦使用此方法)

缺點:

① 冗餘。目標物件和代理物件實現同一個介面,會產生過多的代理類。

② 不易維護。當介面方法增加,目標物件與代理物件都要進行修改。

程式碼實現

場景:廠家生產了商品,但是沒有足夠的精力、人力去銷售,這時候就需要一個代理商幫他售賣,但是代理商需要從中抽取 20% 的利潤。

公共介面

public interface IProducer {
    void sale(float money);
}

被代理物件

public class Producer implements IProducer {
    @Override
    public void sale(float money) {
        System.out.println("賣出產品,廠家獲得" + money + "元");
    }
}

代理物件

public class ProxyProducer implements IProducer{

    private IProducer producer;

    public ProxyProducer(IProducer producer) {
        this.producer = producer;
    }

    @Override
    public void sale(float money) {
        producer.sale(money * 0.8f);
    }
}

測試類

public class Client {
    @Test
    public void test(){
        IProducer producer = new Producer();
        ProxyProducer proxyProducer = new ProxyProducer(producer);
        proxyProducer.sale(1000f);
    }
}

執行結果

賣出產品,廠家獲得800.0元

動態代理

介紹

動態代理也稱:JDK 代理、介面代理,需要目標物件實現介面,否則不能用動態代理,利用 JDK 的 API(java.lang.reflect.Proxy),動態地在記憶體中構建代理物件

靜態代理和動態代理的區別:

  • 靜態代理在編譯時就已經實現,編譯完後的代理類是一個實際的 class 檔案
  • 動態代理實在執行時動態生成的,編譯後沒有實際的 class 檔案,而是在執行時動態的生成類位元組碼,並載入到 JVM 中

程式碼實現

以靜態代理的情景為例,我們只需要修改代理物件的程式碼,代理物件不需要實現公共介面了。

public class ProxyProducer {
    /**
     * 維護一個目標物件
     */
    private Object target;

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

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 執行被代理物件的任何介面方法都會經過這裡
                     * @param proxy 代理物件的引用
                     * @param method 當前執行的方法
                     * @param args 當前執行方法的引數
                     * @return 和被代理物件具有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //代理過程中執行一些方法
                        float money = (float) args[0] * 0.8f;
                        //反射機制呼叫目標物件的方法
                        Object invoke = method.invoke(target, money);
                        return invoke;
                    }
                });
    }
}

Cglib 代理

介紹

Cglib 代理也叫子類代理,目標物件不需要實現任何介面,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件。

Cglib 是一個強大的高效能的程式碼生成包,它可以在執行期間擴充套件 Java 類與實現 Java 介面,它廣泛地被許多 AOP 的框架使用,例如 Spring AOP,用於實現方法攔截。

Cglib 包底層實通過使用位元組碼處理框架 ASM 來轉換位元組碼並生成新的類。

在 AOP 程式設計中選擇哪種代理模式?

  • 目標物件需要實現介面,用 JDK 代理
  • 目標物件不需要實現介面,用 Cglib 代理

程式碼實現

使用之前需要匯入相關 jar 包,可去 maven 倉庫下載

被代理物件,無需實現介面

public class Producer {
    public void sale(float money) {
        System.out.println("賣出產品,廠家獲得" + money + "元");
    }
}

代理物件

public class ProxyProducer implements MethodInterceptor {
    /**
     * 維護一個目標物件
     */
    private Object target;

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

    /**
     * 為目標物件生成代理物件
     */
    public Object getProxyInstance(){
        //建立一個工具類
        Enhancer enhancer = new Enhancer();
        //設定父類
        enhancer.setSuperclass(target.getClass());
        //設定回撥函式
        enhancer.setCallback(this);
        //建立子類物件(代理物件)
        return enhancer.create();
    }

    /**
     * 會攔截被代理物件的所有方法
     * @param obj 增強物件
     * @param method 被代理物件的方法
     * @param args 被代理物件方法的引數
     * @param methodProxy 代理物件
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("obj:" + obj.getClass());
        Object returnValue = null;
        float money = (float) args[0] * 0.8f;
        if("sale".equals(method.getName())){
            returnValue = method.invoke(target, money);
        }
        return returnValue;
    }
}

測試類

public class Client {
    @Test
    public void test() {
        Producer producer = new Producer();
        Producer proxyInstance = (Producer) new ProxyProducer(producer).getProxyInstance();
        proxyInstance.sale(1000f);
    }
}

相關文章