如何使用動(dòng)態(tài)代理求厕?
參照上面的例子端衰,我們可以知道要實(shí)現(xiàn)動(dòng)態(tài)代理需要做兩方面的工作叠洗。
- 首先需要新建一個(gè)類,并且這個(gè)類必須實(shí)現(xiàn) InvocationHandler 接口旅东。
//杏仁動(dòng)態(tài)代理
public class ApricotHandler implements InvocationHandler{
private Object object;
public ApricotHandler(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args); //調(diào)用真正的蛋糕機(jī)做蛋糕
System.out.println("adding apricot...");
return result;
}
}
- 在調(diào)用的時(shí)候使用 Proxy.newProxyInstance() 方法生成代理類灭抑。
public class CakeShop {
public static void main(String[] args) {
//水果蛋糕撒一層杏仁
CakeMachine fruitCakeMachine = new FruitCakeMachine();
ApricotHandler fruitCakeApricotHandler = new ApricotHandler(fruitCakeMachine);
CakeMachine fruitCakeProxy = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
fruitCakeMachine.getClass().getInterfaces(), fruitCakeApricotHandler);
fruitCakeProxy.makeCake();
}
- 最后直接使用生成的代理類調(diào)用相關(guān)的方法即可。
動(dòng)態(tài)代理的幾種實(shí)現(xiàn)方式
動(dòng)態(tài)代理其實(shí)指的是一種設(shè)計(jì)模式概念抵代,指的是通過代理來(lái)做一些通用的事情腾节,常見的應(yīng)用有權(quán)限系統(tǒng)、日志系統(tǒng)等荤牍,都用到了動(dòng)態(tài)代理案腺。
而
Java 動(dòng)態(tài)代理只是動(dòng)態(tài)代理的一種實(shí)現(xiàn)方式而已,動(dòng)態(tài)代理還有另外一種實(shí)現(xiàn)方式康吵,即
CGLib(Code Generation Library)劈榨。
Java 動(dòng)態(tài)代理只能針對(duì)實(shí)現(xiàn)了接口的類進(jìn)行拓展,所以細(xì)心的朋友會(huì)發(fā)現(xiàn)我們的代碼里有一個(gè)叫 MachineCake 的接口晦嵌。而 CGLib 則沒有這個(gè)限制同辣,因?yàn)?CGLib 是使用繼承原有類的方式來(lái)實(shí)現(xiàn)代理的。
我們還是舉個(gè)例子來(lái)說明
CGLib 是如何實(shí)現(xiàn)動(dòng)態(tài)代理的吧惭载。還是前面的例子:我們要做杏仁水果蛋糕旱函、巧克力水果蛋糕、五仁巧克力蛋糕描滔,這時(shí)候用代碼描述是這樣的棒妨。
首先我們需要寫一個(gè)杏仁攔截器類,這個(gè)攔截器可以給做好的蛋糕加上杏仁含长。
public class ApricotInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
methodProxy.invokeSuper(o, objects);
System.out.println("adding apricot...");
return o;
}
}
接著直接讓蛋糕店使用 CGLib 提供的工具類做杏仁水果蛋糕:
public class CakeShop {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(FruitCakeMachine.class);
enhancer.setCallback(new ApricotInterceptor());
FruitCakeMachine fruitCakeMachine = (FruitCakeMachine) enhancer.create();
fruitCakeMachine.makeCake();
}
}
上面的 enhancer.setSuperClass() 設(shè)置需要增強(qiáng)的類券腔,而 enhancer.setCallback() 則設(shè)置需要回調(diào)的攔截器,即實(shí)現(xiàn)了 MethodInterceptor 接口的類拘泞。最后最后使用 enhancer.create() 生成了對(duì)應(yīng)的增強(qiáng)類纷纫,最后輸出結(jié)果為:
making a Fruit Cake...
adding apricot...
和我們預(yù)期的一樣。如果要做一個(gè)杏仁巧克力蛋糕田弥,那么直接讓蛋糕店利用ApricotHandler 再做一個(gè)就可以了,它們的區(qū)別只是傳入的增強(qiáng)類不同铡原。
public class CakeShop {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ChocolateCakeMachine.class);
enhancer.setCallback(new ApricotInterceptor());
ChocolateCakeMachine chocolateCakeMachine = (ChocolateCakeMachine) enhancer.create();
chocolateCakeMachine.makeCake();
}
}
可以看到偷厦,這里傳入的增強(qiáng)類是 ChocolateCakeMachine,而不是之前的 FruitCakeMachine燕刻。
對(duì)比 Java 動(dòng)態(tài)代理和 CGLib 動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式只泼,你會(huì)發(fā)現(xiàn)
Java 動(dòng)態(tài)代理適合于那些有接口抽象的類代理,而 CGLib 則適合那些沒有接口抽象的類代理卵洗。
Java動(dòng)態(tài)代理的原理
從上面的例子我們可以知道请唱,Java 動(dòng)態(tài)代理的入口是從 Proxy.newInstance() 方法中開始的弥咪,那么我們就從這個(gè)方法開始邊剖析源碼邊理解其原理。
其實(shí)通過這個(gè)方法十绑,Java 替我們生成了一個(gè)繼承了指定接口(CakeMachine)的代理類(ApricotHandler)實(shí)例聚至。從
Proxy.newInstance()
的源碼我們可以看到首先調(diào)用了 getProxyClass0 方法,該方法返回了一個(gè) Class 實(shí)例對(duì)象本橙,該實(shí)例對(duì)象其實(shí)就是 ApricotHandler 的 Class 對(duì)象扳躬。接著獲取其構(gòu)造方法對(duì)象,最后生成該 Class 對(duì)象的實(shí)例甚亭。其實(shí)這里最主要的是 getProxyClass0() 方法贷币,這里面動(dòng)態(tài)生成了 ApricotHandler 的 Class 對(duì)象。下面我們就深入到 getProxyClass0() 方法中去了解這里面做了什么操作亏狰。
getProxyClass0() 方法首先是做了一些參數(shù)校驗(yàn)役纹,之后從 proxyClassCache 參數(shù)中取出 Class 對(duì)象。其實(shí) proxyClassCache 是一個(gè) Map 對(duì)象暇唾,緩存了所有動(dòng)態(tài)創(chuàng)建的 Class 對(duì)象促脉。從源碼中的注釋可以知道,如果從 Map 中取出的對(duì)象為空信不,那么其會(huì)調(diào)用
ProxyClassFactory
生成對(duì)應(yīng)的 Class 對(duì)象嘲叔。
在 ProxyClassFactory 類的源碼中,最終是調(diào)用了
ProxyGenerator.genrateProxyClass()
方法生成了對(duì)應(yīng)的 class 字節(jié)碼文件抽活。
到這里硫戈,我們已經(jīng)把動(dòng)態(tài)代理的 Java 源代碼都解析完了,現(xiàn)在思路就很清晰了下硕。Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法簡(jiǎn)單來(lái)說執(zhí)行了以下操作:
- 1丁逝、生成一個(gè)實(shí)現(xiàn)了參數(shù) interfaces 里所有接口且繼承了 Proxy 的代理類的字節(jié)碼,然后用參數(shù)里的 classLoader 加載這個(gè)代理類梭姓。
- 2霜幼、使用代理類父類的構(gòu)造函數(shù) Proxy(InvocationHandler h) 來(lái)創(chuàng)造一個(gè)代理類的實(shí)例,將我們自定義的 InvocationHandler 的子類傳入誉尖。
- 3罪既、返回這個(gè)代理類實(shí)例炕贵,因?yàn)槲覀儤?gòu)造的代理類實(shí)現(xiàn)了 interfaces(也就是我們程序中傳入的 fruitCakeMachine.getClass().getInterfaces() 里的所有接口婿奔,因此返回的代理類可以強(qiáng)轉(zhuǎn)成 MachineCake 類型來(lái)調(diào)用接口中定義的方法。
CGLib動(dòng)態(tài)代理的原理
因?yàn)?JVM 并不允許在運(yùn)行時(shí)修改原有類糕殉,所以所有的動(dòng)態(tài)性都是通過新建類來(lái)實(shí)現(xiàn)的探熔,上面說到的 Java 動(dòng)態(tài)代理也不例外驹针。所以對(duì)于 CGLib 動(dòng)態(tài)代理的原理,其實(shí)也是通過動(dòng)態(tài)生成代理類诀艰,最后由代理類來(lái)完成操作實(shí)現(xiàn)的柬甥。
對(duì)于 CGLib 動(dòng)態(tài)代理的實(shí)現(xiàn)饮六,我并沒有深入到源碼中,而是通過查閱資料了解了其大概的實(shí)現(xiàn)原理苛蒲。
- 首先卤橄,我們?cè)谑褂玫臅r(shí)候通過 enhancer.setSuperclass(FruitCakeMachine.class) 傳入了需要增加的類,CGLib 便會(huì)生成一個(gè)繼承了改類的代理類撤防。
- 接著虽风,我們通過 enhancer.setCallback(new ApricotInterceptor()) 傳入了代理類對(duì)象,CGLib 通過組裝兩個(gè)類的結(jié)構(gòu)實(shí)現(xiàn)一個(gè)靜態(tài)代理寄月,從而達(dá)到具體的目的辜膝。
而在 CGLib 生成新類的過程中,其使用的是一個(gè)名為 ASM 的東西漾肮,它對(duì) Java 的 class 文件進(jìn)行操作厂抖、生成新的 class 文件。如果你對(duì) CGLib 的原理感興趣克懊,不妨看看這篇文章:從兄弟到父子:動(dòng)態(tài)代理在民間是怎么玩的忱辅?
動(dòng)態(tài)代理的應(yīng)用
動(dòng)態(tài)代理在代碼界可是有非常重要的意義,我們開發(fā)用到的許多框架都使用到了這個(gè)概念谭溉。我所知道的就有:Spring AOP墙懂、Hibernate、Struts 使用到了動(dòng)態(tài)代理扮念。
- Spring AOP损搬。Spring 最重要的一個(gè)特性是 AOP(Aspect Oriented Programming 面向切面編程),利用 Spring AOP 可以快速地實(shí)現(xiàn)權(quán)限校驗(yàn)柜与、安全校驗(yàn)等公用操作巧勤。而 Spring AOP 的原理則是通過動(dòng)態(tài)代理實(shí)現(xiàn)的,默認(rèn)情況下 Spring AOP 會(huì)采用 Java 動(dòng)態(tài)代理實(shí)現(xiàn)弄匕,而當(dāng)該類沒有對(duì)應(yīng)接口時(shí)才會(huì)使用 CGLib 動(dòng)態(tài)代理實(shí)現(xiàn)颅悉。
- Hibernate。Hibernate 是一個(gè)常用的 ORM 層框架迁匠,在獲取數(shù)據(jù)時(shí)常用的操作有:get() 和 load() 方法剩瓶,它們的區(qū)別是:get() 方法會(huì)直接獲取數(shù)據(jù),而 load() 方法則會(huì)延遲加載城丧,等到用戶真的去取數(shù)據(jù)的時(shí)候才利用代理類去讀數(shù)據(jù)庫(kù)延曙。
- Struts。Struts 現(xiàn)在雖然因?yàn)槠涮?bug 已經(jīng)被拋棄芙贫,但是曾經(jīng)用過 Struts 的人都知道 Struts 中的攔截器搂鲫。攔截器有非常強(qiáng)的 AOP 特性傍药,仔細(xì)了解之后你會(huì)發(fā)現(xiàn) Struts 攔截器其實(shí)也是用動(dòng)態(tài)代理實(shí)現(xiàn)的磺平。
總結(jié)
我們通過蛋糕店的不同業(yè)務(wù)場(chǎng)景介紹了靜態(tài)代理和動(dòng)態(tài)代理的應(yīng)用魂仍,接著重點(diǎn)介紹了動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式(Java 動(dòng)態(tài)代理、CGLib 動(dòng)態(tài)代理)的使用方法及其實(shí)現(xiàn)原理拣挪,其中還針對(duì) Java 動(dòng)態(tài)代理的源碼進(jìn)行了簡(jiǎn)單的分析擦酌。最后,我們介紹了動(dòng)態(tài)代理在實(shí)際上編程中的應(yīng)用(Spring AOP菠劝、Hibernate赊舶、Struts)。