java代理模式(jdk proxy)

什麼是代理

舉個栗子

比如有一傢美國的大學,可以對全世界招生。但是對於傢長來說,傢長不能直接自己去找學校,傢長沒有能力去直接訪問學校,或者說,美國學校不接受個人來訪,那麼此時就需要一個留學中介來幫助這傢美國學校招

生,中介就是學校的代理。中介和學校要做的事情是一致:招生。對於傢長來說,學校就是目標,留學中介就是代理。日常生活中,有許多代理的例子,比如:代購,房產中介,各種中介,換ip,商傢廠傢和買傢。在開發中

也有同樣的情況,比如,你有a類, 本來是調用c類的方法, 完成某個功能。 但是c不讓a調用。 a —–不能調用 c的方法。在a 和 c 直接 創建一個 b 代理, c讓b訪問。 a –訪問b—訪問c。

原來的訪問關系

通過代理的訪問關系

什麼是代理模式

百度百科

代理模式是指,為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶類和目標對象之間起到中介的作用。

換句話說,使用代理對象,是為瞭在不修改目標對象的基礎上,增強主業務邏輯。客戶類真正的想要訪問的對象是目標對象,但客戶類真正可以訪問的對象是代理對象。客戶類對目標對象的訪問是通過訪問代理對象來實現

的。當然,代理類與目標類要實現同一個接口。

實現代理的方式

靜態代理

靜態代理是指,代理類在程序運行前就已經定義好.java 源文件,其與目標類的關系在程序運行前就已經確立。在程序運行前代理類已經編譯為.class 文件。

舉一個靜態代理的例子

需求:用戶需要購買 u 盤,u 盤廠傢不單獨接待零散購買,廠傢規定一次最少購買 1000個以上,用戶可以通過淘寶的代理商,或者微商哪裡進行購買。淘寶上的商品,微商都是 u 盤工廠的代理商, 他們代理對 u 盤的銷售業

務。用戶購買——-代理商(淘寶,微商)—– u 盤廠傢(金士頓,閃迪等不同的廠傢)

1、定義業務接口

定義業務接口UsbSell(目標接口),其中含有抽象方法sell(int amout);sell是目標方法

public interface UsbSell {
    /**
     * 表示功能的,廠傢和商傢都要完成的功能
     * @param amount
     * @return
     */
    float sellUsb(int amount);
}

2、定義接口的實現類

目標類UsbKingFactory金士頓U盤,該類實現瞭接口

import school.xauat.service.UsbSell;
public class UsbKingFactory  implements UsbSell {
    @Override
    /**
     * 定義金士頓廠傢的銷售價格
     */
    public float sellUsb(int account) {
        return 75.0f;
    }
}

3、定義代理

TaoBao就是一個代理類,代理廠傢銷售U盤

import school.xauat.factory.UsbKingFactory;
import school.xauat.service.UsbSell;
public class Taobao implements UsbSell {
    //聲明 商傢代理的廠傢具體是哪一傢
    private UsbSell factory=new UsbKingFactory();
    @Override
    /**
     * 實現銷售U盤的功能
     */
    public float sellUsb(int account) {
        float price=factory.sellUsb(account);
        //代理增強功能
        price+=25;
        return price;
    }
}

WeiShang也是一個代理類代理廠傢銷售U盤

import school.xauat.factory.UsbKingFactory;
import school.xauat.service.UsbSell;
public class Weishang implements UsbSell {
    //聲明 商傢代理的廠傢具體是哪一傢
    private UsbSell factory=new UsbKingFactory();
    @Override
    public float sellUsb(int amount) {
        float price=factory.sellUsb(amount);
        //代理增強功能
        price+=15;
        return price;
    }
}

4、客戶端調用者,購買商品類

客戶端可以通過Taobao和WeiShang兩個代理來購買U盤

import school.xauat.business.Taobao;
import school.xauat.business.Weishang;
public class ShopMain {
    public static void main(String[] args) {
        Taobao taoBao=new Taobao();
        float price=taoBao.sellUsb(1);
        System.out.println(price);
        Weishang weishang=new Weishang();
        float price2=weishang.sellUsb(1);
        System.out.println(price2);
    }
}

根據以上過程,分析靜態代理的優缺點

優點:實現簡單,易於理解

缺點:

代碼復雜,難於管理

代理類和目標類實現瞭相同的接口,每個代理都需要實現目標類的方法,這樣就出現瞭大量的代碼重復。如果接口增加一個方法,除瞭所有目標類需要實現這個方法外,所有代理類也需要實現此方法。增加瞭代碼維護的復雜度。

代理類依賴目標類,代理類過多

代理類隻服務於一種類型的目標類,如果要服務多個類型。勢必要為每一種目標類都進行代理,靜態代理在程序規模稍大時就無法勝任瞭,代理類數量過多。

動態代理

動態代理是指代理類對象在程序運行時由 JVM 根據反射機制動態生成的。動態代理不需要定義代理類的.java 源文件。

動態代理其實就是 jdk 運行期間,動態創建 class 字節碼並加載到 JVM。

動態代理的實現方式常用的有兩種:使用 JDK 動態代理(這裡主要講),與通過 CGLIB 動態代理

CGLIB代理

CGLIB(Code Generation Library)是一個開源項目。是一個強大的,高性能,高質量的 Code 生成類庫,它可以在運行期擴展 Java 類與實現 Java 接口。它廣泛的被許多 AOP 的框架使用,例如 Spring AOP。使用 JDK 的

Proxy 實現代理,要求目標類與代理類實現相同的接口。若目標類不存在接口,則無法使用該方式實現。但對於無接口的類,要為其創建動態代理,就要使用 CGLIB 來實現。CGLIB 代理的生成原理是生成目標類的子類,而

子類是增強過的,這個子類對象就是代理對象。所以,使用CGLIB 生成動態代理,要求目標類必須能夠被繼承,即不能是 final 的類。cglib 經常被應用在框架中,例如 Spring ,Hibernate 等。Cglib 的代理效率高於 Jdk。對

於 cglib 一般的開發中並不使用。做瞭一個瞭解就可以。

JDK代理

jdk 動態代理是基於 Java 的反射機制實現的。使用 jdk 中接口和類實現代理對象的動態創 建。 Jdk 的動態要求目標對象必須實現接口,這是 java 設計上的要求。 從 jdk1.3 以來,java 語言通過 java.lang.reflect 包提供三個類

支持代理模式 Proxy, Method InovcationHandler

InvocationHandler接口

InvocationHandler 接口叫做調用處理器,負責完調用目標方法,並增強功能。通 過 代 理 對 象 執 行 目 標 接 口 中 的 方 法 , 會 把 方 法 的 調 用 分 派 給 調 用 處 理 器(InvocationHandler)的實現類,執行實現類中的 i

nvoke()方法,我們需要把功能代理寫在 invoke()方法中 。

在 invoke 方法中可以截取對目標方法的調用。在這裡進行功能增強。Java 的動態代理是建立在反射機制之上的。實現瞭 InvocationHandler 接口的類用於加強目標類的主業務邏輯。這個接口中有一個方法 invoke(),具體加

強的代碼邏輯就是定義在該方法中的。通過代理對象執行接口中的方法時,會自動調用 invoke()方法。

invoke()方法的介紹如下:

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

proxy:代表生成的代理對象

method:代表目標方法

args:代表目標方法的參數

第一個參數 proxy 是 jdk 在運行時賦值的,在方法中直接使用,第二個參數後面介紹,第三個參數是方法執行的參數, 這三個參數都是 jdk 運行時賦值的,無需程序員給出。

Method類

invoke()方法的第二個參數為 Method 類對象,該類有一個方法也叫 invoke(),可以調用目標方法。這兩個 invoke()方法,雖然同名,但無關。

public Object invoke ( Object obj, Object... args)

obj:表示目標對象

args:表示目標方法參數,就是其上一層 invoke 方法的第三個參數

該方法的作用是:調用執行 obj 對象所屬類的方法,這個方法由其調用者 Method 對象確定。

在代碼中,一般的寫法為

method.invoke(target, args);

其中,method 為上一層 invoke 方法的第二個參數。這樣,即可調用瞭目標類的目標方法。

Proxy類

通 過 JDK 的 java.lang.reflect.Proxy 類 實 現 動 態 代 理 , 會 使 用 其 靜 態 方 法newProxyInstance(),依據目標對象、業務接口及調用處理器三者,自動生成一個動態代理對象。

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

loader:目標類的類加載器,通過目標對象的反射可獲取

interfaces:目標類實現的接口數組,通過目標對象的反射可獲取

handler:調用處理器。

jdk動態代理的實現步驟

jdk 動態代理是代理模式的一種實現方式,其隻能代理接口。

實現步驟

1、新建一個接口,作為目標接口

2、為接口創建一個實現類,是目標類

3、創建類實現 java.lang.reflect.InvocationHandler 接口,調用目標方法並增加其他功能代碼

4、創建動態代理對象,使用 Proxy.newProxyInstance()方法,並把返回值強制轉為接口類型。

舉例

1、創建目標接口,定義目標接口功能

2、為接口創建實現類

以上兩步同靜態代理

3、創建InvocationHandler接口的實現類,調用目標方法和增加其他代碼功能

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MySellHandler implements InvocationHandler {
    //目標對象
    private Object target=null;
    public MySellHandler(Object target){
        this.target=target;
    }
    @Override
    /**
     * 實現InvocationHandler接口的實現類,在invoke方法中完成代理類的功能
     *      -調用目標方法
     *      -功能增強
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result=null;
        result=method.invoke(target,args);

        //功能增強
        //這裡為瞭簡化,我們將功能增強定義為加價25元
        if (result!=null){
            float price=(float)result;
            price=price+25;
            result=price;
        }
        return result;
    }
}

這裡的target對象相當於靜態代理中的TaoBao和WeiShang

4、模擬客戶購買U盤,使用proxy.newProxyInstance創建Proxy代理對象並且使返回值為目標接口類型

import school.xauat.factory.UsbKingFactory;
import school.xauat.handler.MySellHandler;
import school.xauat.service.UsbSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class MainShop {
    public static void main(String[] args) throws Exception {
        //創建目標對象
        Class c=UsbKingFactory.class;
        Object obj=c.newInstance();
        //獲得目標類的類加載器
        ClassLoader loader =UsbKingFactory.class.getClassLoader();
        //獲取目標類實現的接口數組
        Class<?>[]interfaces=obj.getClass().getInterfaces();
        //創建InvocationHandler對象
        InvocationHandler handler=new MySellHandler(obj);
        //創建代理對象
        UsbSell proxy=(UsbSell) Proxy.newProxyInstance(loader,interfaces,handler);
        //通過這個代理執行方法
        float price=proxy.sell(1);
        System.out.println(price);
    }
}

靜態代理

動態代理

UML圖

總結

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: