秒懂 Java 的三種代理模式

初念初戀發表於2021-08-04

前言

代理(Proxy)模式是一種結構型設計模式,提供了對目標物件另外的訪問方式;即通過代理物件訪問目標物件。

image-20210724125009890

這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能。

這裡使用到程式設計中的一個思想:不要隨意去修改別人已經寫好的程式碼或者方法,如果需要修改,可以通過代理的方式來擴充套件該方法。

代理模式大致有三種角色:

  • Real Subject:真實類,也就是被代理類、委託類。用來真正完成業務服務功能;
  • Proxy:代理類,將自身的請求用 Real Subject 對應的功能來實現,代理類物件並不真正的去實現其業務功能;
  • Subject:定義 RealSubject 和 Proxy 角色都應該實現的介面。

image-20210724125955100

代理模式有三種型別,靜態代理,動態代理(JDK代理,介面代理)、Cglib代理(在記憶體中動態的建立目標物件的子類)

正文

靜態代理

靜態代理需要先定義介面,被代理物件與代理物件一起實現相同的介面,然後通過呼叫相同的方法來呼叫目標物件的方法

image-20210726222112024

可以看見,代理類無非是在呼叫委託類方法的前後增加了一些操作。委託類的不同,也就導致代理類的不同。

某公司生產電視機,在當地銷售需要找到一個代理銷售商。那麼客戶需要購買電視機的時候,就直接通過代理商購買就可以。

程式碼示例:

電視機:

public class TV {

    private String name;//名稱

    private String address;//生產地

    public TV(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "TV{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

建立公司介面:

public interface TVCompany {

    /**
     * 生產電視機
     * @return 電視機
     */
    public TV produceTV();
}

公司的工廠生產電視機:

public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米電視機","合肥");
    }
}

代理商去下單拿貨(靜態代理類):

public class TVProxy implements TVCompany{

    private TVCompany tvCompany;

    public TVProxy(){

    }

    @Override
    public TV produceTV() {
        System.out.println("TV proxy get order .... ");
        System.out.println("TV proxy start produce .... ");
        if(Objects.isNull(tvCompany)){
            System.out.println("machine proxy find factory .... ");
            tvCompany = new TVFactory();
        }
        return tvCompany.produceTV();
    }
}

消費者通過代理商拿貨(代理類的使用):

public class TVConsumer {

    public static void main(String[] args) {
        TVProxy tvProxy = new TVProxy();
        TV tv = tvProxy.produceTV();
        System.out.println(tv);
    }
}

輸出結果:

TV proxy get order .... 
TV proxy start produce .... 
machine proxy find factory .... 
TV factory produce TV...
TV{name='小米電視機', address='合肥'}

Process finished with exit code 0

小結:

  • 優點:靜態代理模式在不改變目標物件的前提下,實現了對目標物件的功能擴充套件。

  • 缺點:靜態代理實現了目標物件的所有方法,一旦目標介面增加方法,代理物件和目標物件都要進行相應的修改,增加維護成本。

如何解決靜態代理中的缺點呢?答案是可以使用動態代理方式

動態代理

image-20210726224121229

動態代理具有如下特點:

  1. JDK動態代理物件不需要實現介面,只有目標物件需要實現介面。

  2. 實現基於介面的動態代理需要利用JDK中的API,在JVM記憶體中動態的構建Proxy物件

  3. 需要使用到 java.lang.reflect.Proxy,和其newProxyInstance方法,但是該方法需要接收三個引數。

image-20210724132028289

注意該方法是在Proxy類中是靜態方法,且接收的三個引數依次為:

  • ClassLoader loader:指定當前目標物件使用類載入器,獲取載入器的方法是固定的。
  • Class<?>[] interfaces:目標物件實現的介面的型別,使用泛型方式確認型別。
  • InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入。

有一天公司增加了業務,出售的商品越來越多,售後也需要更上。但是公司發現原來的代理商,還要再培訓才能完成全部的業務,於是就找了另外的動態代理商B代理商B 承諾無縫對接公司所有的業務,不管新增什麼業務,均不需要額外的培訓即可完成。

程式碼示例:

公司增加了維修業務:

public interface TVCompany {

    /**
     * 生產電視機
     * @return 電視機
     */
    public TV produceTV();

    /**
     * 維修電視機
     * @param tv 電視機
     * @return 電視機
     */
    public TV repair(TV tv);
}

工廠也得把維修業務搞起來:

public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米電視機","合肥");
    }

    @Override
    public TV repair(TV tv) {
        System.out.println("tv is repair finished...");
        return new TV("小米電視機","合肥");
    }
}

B代理商 全面代理公司所有的業務。使用Proxy.newProxyInstance方法生成代理物件,實現InvocationHandler中的 invoke方法,在invoke方法中通過反射呼叫代理類的方法,並提供增強方法。

public class TVProxyFactory {

    private Object target;

    public TVProxyFactory(Object o){
        this.target = o;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("TV proxy find factory for tv.... ");
                Object invoke = method.invoke(target, args);
                return invoke;
            }
        });
    }
}

購買、維修這兩個業務 B代理就可以直接搞定了。後面公司再增加業務,B代理也可以一樣搞定。

public class TVConsumer {

    public static void main(String[] args) {
        TVCompany target = new TVFactory();
        TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy();
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
    }
}

輸出結果:

TV proxy find factory for tv.... 
TV factory produce TV...
TV proxy find factory for tv.... 
tv is repair finished...

Process finished with exit code 0

小結:

  1. 代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理。

  2. 動態代理的方式中,所有的函式呼叫最終都會經過 invoke 函式的轉發,因此我們就可以在這裡做一些自己想做的操作,比如日誌系統、事務、攔截器、許可權控制等。

JDK 動態代理有一個最致命的問題是它只能代理實現了某個介面的實現類,並且代理類也只能代理介面中實現的方法,要是實現類中有自己私有的方法,而介面中沒有的話,該方法不能進行代理呼叫。

怎麼解決這個問題呢?我們可以用 CGLIB 動態代理機制。

Cglib代理

靜態代理和JDK代理都需要某個物件實現一個介面,有時候代理物件只是一個單獨物件,此時可以使用Cglib代理。

image-20210726224750356

Cglib代理可以稱為子類代理,是在記憶體中構建一個子類物件,從而實現對目標物件功能的擴充套件。

C代理商不僅想代理公司,而且還想代理多個工廠的產品。

Cglib通過Enhancer 來生成代理類,通過實現MethodInterceptor介面,並實現其中的intercept方法,在此方法中可以新增增強方法,並可以利用反射Method或者MethodProxy繼承類 來呼叫原方法。

看到 B代理商承接了公司(介面)的多種業務,那麼此時C代理商又從中發現新的商機, B 只能代理某個公司的產品,而我不僅想要代理公司產品,而且對接不同的工廠,拿貨渠道更廣,賺錢更爽快。於是Cglib就用上了。

程式碼示例:

public class TVProxyCglib implements MethodInterceptor {

    //給目標物件建立一個代理物件
    public Object getProxyInstance(Class c){
        //1.工具類
        Enhancer enhancer = new Enhancer();
        //2.設定父類
        enhancer.setSuperclass(c);
        //3.設定回撥函式
        enhancer.setCallback(this);
        //4.建立子類(代理物件)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("TVProxyFactory enhancement.....");
        Object object = methodProxy.invokeSuper(o, objects);
        return object;
    }
}

新代理的B工廠

public class TVFactoryB {

    public TV produceTVB() {
        System.out.println("tv factory B producing tv.... ");
        return new TV("華為電視機", "南京");
    }

    public TV repairB(TV tv) {
        System.out.println("tv B is repair finished.... ");
        return tv;
    }
}

C代理可以直接和公司合作,也可以和工廠打交道。並且可以代理任何工廠的產品。

public class TVConsumer {

    public static void main(String[] args) {
        TVCompany tvCompany = (TVCompany) new TVProxyCglib().getProxyInstance(TVFactory.class);
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
        System.out.println("==============================");

        TVFactoryB tvFactoryB = (TVFactoryB) new TVProxyCglib().getProxyInstance(TVFactoryB.class);
        TV tv = tvFactoryB.produceTVB();
        tvFactoryB.repairB(tv);
    }
}

輸出結果:

TVProxyFactory enhancement.....
TV factory produce TV...
TVProxyFactory enhancement.....
tv is repair finished...
==============================
TVProxyFactory enhancement.....
tv factory B producing tv.... 
TVProxyFactory enhancement.....
tv B is repair finished.... 

Process finished with exit code 0

Spring中AOP使用代理

Spring中AOP的實現有JDK和Cglib兩種,如下圖:

image-20210724133134109

如果目標物件需要實現介面,則使用JDK代理。

如果目標物件不需要實現介面,則使用Cglib代理。

總結

  1. 靜態代理:需要代理類和目標類都實現介面的方法,從而達到代理增強其功能。

  2. JDK動態代理:需要代理類實現某個介面,使用Proxy.newProxyInstance方法生成代理類,並實現InvocationHandler中的invoke方法,實現增強功能。

  3. Cglib動態代理:無需代理類實現介面,使用Cblib中的Enhancer來生成代理物件子類,並實現MethodInterceptor中的intercept方法,在此方法中可以實現增強功能。

最後

我是一個正在被打擊還在努力前進的碼農。如果文章對你有幫助,記得點贊、關注喲,謝謝!

相關文章