Java 動(dòng)態(tài)代理

先了解下代理模式的概念:為其他對(duì)象提供一種代理,以控制對(duì)這個(gè)對(duì)象的訪問。也就是通過一個(gè)新的對(duì)象去代表目標(biāo)對(duì)象,再通過新對(duì)象間接去訪問目標(biāo)對(duì)象的功能,主要的作用有:

  • 增強(qiáng)目標(biāo)對(duì)象的功能
  • 保護(hù)目標(biāo)對(duì)象,屏蔽掉一些敏感的功能

具體的實(shí)現(xiàn)可以采用靜態(tài)代理或者動(dòng)態(tài)代理兩種方案,我們?cè)鰪?qiáng)目標(biāo)對(duì)象的功能為例來學(xué)習(xí)動(dòng)態(tài)代理。

注意,有些說法是“代理模式一般是內(nèi)部創(chuàng)建被代理的對(duì)象” ,你這例子是裝飾者模式,但我認(rèn)為這并不是必須,更多的時(shí)候需要根據(jù)業(yè)務(wù)去權(quán)衡,比如在某些場(chǎng)景上要求代理類有一定通用性,目標(biāo)對(duì)象是可以依賴注入的,就需要變通了。

一、靜態(tài)代理

首先定義一個(gè)Shop接口,作用就是售賣物品:

public interface Shop {
    void sale(String name);
}

再定義一個(gè)CoffeeShop類,實(shí)現(xiàn)Shop接口,專門售賣咖啡:

public class CoffeeShop implements Shop {
    public void sale(String name) {
        System.out.println("開始制作" + name + "......制作完成!");
    }
}

最后定義代理類,需要通過構(gòu)造函數(shù)注入目標(biāo)對(duì)象,調(diào)用目標(biāo)對(duì)象的售賣方法,并在售賣的前后添加問候語:

public class StaticProxy implements Shop {
    private Shop shop;

    public StaticProxy(Shop shop) {
        this.shop = shop;
    }

    public void sale(String name) {
        System.out.println("歡迎!");
        shop.sale(name);
        System.out.println("再見!");
    }
}
public class ProxyTest {
    @Test
    public void staticProxyTest() {
        Shop shop = new CoffeeShop();
        StaticProxy staticProxy = new StaticProxy(shop);
        staticProxy.sale("拿鐵");
    }
}

測(cè)試結(jié)果如下:


動(dòng)態(tài)代理我們主要學(xué)習(xí)兩種:JDK動(dòng)態(tài)代理、CGLIB動(dòng)態(tài)代理,在上邊的例子基礎(chǔ)上進(jìn)行改進(jìn)。

二、JDK動(dòng)態(tài)代理

JDK動(dòng)態(tài)代理主要依賴java.lang.reflect包下的相關(guān)類實(shí)現(xiàn),但要求被代理類必須最少實(shí)現(xiàn)一個(gè)接口,來規(guī)定類要實(shí)現(xiàn)哪些方法,否則無法創(chuàng)建代理對(duì)象,所以可以繼續(xù)使用上邊的CoffeeShop類。

public class JdkProxy {
    public Shop createProxy(final Shop shop) {
        ClassLoader loader = shop.getClass().getClassLoader();
        Class[] interfaces = shop.getClass().getInterfaces();
        InvocationHandler h = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("歡迎!");
                Object result = method.invoke(shop, args);
                System.out.println("再見!");
                return result;
            }
        };
        return (Shop) Proxy.newProxyInstance(loader, interfaces, h);
    }
}

可以看到,上邊的Proxy.newProxyInstance()方法就是用來創(chuàng)建代理對(duì)象的,需要三個(gè)參數(shù):

  • ClassLoader :一般我們使用目標(biāo)對(duì)象的類加載器。
  • Class[]:被代理的類實(shí)現(xiàn)接口的字節(jié)碼數(shù)組,保證代理對(duì)象和目標(biāo)對(duì)象有相同的方法,要不然還怎么代理。這也是為什么要求被代理的類必須最少實(shí)現(xiàn)一個(gè)接口了。
  • InvocationHandler :一般可以用匿名內(nèi)部類,在其invoke()回調(diào)中完成目標(biāo)對(duì)象的功能增強(qiáng)。

invoke()方法三個(gè)參數(shù)的作用:

  • Object proxy:創(chuàng)建的代理對(duì)象。
  • Method method:當(dāng)前調(diào)用的代理對(duì)象的方法。
  • Object[] args:當(dāng)前調(diào)用的代理對(duì)象方法傳遞的參數(shù)。

這樣就可以通過反射間接調(diào)用目標(biāo)對(duì)象的對(duì)應(yīng)方法了:Object result = method.invoke(shop, args)

接下來就是測(cè)試代碼了:

public class ProxyTest {
    @Test
    public void jdkProxyTest() {
        Shop shop = new CoffeeShop();
        JdkProxy jdkProxy = new JdkProxy();
        Shop shopProxy = jdkProxy.createProxy(shop);
        shopProxy.sale("拿鐵");
    }
}

三、CGLIB動(dòng)態(tài)代理

之前提到過,JDK動(dòng)態(tài)代理要求被代理類必須最少實(shí)現(xiàn)一個(gè)接口,否則無法創(chuàng)建代理對(duì)象。但我們并不能要求用到的類都實(shí)現(xiàn)了接口,所以就需要第三方的 CGLIB 庫來實(shí)現(xiàn)動(dòng)態(tài)代理了,jar自行導(dǎo)入即可。下面用 CGLIB 來改造代碼:

public class CglibProxy {
    public Object createProxy(final Object shop) {
        Class clazz = shop.getClass();
        Callback callback = new MethodInterceptor() {
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("歡迎!");
                Object result = method.invoke(shop, args);
                System.out.println("再見!");
                return result;
            }
        };
        return Enhancer.create(clazz, callback);
    }
}

其實(shí)和 JDK動(dòng)態(tài)代理的使用步驟類似。

這里用Enhancer.create()方法來創(chuàng)建代理對(duì)象,需要兩個(gè)參數(shù):

  • Class :被代理對(duì)象的字節(jié)碼。
  • Callback :這里用到了它的MethodInterceptor子類,并在intercept()回調(diào)中完成目標(biāo)對(duì)象的功能增強(qiáng),這點(diǎn)和JDK動(dòng)態(tài)代理類似。

通過如下測(cè)試代碼可以實(shí)現(xiàn)相同的效果:

public class ProxyTest {
    @Test
    public void cglibProxyTest() {
        CoffeeShop2 shop = new CoffeeShop2();
        CglibProxy cglibProxy = new CglibProxy();
        CoffeeShop2 shopProxy = (CoffeeShop2) cglibProxy.createProxy(shop);
        shopProxy.sale("拿鐵");
    }
}

其實(shí) CGLIB 庫也可以用來實(shí)現(xiàn)JDK動(dòng)態(tài)代理例子中的功能,因?yàn)?code>Enhancer.create()還有其他的重載方法:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容