Java動態代理

1、簡介

在java常用的設計模式中有一種模式叫:代理模式----通過一個代理類負責處理被代理類(委託類)的調用,如果你瞭解或使用過Spring的切面(AOP),可以更好的理解何爲代理,通俗的講就是在一個類的方法執行的前後加入操作(如進入和完成方法打印日誌),實際上Spring正是使用java的動態代理來實現切面的;一般代理類和委託類實現同樣的接口,代理類通過調用委託類對象的方法真正的實現服務,並且可以在實現的前後增加需要的處理。

從代理創建時期區分,java的代理可以分爲兩種:
靜態代理:代理類在程序運行前就已經存在;
動態代理:代理類在程序的運行期間才創建;

2、靜態代理
靜態代理方式更好理解,我們先簡單實用測試代碼說明,以手機生產爲例:一般手機廠商都不是通過自己整機生產手機,而是由代工廠代理生產;
/**
 * 手機生產接口
 * @author hwz
 *
 */
public interface PhoneBuilder {

    public void buildAPhone(); 
}
/**
 * 手機生產廠商
 * @author hwz
 *
 */
public class PhoneVendor implements PhoneBuilder{

    @Override
    public void buildAPhone() {
        //設計和生產手機
        System.out.println("design and build a phone");
    }
}
/**
 * 代理工廠類
 * @author hwz
 *
 */
public class PhoneBuilderProxyFactory implements PhoneBuilder {

    private PhoneBuilder phoneBuilder;//代理類一把通過聚合持有委託類的一個實例

    public PhoneBuilderProxyFactory(PhoneBuilder phoneBuilder) {
        this.phoneBuilder = phoneBuilder;
    }

    @Override
    public void buildAPhone() {

        phoneBuilder.buildAPhone();
        //組裝手機
        System.out.println("phone  assemble and produce...");
    }
}
/**
 * 靜態代理測試
 * @author hwz
 *
 */
public class StaticProxyTest {

    public static void main(String[] args) {

        PhoneVendor realBuilder = new PhoneVendor();

        PhoneBuilderProxyFactory proxyBuilder = new PhoneBuilderProxyFactory(realBuilder);

        proxyBuilder.buildAPhone();
    }
}

可以看出,靜態代理的優點可以解耦調用方和委託類,在不修改委託類的源碼情況下增加處理,但是靜態代理的代理類代碼是必須在程序編譯運行前編寫好。

3、動態代理
簡單介紹完靜態代理,下面重點介紹java動態代理:
顧名思義,動態代理的代理類是在程序運行中動態創建的,也就是說代理類並不是由我們通過代碼定義的,
下面我們通過以下需求來詳細說明:
對所有被調用的外部類的方法全部在方法開始前打印入參日誌,方法執行完成後打印返回結果日誌
先按照靜態代理的方式實現:
/**
 * 一個簡單的外部接口
 * @author hwz
 */
public interface OneOuterInterface {
    /**
     * 方法一:翻轉字符串
     * @param str
     * @return
     */
    public String methodOne(String str);
    /**
     * 方法二:計算兩個數的乘積
     * @param a
     * @param b
     * @return
     */
    public int methodSecond(int a, int b);
}
/**
 * 簡單的外部類
 * @author hwz
 *
 */
public class OneOuterClass implements OneOuterInterface {

    @Override
    public String methodOne(String str) {
        return new StringBuilder(str).reverse().toString();
    }

    @Override
    public int methodSecond(int a, int b) {
        return a*b;
    }
}
/**
 * 代理類
 * @author hwz
 *
 */
public class InnerProxyClass implements OneOuterInterface {

    private OneOuterInterface outer;

    public InnerProxyClass(OneOuterInterface outer) {
        this.outer = outer;
    }

    @Override
    public String methodOne(String str) {
        System.out.println("Enter methodOne,str=" + str);
        String result = outer.methodOne(str);
        System.out.println("Leave methodOne,result=" + result);
        return result;
    }

    @Override
    public int methodSecond(int a, int b) {
        System.out.println("Enter methodSecond,a=" + a + ",b=" + b);
        int result = outer.methodSecond(a, b);
        System.out.println("Leave methodSecond,result=" + result);
        return result;
    }
}
/**
 * 測試代碼
 * @author hwz
 *
 */
public class ProxyOuterClassTest {

    public static void main(String[] args) {

        InnerProxyClass proxy = new InnerProxyClass(new OneOuterClass());
        proxy.methodOne("abcdef");
        proxy.methodSecond(2, 3);
    }
}

通過這樣的靜態代理方式,實現簡單,也容易理解,但是對於這個增加日誌打印的需求,這種實現方式明顯不好,每個外部接口都要實現一個代理類,接口中的每個方法都要增加日記打印語句,這樣的方式重複工作量大,繁瑣,誰都不願意這樣實現;
通過動態代理方式就可以高效的實現上面的需求:
動態代理類的字節碼在程序運行中由java的反射機制生成,具有更好的擴展性;java.lang.reflect包下Proxy類和InvocationHandler接口提供實現動態代理的方式:

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

invoke參數:
proxy  :  生成的代理類實例對象
method : 實際要調用的方法
args : 方法調用時的參數
InvocationHandler 接口可以理解爲“調用處理器”,但是具體的實現有程序員決定;

Proxy類,是實際完成代理的操作類,由這個類的方法動態生成代理對象,使用如下方法:

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

loader:類加載器
interfaces:需要代理的類的全部接口
handler:上面InvocationHandler的實例

下面我們通過動態代理來實現上面的需求(打印日誌):
/**
 * 代理處理器
 * @author hwz
 *
 */
public class OutInterfaceInvokeHandler implements InvocationHandler {

    private Object proxyObj;

    public Object proxy(Object proxyObj) {
        this.proxyObj = proxyObj;
        return Proxy.newProxyInstance(proxyObj.getClass().getClassLoader(), proxyObj.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        StringBuilder builder = new StringBuilder();
        if (args != null) {
            for (int i=0; i<args.length; ++i) {
                builder.append(",arg" + i + "=" + args[i].toString());
            }
        }
        System.out.println("Enter " + method.toString() + ",args:" + builder.toString());
        //此處proxy是生成代理類的實例
        Object result = method.invoke(proxyObj, args);

        System.out.println("Leave " + method + ",result=" + result.toString());
        return result;
    }
}
/**
 * 測試代碼
 * @author hwz
 *
 */
public class ProxyOuterClassTest {

    public static void main(String[] args) {

        OneOuterInterface outer = new OneOuterClass();
        OutInterfaceInvokeHandler handler = new OutInterfaceInvokeHandler();
        OneOuterInterface proxy = (OneOuterInterface) handler.proxy(outer);

        proxy.methodOne("abcdef");
        proxy.methodSecond(2, 4);
    }
}
注意:InvocationHandler.invoke方法的第一個入參是動態生成代理類的實例對象引用,直接使用這個proxy調用方法會發生無限循環調用

動態代理方式能可以更加高效的實現需求,不需要每個類都寫一個代理類;
通過以上的示例代碼可以看出,java的動態代理是必須基於接口實現的,當然這也非常符合java編程的規範;



發佈了50 篇原創文章 · 獲贊 8 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章