通俗易懂詳解Java代理及程式碼實戰

阿豪聊乾貨發表於2017-09-15

一、概述

  理模式是Java常用的設計模式之一,實現代理模式要求代理類和委託類(被代理的類)具有相同的方法(提供相同的服務),代理類物件自身並不實現真正的核心邏輯,而是通過呼叫委託類物件的相關方法來處理核心邏輯,而代理類物件主要負責為委託類物件過濾訊息、預處理訊息、轉發訊息給委託類、事後處理訊息等等。通常代理類會與委託類存在關聯關係。

  按照代理的建立時期,代理可分為:靜態代理和動態代理。靜態代理由開發者手動建立,在程式執行前,已經存在;而動態代理不需要手動建立,它是在程式執行時動態的建立代理類。

二、靜態代理

  我們直接用程式碼來說明什麼叫靜態代理,場景是我要賣掉我的車子,但是由於我很忙,所以賣掉車子的過程中不想每天被電話騷擾,於是我就在附近找了一個二手車交易的中介,希望在他的幫助下很輕鬆的賣掉車子。
1.賣車子介面
public interface SaleCar {
    void sale();
}

2.hafiz真正賣車子實現類

public class HafizSaleCar implements SaleCar {
    @Override
    public void sale() {
        System.out.println("hafiz sale his car...");
    }
}

3.二手車交易中介類

public class CarTradeProxy implements SaleCar {
    private HafizSaleCar owner;
    public CarTradeProxy(HafizSaleCar owner) {
        this.owner = owner;
    }
    @Override
    public void sale() {
        System.out.println("proxy add price...");
        owner.sale();
    }
}

4.測試類

public class Client {
    public static void main(String[] args) {
        HafizSaleCar owner = new HafizSaleCar();
        CarTradeProxy proxy = new CarTradeProxy(owner);
        proxy.sale();
    }
}

5.測試結果

從上面的程式碼中,我們可以看出,其實代理類(CarTradeProxy)和委託類(HafizSaleCar)好像區別並不大,我們直接建立一個HafizSaleCar物件,然後呼叫它的sale()方法不就好了?細心的同學你會發現,其實代理在真正呼叫委託類的方法之前做了中介加價的操作,這也就意味著我們使用代理模式實現在委託類的基礎上增加額外的邏輯操作。

  以上就是一個很簡單的靜態代理的實現過程。但是這個時候我又有了一個新需求,我想用我手裡的存款以及買車子賺的錢來給自己買一套新房子,那我又不想東奔西跑找房源,於是我又把買房這件事委託給了房產中介,下面我們就來實現這個邏輯。

1.再定義一個買房的介面

public interface BuyHouse {
    void buy();
}

2.重寫委託類,實現賣車和買房兩個介面

public class HafizTrade implements SaleCar, BuyHouse {
    @Override
    public void buy() {
        System.out.println("hafiz buy house...");
    }
    @Override
    public void sale() {
        System.out.println("hafiz sale car...");
    }
}

可以看到,我現在既要賣掉我的車子,又要購買新的房子。

3.再建立一個買房子的中介代理類

public class HouseTradeProxy implements BuyHouse {
    private HafizTrade customer;
    public HouseTradeProxy(HafizTrade customer) {
        this.customer = customer;
    }
    @Override
    public void buy() {
        System.out.println("proxy add price...");
        customer.buy();
    }
}

4.賣車子的代理類修改如下

public class CarTradeProxy implements SaleCar {
    private HafizTrade owner;
    public CarTradeProxy(HafizTrade owner) {
        this.owner = owner;
    }
    @Override
    public void sale() {
        System.out.println("proxy add price...");
        owner.sale();
    }
}

5.新的測試類

public class Client {
    public static void main(String[] args) {
        HafizTrade trader = new HafizTrade();
        CarTradeProxy carTradeProxy = new CarTradeProxy(trader);
        carTradeProxy.sale();
        System.out.println("-----------------------------------------------");
        HouseTradeProxy houseTradeProxy = new HouseTradeProxy(trader);
        houseTradeProxy.buy();
        System.out.println("-----------------------------------------------");
    }
}

6.測試結果

這樣通過靜態代理的方式,我們的確也可以很完美的解決我們的問題,但當我們有越來越多的委託類需要代理,而且代理做的工作又一樣,那是不是會多出來很多的代理類,我們開發者會瘋掉的,這時候我們就想:如果我們可以只做一次,就能代理一類委託類該多好啊?那麼這個時候,動態代理就應運而生了,它可以使得我們只定義一次就能為一類委託類做代理。

三、動態代理

  靜態代理要求我們在程式釋出上線執行之前,就要開發好對應委託類的代理類,而動態代理是我們在程式釋出之前,並沒有建立好對應的代理類,而是在執行的時候動態的建立代理類。
  動態代理實現方式有兩種:jdk自帶動態代理實現以及cglib實現。jdk代理只適合代理實現介面的目標物件,cglib可以代理沒有實現介面的目標物件。

四、基於JDK實現動態代理

1.實現步驟

  1).通過實現 InvocationHandler 介面建立自己的呼叫處理器

  2).通過為 Proxy 類指定 ClassLoader 物件和一組 interface 來建立動態代理類

  3).通過反射機制獲得動態代理類的建構函式(jdk自帶,不需手動處理)

  4).通過建構函式建立動態代理類例項,構造時呼叫處理器物件作為引數被傳入(jdk自帶,不需手動處理)

2.建立代理處理器

public class ProxyHandler implements InvocationHandler {
    private Object target;
    public ProxyHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy add price...");
        Object result = method.invoke(target, args);
        return result;
    }
}

3.測試類

public class Client {
    public static void main(String[] args) {
        HafizTrade trader = new HafizTrade();
        ProxyHandler handler = new ProxyHandler(trader);
        Class<? extends HafizTrade> clazz = trader.getClass();
        ClassLoader classLoader = clazz.getClassLoader();
        Class<?>[] interfaces = clazz.getInterfaces();
        SaleCar carProxy = (SaleCar)Proxy.newProxyInstance(classLoader, interfaces, handler);
        carProxy.sale();
        System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++");
        BuyHouse houseProxy = (BuyHouse)Proxy.newProxyInstance(classLoader, interfaces, handler);
        houseProxy.buy();
        System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++");

    }
}

4.測試結果

5.原理

  生成一個代理類,這個代理類繼承Proxy類並且實現了我們定義的介面,代理物件呼叫方法的時候,呼叫這個代理物件的一個成員InvocationHandler(上面我們傳入了一個InvocationHandler實現物件)的方法,也就是我們包裝了委託類後的方法。

五、基於cglib實現動態代理

1.實現步驟

  1).通過實現CGLib包提供的MethodInterceptor介面,重寫intercept方法,建立自己的方法攔截器

  2).通過CGLib中的Enhancer的creat方法建立動態代理物件

2.新增cglib的maven依賴

 <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

3.自定義ProxyInterceptor

public class ProxyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Trade proxy add price...");
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }
}

4.測試client

public class Client {
    public static void main(String[] args) {
        ProxyInterceptor proxy = new ProxyInterceptor();
        HafizTrade tradeProxy = (HafizTrade)Enhancer.create(HafizTrade.class, proxy);
        tradeProxy.sale();
        System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++");
        tradeProxy.buy();
    }
}

5.測試結果

6.原理

  首先通過asm位元組碼生成框架生成代理類Class的二進位制位元組碼,然後通過Class.forName載入二進位制位元組碼,生成Class物件,最後通過反射機制獲取例項構造,並初始化代理類物件。

六、總結

  動態代理可以使得我們一次可以解決一批需要建立代理的問題,使得程式碼更加靈活,提高了程式的擴充套件性。動態代理在主流java框架中也非常常用,比如最著名的spring,它在AOP的功能就是使用動態代理實現,還有Dubbo等這樣的RPC服務框架,在客戶端都是通過代理完成服務的真正呼叫。瞭解和學會代理以及實現方式能幫助我們更好地理解主流框架。

  關於動態代理的實現細節,可以參考:http://www.360doc.com/content/14/0801/14/1073512_398598312.shtml#

相關文章