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

先了解下代理模式的概念:為其他對(duì)象提供一種代理,以控制對(duì)這個(gè)對(duì)象的訪(fǎng)問(wèn)。也就是通過(guò)一個(gè)新的對(duì)象去代表目標(biāo)對(duì)象,再通過(guò)新對(duì)象間接去訪(fǎng)問(wèn)目標(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ì)象的功能為例來(lái)學(xué)習(xí)動(dòng)態(tài)代理。

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

一、靜態(tài)代理

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

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

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

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

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

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("再見(jiàn)!");
    }
}
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)代理主要依賴(lài)java.lang.reflect包下的相關(guān)類(lèi)實(shí)現(xiàn),但要求被代理類(lèi)必須最少實(shí)現(xiàn)一個(gè)接口,來(lái)規(guī)定類(lèi)要實(shí)現(xiàn)哪些方法,否則無(wú)法創(chuàng)建代理對(duì)象,所以可以繼續(xù)使用上邊的CoffeeShop類(lèi)。

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("再見(jiàn)!");
                return result;
            }
        };
        return (Shop) Proxy.newProxyInstance(loader, interfaces, h);
    }
}

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

  • ClassLoader :一般我們使用目標(biāo)對(duì)象的類(lèi)加載器。
  • Class[]:被代理的類(lèi)實(shí)現(xiàn)接口的字節(jié)碼數(shù)組,保證代理對(duì)象和目標(biāo)對(duì)象有相同的方法,要不然還怎么代理。這也是為什么要求被代理的類(lèi)必須最少實(shí)現(xiàn)一個(gè)接口了。
  • InvocationHandler :一般可以用匿名內(nèi)部類(lè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ù)。

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

接下來(lái)就是測(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)代理

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

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("再見(jiàn)!");
                return result;
            }
        };
        return Enhancer.create(clazz, callback);
    }
}

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

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

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

通過(guò)如下測(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 庫(kù)也可以用來(lái)實(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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