1. 程式人生 > >Java代理模式(2)一動態代理

Java代理模式(2)一動態代理

目錄

前言

之前Java代理模式(1)已經介紹了Java的靜態模式,其缺點也已經說明。為此引出了Java的動態代理。

一、Java動態代理與靜態代理的對比

靜態代理:

  • 是通過開發人員手動去實現介面並呼叫的;
  • 只能一個代理類實現一個介面,如果介面中的還有未實現的方法時,代理類也要必須實現。同時會產生大量重複的程式碼。

動態代理:

  • 通過反射機制實現動態代理(利用java.lang.reflect.InvocationHandlerjava.lang.reflect.Proxy
  • 可以實現多個代理物件,不再受限制與一個代理只能實現一個介面。

既然動態代理提到了反射,那麼就不得不說與動態代理有關的這兩個Java API

java.lang.reflect.InvocationHandler

  • 對委託例項進行呼叫代理時,將委託物件的方法指派到其代理物件實現的呼叫處理器InvocationHandlerinvoke方法中
  • 然後代理類就可以呼叫invoke方法完成代理,在代理過程中invoke會根據傳入的代理物件、方法名稱以及引數決定呼叫代理的哪個方法。

public interface InvocationHandler {
    //Object proxy:需要被代理的物件  
    //Method method:被代理物件委託的方法
    //Object[] args:方法呼叫時所需要引數  
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }

java.lang.reflect.Proxy

代理類是通過Proxy.newProxyInstance()來生成的,

  • ClassLoader loader: 定義代理類的類載入器
  • Class<?>[] interfaces :代理類要實現的介面列表
  • InvocationHandler h : 指派方法呼叫的呼叫處理程式,即實現InvocationHandler介面的呼叫處理程式
public
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {

二、完整舉例實現程式碼:

利用之前(1)中所舉例的例子完成一個完成的動態代理例子:

(1)目標物件實現的介面:ProblemInterface:

public interface ProblemInterface {
    void resolve();//解決問題
}

(2)目標物件:RealSubject

public class RealSubject implements ProblemInterface{

    @Override
    public void resolve() {
        System.out.println("There is an fatal bug in xxx project, It mainly includes...");
    }
}

(3)DynamicProxyHandler:實現InvocationHandler介面的呼叫處理類,用來生成代理物件、

public class DynamicProxyHandler implements InvocationHandler{

    //1.目標委託類即RealSubject
    private Object targetObject;

     /**
      * 2.委託類與代理類繫結關係:根據傳入的目標物件返回一個代理物件  
      * @param targetObject
      * @return proxyObject
      */
     public Object getProxyInstance(Object targetObject){  
            this.targetObject=targetObject;  
            //targetObject.getClass().getClassLoader():指定產生代理物件的類載入器(需要將其指定為和目標物件同一個類載入器 )
            //targetObject.getClass().getInterfaces():目標物件委託類的所有實現的介面(代理類要實現的介面序列是與目標物件是相同的)
            //this:指派方法呼叫的呼叫處理程式,即實現InvocationHandler介面的呼叫處理程式
            return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),  
                    targetObject.getClass().getInterfaces(),this);  
    }
     /**
      * 3.呼叫處理器根據這三個引數決定呼叫代理的哪個方法
      */
    //Object proxy:需要被代理的物件  
    //Method method:被代理物件委託的方法
    //Object[] args:方法呼叫時所需要引數  
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result=method.invoke(targetObject, args);//通過反射呼叫真實業務物件的業務方法,並且返回
        return result;
    }
}

(4)客戶端測試類:DynamicClient

public class DynamicClient {

    public static void main(String[] args) {
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
        //傳入目標物件new RealSubject()之後獲得動態代理物件
        ProblemInterface proxy = (ProblemInterface)dynamicProxyHandler.getProxyInstance(new RealSubject());
        //代理目標物件的業務
        proxy.resolve();
    }
}

輸出結果為如下,RealSubject目標物件的resolve()方法得到了代理

There is an fatal bug in xxx project, It mainly includes…

之後我們在ProblemInterface中隨便增加add()方法來更加深入理解靜態代理與動態代理的的區別

public interface ProblemInterface {
    void resolve();//解決問題
    void add();
}

真實物件RealSubject必須實現add()方法:

public class RealSubject implements ProblemInterface{

    @Override
    public void resolve() {
        System.out.println("There is an fatal bug in xxx project, It mainly includes...");
    }
    public void add() {
        System.out.println("This is add() method");
    }
}

Java靜態代理:Proxy必須還要實現add()方法,才能代理成功

public class Proxy implements ProblemInterface{
    private ProblemInterface problemInterface = new RealSubject();//小李的問題已經得到反映
    @Override
    public void resolve() {
        System.out.println("I'm preparing for it");//領導A先準備好材料
        problemInterface.resolve();//開始與經理B溝通反映
        System.out.println("In a word, I think the problem...");//結束後領導A做了總結
    }
    @Override
    public void add() {
        problemInterface.add();//增加的方法
    }

}

而在動態代理中,只需要需要委託的目標物件實現add()方法之後,再增加一個方法呼叫proxy.add()即可

public class DynamicClient {

    public static void main(String[] args) {
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
        //傳入目標物件new RealSubject()之後獲得動態代理物件
        ProblemInterface proxy = (ProblemInterface)dynamicProxyHandler.getProxyInstance(new RealSubject());
        //代理目標物件的業務
        proxy.resolve();
        //增加方法
        proxy.add();
    }
}

再比如我們再需要代理另外一個目標物件RealSubject2的業務:

public class RealSubject2 implements ProblemInterface{

    @Override
    public void resolve() {
        System.out.println("I am RealSubject2");
    }
    public void add() {
        System.out.println("This is add() method");
    }
}

如果是靜態代理還得像Java代理模式(1)一樣再建立一個Poxy2代理類代理RealSubject2,這樣會很麻煩,程式碼量自然會增加。而使用動態代理方式後,只需要再建立呼叫處理類然後再傳入需要代理的RealSubject2目標物件即可。

public class DynamicClient {

    public static void main(String[] args) {
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
        DynamicProxyHandler dynamicProxyHandler2 = new DynamicProxyHandler();
        //傳入目標物件new RealSubject()之後獲得動態代理物件
        ProblemInterface proxy = (ProblemInterface)dynamicProxyHandler.getProxyInstance(new RealSubject());
        //再代理new RealSubject2()
        ProblemInterface proxy2 = (ProblemInterface)dynamicProxyHandler2.getProxyInstance(new RealSubject2());
        //RealSubject的需要代理的業務
        proxy.resolve();
        proxy.add();
        //RealSubject2的需要代理的業務
        proxy2.resolve();
        proxy2.add();
    }
}

最後的代理結果:

There is an fatal bug in xxx project, It mainly includes...
This is add() method
I am RealSubject2
This is add() method

總結

雖然JDK的動態代理已經基本滿足代理要求,但只是侷限於實現介面的被代理類,而在大多數的業務情況下如果遇到需要代理並沒有實現介面類的目標物件時,應該使用CGLib動態代理(Java代理模式(3)一CGLib代理)。典型框架Spring AOP就運用了JDK動態代理與CGLib動態代理兩者的結合。