深入分析JAVA動(dòng)態(tài)代理

1、為什么要?jiǎng)討B(tài)代理

動(dòng)態(tài)代理的作用其實(shí)就是在不修改原代碼的前提下,對(duì)已有的方法進(jìn)行增強(qiáng)。
關(guān)鍵點(diǎn):

不修改原來已有的代碼(滿足設(shè)計(jì)模式的要求)

對(duì)已有方法進(jìn)行增強(qiáng)

2、舉個(gè)栗子

我們用一個(gè)很簡單的例子來說明:Hello類,有一個(gè)introduction方法。

現(xiàn)在我們的需求就是不修改Hello類的introduction方法,在introduction之前先sayHello,在introduction之后再sayGoodBye

3、實(shí)現(xiàn)方式

JAVA中,實(shí)現(xiàn)動(dòng)態(tài)代理有兩種方式,一種是JDK提供的,一種是第三方庫CgLib提供的。特點(diǎn)如下:

JDK動(dòng)態(tài)代理:被代理的目標(biāo)類需要實(shí)現(xiàn)接口

CgLib方式:可以對(duì)任意類實(shí)現(xiàn)動(dòng)態(tài)代理

3.1、JDK動(dòng)態(tài)代理
JDK動(dòng)態(tài)代理需要實(shí)現(xiàn)接口,然后通過對(duì)接口方法的增強(qiáng)來實(shí)現(xiàn)動(dòng)態(tài)代理

所以要使用JDK動(dòng)態(tài)代理的話,我們首先要?jiǎng)?chuàng)建一個(gè)接口,并且被代理的方法要在這個(gè)接口里面

3.1.1、創(chuàng)建一個(gè)接口
我們創(chuàng)建一個(gè)接口如下:

Personal.java

public interface Personal {

    /**
     * 被代理的方法
     */
    void introduction();

}

3.1.2、實(shí)現(xiàn)接口
創(chuàng)建接口實(shí)現(xiàn)類,并且完成introduction方法

PersonalImpl.java

public class PersonalImpl implements Personal {
    @Override
    public void introduction() {
        System.out.println("我是程序員!");
    }
}

3.1.3、創(chuàng)建代理類
JDK代理的關(guān)鍵就是這個(gè)代理類了,需要實(shí)現(xiàn)InvocationHandler

在代理類中,所有方法的調(diào)用都好分發(fā)到invoke方法中。我們?cè)趇nvoke方法完成對(duì)方法的增強(qiáng)即可

JDKProxyFactory.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxyFactory<T> implements InvocationHandler {

    /**
     * 目標(biāo)對(duì)象
     */
    private T target;

    /**
     * 構(gòu)造函數(shù)傳入目標(biāo)對(duì)象
     *
     * @param target 目標(biāo)對(duì)象
     */
    public JDKProxyFactory(T target) {
        this.target = target;
    }

    /**
     * 獲取代理對(duì)象
     *
     * @return 獲取代理
     */
    public T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 對(duì)方法增強(qiáng)
        System.out.println("大家好!");
        // 調(diào)用原方法
        Object result = method.invoke(target, args);
        // 方法增強(qiáng)
        System.out.println("再見!");
        return result;
    }
}

就這樣,JDK動(dòng)態(tài)代理的代碼就完成了,接下來寫一份測(cè)試代碼

3.1.4、編寫測(cè)試代碼
為了方便測(cè)試,我們編寫一個(gè)test方法

同時(shí)為了查看class文件,還添加了一個(gè)generatorClass方法,這個(gè)方法可以將動(dòng)態(tài)代理生成的.class輸出到文件

ProxyTest.java

import org.junit.Test;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

public class ProxyTest {

    @Test
    public void testJdkProxy() {
        // 生成目標(biāo)對(duì)象
        Personal personal = new PersonalImpl();
        // 獲取代理對(duì)象
        JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal);
        Personal proxy = proxyFactory.getProxy();

        // 將proxy的class字節(jié)碼輸出到文件
        generatorClass(proxy);

        // 調(diào)用代理對(duì)象
        proxy.introduction();
    }

    /**
     * 將對(duì)象的class字節(jié)碼輸出到文件
     *
     * @param proxy 代理類
     */
    private void generatorClass(Object proxy) {
        FileOutputStream out = null;
        try {
            byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()});
            out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
            out.write(generateProxyClass);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                }
            }
        }

    }

}

3.1.5、查看運(yùn)行結(jié)果
可以看到,運(yùn)行test方法之后,控制臺(tái)打印出如下:

大家好!
我是程序員!
再見!

我們?cè)趇ntroduction方法前和后都成功增加了功能,讓這個(gè)程序員的自我介紹瞬間變得更加有禮貌了。

3.1.6、探探動(dòng)態(tài)代理的秘密
動(dòng)態(tài)代理的代碼并不多,那么JDK底層是怎么幫我們實(shí)現(xiàn)的呢?

在測(cè)試的時(shí)候我們將動(dòng)態(tài)生成的代理類的class字節(jié)碼輸出到了文件,我們可以反編譯看看。

結(jié)果有點(diǎn)長,就不全部貼出來了,不過我們可以看到,里面有一個(gè)introduction方法如下:

/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}

public final void introduction() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

原來,生成的代理對(duì)象里面,引用了我們的InvocationHandler,然后在將introduction方法里面調(diào)用了InvocationHandler的introduction,而InvocationHandler是由我們編寫的代理類,在這里我們?cè)黾恿藄ayHello和sayGoodBye操作,然后還調(diào)用了原對(duì)象的introduction方法,就這樣完成了動(dòng)態(tài)代理。

3.2、CgLib動(dòng)態(tài)代理
CgLib動(dòng)態(tài)

3.2.1、創(chuàng)建被代理對(duì)象
由于CgLib不需要實(shí)現(xiàn)接口,所以我們不需要?jiǎng)?chuàng)建接口文件了(當(dāng)然,你要有接口也沒有問題)

直接創(chuàng)建目標(biāo)類,實(shí)現(xiàn)introduction方法

PersonalImpl.java

public class PersonalImpl {
    public void introduction() {
        System.out.println("我是程序員!");
    }
}

3.2.2、創(chuàng)建代理類
同樣,我們也需要?jiǎng)?chuàng)建代理類,并且在這里實(shí)現(xiàn)增強(qiáng)的邏輯,這次我們不是實(shí)現(xiàn)InvocationHandler接口了,而是實(shí)現(xiàn)CgLib提供的接口MethodInterceptor,都是類似的,MethodInterceptor中,全部方法調(diào)用都會(huì)交給intercept處理,我們?cè)趇ntercept添加處理邏輯即可。

CgLibProxyFactory.java

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CgLibProxyFactory<T> implements MethodInterceptor {

    /**
     * 獲取代理對(duì)象
     *
     * @param tClass 被代理的目標(biāo)對(duì)象
     * @return 代理對(duì)象
     */
    public T getProxyByCgLib(Class<T> tClass) {
        // 創(chuàng)建增強(qiáng)器
        Enhancer enhancer = new Enhancer();

        // 設(shè)置需要增強(qiáng)的類的類對(duì)象
        enhancer.setSuperclass(tClass);

        // 設(shè)置回調(diào)函數(shù)
        enhancer.setCallback(this);

        // 獲取增強(qiáng)之后的代理對(duì)象
        return (T) enhancer.create();
    }

     /**
     *  代理類方法調(diào)用回調(diào)
     *  
     * @param obj 這是代理對(duì)象,也就是[目標(biāo)對(duì)象]的子類
     * @param method [目標(biāo)對(duì)象]的方法
     * @param args 參數(shù)
     * @param proxy 代理對(duì)象的方法
     * @return 返回結(jié)果,返回給調(diào)用者
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        System.out.println("大家好!");

        Object result = proxy.invokeSuper(obj, args);

        System.out.println("再見!");

        return result;
    }
}

3.2.3、編寫測(cè)試代碼
在剛才的測(cè)試方法中,我們添加一個(gè)cglib的測(cè)試方法:

@Test
public void testCgLibProxy() {
    // 生成被代理的目標(biāo)對(duì)象
    PersonalImpl personal = new PersonalImpl();

    // 獲取代理類
    CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>();
    PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass());

    // 將proxy的class字節(jié)碼輸出到文件
    generatorClass(proxy);

    // 調(diào)用代理對(duì)象
    proxy.introduction();
}

3.2.4、查看運(yùn)行結(jié)果
運(yùn)行測(cè)試用例,可以看到跟JDK的實(shí)現(xiàn)一樣的效果

大家好!
我是程序員!
再見!

3.2.5、探探動(dòng)態(tài)代理的秘密
跟JDK的測(cè)試一樣,我們也來看看生成的class文件

public final void introduction() throws  {
    try {
        super.h.invoke(this, m7, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

可以發(fā)現(xiàn),與JDK的動(dòng)態(tài)代理并沒有區(qū)別。

4、如何選擇

既然有兩種實(shí)現(xiàn)方式,那么到底應(yīng)該怎么選擇呢?

就兩個(gè)原則:

目標(biāo)類有接口實(shí)現(xiàn)的,JDK和CgLib都可以選擇,你開心就好

目標(biāo)類沒有實(shí)現(xiàn)任何接口,那只能用CgLib了

5、后記

其實(shí)在第一次看到動(dòng)態(tài)代理的時(shí)候,我就想不明白,我們都把目標(biāo)類new出來了,為什么還要將目標(biāo)類丟給代理類呢?為什么不直接調(diào)用目標(biāo)類對(duì)應(yīng)的方法呢?

后來才發(fā)現(xiàn),原來我沒搞清楚動(dòng)態(tài)代理的使用場(chǎng)景,場(chǎng)景很清晰,就是:

不修改原來已有的代碼(滿足設(shè)計(jì)模式的要求)

對(duì)已有方法進(jìn)行增強(qiáng)

關(guān)鍵是增強(qiáng),代理類里面我們是可以添加很多處理邏輯的,從而實(shí)現(xiàn)增強(qiáng)效果。就像黃牛搶票比我們厲害些一樣。
歡迎關(guān)注
最后編輯于
?著作權(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ù)。

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