這篇文章主要介紹了大數(shù)據(jù)開發(fā)中動態(tài)代理是什么,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:做網(wǎng)站、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的烏海海南網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
一、動態(tài)代理的意義
首先明白一點,動態(tài)代理就是用來生成代理對象的。我們知道傳統(tǒng)的代理模式,通常是先定義一個代理類,該代理類需要持有目標對象(也有叫被代理對象,我覺得都行吧)。假設(shè)我們有1000個不同的目標對象(這1000個對象不是同一個類),那么我們需要預(yù)先定義1000個代理類,這是我們不能容忍的。于是乎,動態(tài)代理就出現(xiàn)了,它本質(zhì)上是生成一個外表上和目標對象一樣的代理對象,然后當(dāng)我們調(diào)用代理對象的方法的時候,實際上它在他的方法里面去調(diào)用了目標對象對應(yīng)的同名方法。
二、動態(tài)代理設(shè)計的核心思想
其實不要把這些設(shè)計想得多么高尚,假如我是動態(tài)代理設(shè)計的作者,由動態(tài)代理的意義部分我們知道,我們就是要想盡一切辦法,通過目標對象生成代理對象,然后讓代理對象的方法調(diào)用作用到目標對象的方法調(diào)用。沒錯動態(tài)代理的核心思想就是這么簡單。比如目標類為Person,Person有一個方法叫做purchase(),此方法用于購物。我們期望purchase()方法有代理類去做處理,比如在購物前記錄下購買了哪些東西。我們知道在使用一個類之前,是需要創(chuàng)建一個對象的,我們就在創(chuàng)建的地方動手腳。所以你看到了JDK動態(tài)Proxy.newInstance()的方式,也領(lǐng)略過Spring的Enhancer.create()。個人比較喜歡cglib的優(yōu)雅、干凈、利落。吐槽一下JDK的InvocationHandler像極了惡心的中間商。下面是JDK動態(tài)代理UML示意圖
三、JDK動態(tài)代理
1,原理
在了解動態(tài)代理之前,我們需要了解Java字節(jié)碼。如果不熟悉Java字節(jié)碼,你可以理解為通過代碼動態(tài)生成一個.java文件,然后將其編譯為class文件加載到內(nèi)存中。接下來JDK中的動態(tài)代理要做的事情就是怎么去生成一個ProxyPerson字節(jié)碼文件。其實它就是在生成字節(jié)碼的時候,持有了InvocationHandler對象,然后去實現(xiàn)了ProxyPerson對應(yīng)的接口。在該接口的所有實現(xiàn)方法中,只做了一件事情就是調(diào)用invocationHandler.invoke()方法。從代碼層面來看如下所示:
public class ProxyPerson implements Purchase{
static{
Method method;// 接口的方法
Object[] args;// 接口參數(shù)
}
InvocationHandler handler;
public ProxyPerson(InvocationHandler handler){
this.handler = handler;
}
@overrde
public purchage(){
this,handler.invoke(this,method,args);
}
}
那么上面這段代碼是在什么時候生成的呢?
Proxy.newProxyInstance()
在我們調(diào)用JDK上面的這個方法的時候,底層就會去生成一個ProxyPerson字節(jié)碼。知道了原理我們來解答一下JDK動態(tài)代理為何只能基于接口代理而不能基于類呢?
1),受限于字節(jié)碼的生成方式,JDK本身就是基于InvocationHandler去做的代理中轉(zhuǎn)。我們看到代理對象的方法調(diào)用于目標對象的調(diào)用沒有半毛球關(guān)系,調(diào)用目標對象是我們自己在invoke方法里面完成的。
2),受限于同名的方法只能被向上轉(zhuǎn)型成功的對象調(diào)用。比如有兩個類Boy與Girl,他們都實現(xiàn)了接口Purchase,如果我們先獲取到Girl的purchase()方法method,我們通過method.invoke(new Boy())這樣必定會報錯。但是如果我們獲取到Purchase接口purchase()方法method,我們通過method.invoke(new Boy())這樣是ok的,因為new Boy()可以向上轉(zhuǎn)型為Purchase。
2,應(yīng)用
比如無論是傳統(tǒng)的MVC模型還是DDD模型,都離不開Service。我們知道Service方法使用@Transactional是可以開啟事務(wù)控制的。那么這種注解式事務(wù)是如何實現(xiàn)的呢?其實在工程啟動的時候,我們就會有一個Bean的后置處理器去檢查所有Bean一旦發(fā)現(xiàn)Bean的方法上有事務(wù)注解,他就通過Proxy.newInstance()去創(chuàng)建一個代理對象,將代理對象進行返回注入,而拋棄原本應(yīng)該注入到容器的對象。所以我們看起來通過容器拿到的Service其實已經(jīng)是代理對象了。在調(diào)用目標對象前,開啟編程式事務(wù)即可。
四、cglib動態(tài)代理
有了上面的知識,我們要有對于cglib而言只是在生成字節(jié)碼上面動手腳的覺悟。下面直觀感受與一下生成過程
public static void main(final String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Boy.class); enhancer.setCallback(new MethodInterceptor(){ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("proxy method "+ method.getName()); if(method.getAnnotation(Transactional.class)!=null){ System.out.println(method.getName()+"發(fā)現(xiàn)注解"); } return methodProxy.invokeSuper(o,args); } }); Boy proxy = (Boy) enhancer.create(); proxy.test(); } public static class Boy{ public void run(){ System.out.println("run..."); } @Transactional public void walk(){ System.out.println("walk..."); } @Transactional public void test(){ System.out.println("test..."); walk(); } }
可以看到cglib是基于繼承的方式進行字節(jié)碼動態(tài)生成。 它在子類的實現(xiàn)中,只是調(diào)用了注入的methodIntercptor.interceptor()方法。 具體字節(jié)碼實現(xiàn)細節(jié),這里不在深究。 我們在這里探討一下,為什么cglib可以使同一個service方法中的其他帶有事務(wù)注解的事務(wù)生效?因為基于繼承的動態(tài)代理,本質(zhì)發(fā)起上調(diào)用的代理對象可以向上轉(zhuǎn)型為原本的目標對象,所以它可以直接通過代理對象去調(diào)目標對象方法。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“大數(shù)據(jù)開發(fā)中動態(tài)代理是什么”這篇文章對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!