教你清楚了解JAVA动态代理
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
- 代理在生活中很常見,比如說婚介網(wǎng)站,其實(shí)就是找對(duì)象的代理;還有社保代理、人事代理;還有找黃牛搶票,其實(shí)也是一種代理;而這些代理,在JAVA中也是有對(duì)應(yīng)實(shí)現(xiàn)的。
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è)很簡(jiǎn)單的例子來說明: 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)如下:
? CgLib3.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è)?invoke 方法完成對(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è)?introduction 方法前和后都成功增加了功能,讓這個(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è)黾恿?sayHello 和 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è)?intercept 添加處理邏輯即可。
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 了
分享一些知識(shí)點(diǎn)給大家希望能幫助到大家,或者從中啟發(fā)。
加入Q君羊:821169538 都是java愛好澤,大家可以一起討論交流學(xué)習(xí)
原文
http://fengqiangboy.com/15377761043880.html
轉(zhuǎn)載于:https://my.oschina.net/u/3972077/blog/2208364
總結(jié)
以上是生活随笔為你收集整理的教你清楚了解JAVA动态代理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 封装高可复用的服务端响应SSC程序修复对
- 下一篇: DNS服务初级应用