1. 程式人生 > >Java反射機制與動態代理(二)

Java反射機制與動態代理(二)

導讀:
1. 代理模式
2. 靜態代理
3. 動態代理
4. 小結

一、代理模式

代理模式所涉及的角色有:

  • 抽象主題角色:聲明瞭真實主題和代理主題的共同介面,這樣一來在任何可以使用真實主題的地方都可以使用代理主題。
  • 真實主題角色:定義了代理角色所代表的真實物件。
  • 代理主題(Proxy)角色:代理主題角色內部含有對真實主題的引用,從而可以在任何時候操作真實主題物件;代理主題角色提供了一個與真實主題角色相同的介面,以便可以在任何時候都可以替代真實主題;控制對真實主題物件的引用,負責在需要的時候建立真實主題物件;代理角色通常在將客戶端呼叫傳遞給真實物件的主題之前或者之後,都是執行某個操作,而不是簡單地將呼叫傳遞給真實物件主題。

代理模式的一個簡單的示意性實現,實現的類圖如下圖所示:
這裡寫圖片描述

二、靜態代理

  設計模式中通常提到的代理模式,指的就是靜態代理。下面通過懶載入的例子來了解Java中代理模式是如何實現的。

  所謂懶載入,就是延遲載入,核心思路是如果當前物件沒有真正地使用某個物件時,就不去建立這個物件,而是在真正需要使用該物件的時候才去建立真正的物件。懶載入的作用主要是降低系統初始化時建立的物件過多而影響系統性能。

1. 抽象主題角色定義
package staticproxy;
/*
 * 抽象主題角色
 */
public abstract class Subject {
    /*
     * 宣告一個抽象的請求方法
     */
public abstract void request(); }
2. 真實主題角色定義
package staticproxy;
/*
 * 真正主題角色
 */
public class RealSubject extends Subject {

    /*
     * 構造器
     */
    public RealSubject() { }
    /*
     * 實現請求方法
     */
    @Override
    public void request() {
        System.out.println("call real subject's request"
); } }
3. 代理主題角色定義
package staticproxy;
/*
 * 代理主題角色
 * 
 * 靜態代理,對具體真實物件直接引用代理角色,代理角色需要有對真實角色的引用,代理做真實角色要做的事。
 */
public class StaticProxySubject extends Subject {
    private RealSubject realSubject;

    /*
     * 建構函式
     */
    public StaticProxySubject() {    }

    /*
     * 實現請求方法
     */
    @Override
    public void request() {

        preRequest();        //請求前的操作

        //延遲載入RealSubject物件
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        realSubject.request();

        postRequest();        //請求後的操作
    }

    /*
     * 請求前的操作
     */
    public void preRequest() {
        System.out.println("something to do before requesting");
    }

    /*
     * 請求後的操作
     */
    public void postRequest() {
        System.out.println("something to do after requesting");
    }
}
4. 客戶端呼叫
package staticproxy;
/*
 * 客戶端呼叫
 */
public class Client {
    public static void main(String[] args) {
        Subject sub = new StaticProxySubject();
        sub.request();    //代理者代替真實者做事情
    }
}
5. 執行結果
something to do before requesting
call real subject's request
something to do after requesting

三、動態代理

從靜態代理的實現中,當被代理的物件比較多,方法也比較多的時候,代理類的實現就比較繁瑣,需要對每一個真實角色建立相應的代理類。動態代理機制的出現可以動態生成代理類,從而使代理模式的實現更加優雅。

1. 動態代理的實現

JDK動態代理中包含一個類和一個介面,即Proxy類和InvocationHandler介面。
(1)Proxy類:Proxy類是專門完成代理的操作類,可以通過此類為一個或多個介面動態地生成類。

public static Object newProxyInstance(ClassLoader                    loader,Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException

引數說明:①ClassLoader loader:類載入器;②Class<?>[ ] interface:得到代理類的全部介面;③InvocationHandler h:得到InvocationHandler介面的子類例項。

(2)InvocationHandler介面:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable; 
}

引數說明:①Object proxy:指被代理的物件;②Method method:要呼叫的方法;③Object[ ] args:呼叫的方法所需的引數。

2. 動態代理的建立

使用Proxy類建立動態代理的例項:

Objectsubject= Proxy.newProxyInstance(classloader, interfaces, handler);

等價於使用Proxy的方式:

//生成動態代理的物件
Class<?> c = Proxy.getProxyClass(classloader, interfaces);
//通過反射獲取生成的動態代理類的構造方法
Constructor<?> ct = c.getConstructor(new Class[] {InvocationHandler.class});
//通過建構函式物件生成動態代理類的例項
Subject subject = (Subject)ct.newInstance(new Object[]{handler});
3. JDK動態代理例項

(1)抽象主題角色

package dynamicproxy;
/*
 * 抽象主題角色
 * 抽象類應改為介面
 */
public interface Subject {
    /*
     * 宣告一個抽象的請求方法
     */
    void request();
}

(2)真實主題角色

package dynamicproxy;
/*
 * 真正主題角色
 */
public class RealSubject implements Subject {

    public RealSubject() { }
    /*
     * 實現請求方法
     */
    @Override
    public void request() {
        System.out.println("call real subject's request");
    }
}

(3)動態代理主題角色定義

package dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/*
 * 動態代理主題角色
 */
public class DynamicProxySubject implements InvocationHandler {
    private Object obj;

    public DynamicProxySubject(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before call: " + method);
        method.invoke(obj, args);
        System.out.println("after call: " + method);
        return null;
    }
}

(4)客戶端呼叫

package dynamicproxy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {

    public static void main(String[] args) throws Exception {
        RealSubject realSubject = new RealSubject();
        InvocationHandler handler = new DynamicProxySubject(realSubject);

        Class<?> cls = realSubject.getClass();

       //建立動態代理例項的方式一
        //生成動態代理的物件
        Class<?> c = Proxy.getProxyClass(cls.getClassLoader(), cls.getInterfaces());
        //通過反射獲取生成的動態代理類的構造方法
        Constructor<?> ct = c.getConstructor(new Class[] {InvocationHandler.class});
        //通過建構函式物件生成動態代理類的例項
        Subject subject = (Subject)ct.newInstance(new Object[]{handler});

        //建立動態代理例項的方式二
        Subject subject2 = (Subject)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), handler); 

        subject.request();
    }
}

(5)執行結果

before call: public abstract void dynamicproxy.Subject.request()
call real subject's request
after call: public abstract void dynamicproxy.Subject.request()

四、小結

  Java中的動態代理模式通過動態位元組碼生成、反射機制等技術,使得開發人員更為優雅地實現代理模式,動態代理在很多技術框架中都有使用,如Spring中的AOP、一些RPC框架等,出了Java自帶的動態代理外,也可以使用CGLIB等類似的庫。

【參考資料】