#靜態(tài)代理
? ? 靜態(tài)代理是在編譯時(shí)就將接口伞广、實(shí)現(xiàn)類拣帽、代理類一股腦兒全部手動(dòng)完成,但如果我們需要很多的代理嚼锄,每一個(gè)都這么手動(dòng)的去創(chuàng)建實(shí)屬浪費(fèi)時(shí)間减拭,而且會(huì)有大量的重復(fù)代碼 。
例如:
```
? ? //聲明一個(gè)接口
? ? package com.ceshi.proxy;
? ? public interface Moveable {
? ? ? ? //提供一個(gè)move方法
void move();
}
package com.ceshi.proxy;
import java.util.Random;
//寫一個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)這個(gè)接口
public class Tank implements Moveable {
@Override
public void move() {
System.out.println("Tank Moving...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.ceshi.proxy;
//再寫一個(gè)tank的時(shí)間代理類實(shí)現(xiàn)Moveable接口
public class TankTimeProxy implements Moveable {
? ? public TankTimeProxy(Moveable t) {
? ? ? ? super();
? ? ? ? this.t = t;
? ? }
? ? Moveable t;
? ? @Override
? ? public void move() {
? ? ? ? //在執(zhí)行move方法前后加上有關(guān)時(shí)間的代碼邏輯区丑,以此來實(shí)現(xiàn)類似日志管理
? ? ? ? long start = System.currentTimeMillis();
? ? ? ? System.out.println("starttime:" + start);
? ? ? ? t.move();
? ? ? ? long end = System.currentTimeMillis();
? ? ? ? System.out.println("time:" + (end-start));
? ? }
}
package com.ceshi.proxy;
public class Client {
public static void main(String[] args) throws Exception {
Tank t = new Tank();
TankTimeProxy time = new TankTimeProxy(t);
time.move();
}
}
```
運(yùn)行結(jié)果為:
```
starttime:1526992632651
Tank Moving...
time:3604
```
通過上述例子拧粪,我們看到,靜態(tài)代理需要手動(dòng)的取創(chuàng)建沧侥,并且在以后我們有可會(huì)對(duì)tank這個(gè)類進(jìn)行日志管理可霎,權(quán)限管理,事務(wù)管理等等宴杀,總之癣朗,在他前后添加代碼邏輯,并且實(shí)現(xiàn)各種代理順序的調(diào)換旺罢,那我們豈不是要寫很多代理類去實(shí)現(xiàn)那個(gè)接口旷余。代碼太冗余绢记。
#動(dòng)態(tài)代理
動(dòng)態(tài)代理可以在程序運(yùn)行期間根據(jù)需要?jiǎng)討B(tài)的創(chuàng)建代理類及其實(shí)例,來完成具體的功能正卧。
接續(xù)上面的例子蠢熄,看看用動(dòng)態(tài)代理怎么實(shí)現(xiàn)
```
//聲明一個(gè)接口
? ? package com.ceshi.proxy;
? ? public interface Moveable {
? ? ? ? //提供一個(gè)move方法
void move();
}
package com.ceshi.proxy;
import java.util.Random;
//寫一個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)這個(gè)接口
public class Tank implements Moveable {
@Override
public void move() {
System.out.println("Tank Moving...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.ceshi.proxy.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//寫一個(gè)事物操作的Handler實(shí)現(xiàn)jdk中的InvocationHandler。
public class TransactionHandler implements InvocationHandler {
private Object target;
//寫一個(gè)構(gòu)造方法將被代理的對(duì)象傳進(jìn)去
public TransactionHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Transaction Start");
try {
method.invoke(target);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Transaction Commit");
return null;
}
}
package com.ceshi.proxy.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import com.ceshi.proxy.TimeHandler;
public class Client {
public static void main(String[] args) throws Exception {
Tank tank = new Tank();
InvocationHandler h = new TransactionHandler(tank );
Moveable m = (Moveable )Proxy.newProxyInstance(Moveable .class.getClassLoader(),new Class[]{Moveable .class},h);
m.move();
}
}
```
運(yùn)行結(jié)果為:
```
Transaction Start
Tank Moving...
Transaction Commit
```
#總結(jié)
通過上面的動(dòng)態(tài)代理實(shí)例我們來仔細(xì)分析研究一下動(dòng)態(tài)代理的實(shí)現(xiàn)過程
(1)首先我要說的就是接口炉旷,為什么JDK的動(dòng)態(tài)代理是基本接口實(shí)現(xiàn)的呢签孔?
因?yàn)橥ㄟ^使用接口指向?qū)崿F(xiàn)類的實(shí)例的多態(tài)實(shí)現(xiàn)方式,可以有效的將具體的實(shí)現(xiàn)與調(diào)用之間解耦窘行,便于后期修改與維護(hù)饥追。
再具體的說就是我們在代理類中創(chuàng)建一個(gè)私有成員變量(private修飾),使用接口來指向?qū)崿F(xiàn)類的對(duì)象(純種的多態(tài)體現(xiàn)抽高,向上轉(zhuǎn)型的體現(xiàn))判耕,然后在該代理類中的方法中使用這個(gè)創(chuàng)建的實(shí)例來調(diào)用實(shí)現(xiàn)類中的相應(yīng)方法來完成業(yè)務(wù)邏輯功能。
這么說起來翘骂,我之前說的“將具體實(shí)現(xiàn)類完全隱藏”就不怎么正確了壁熄,可以改成,將具體實(shí)現(xiàn)類的細(xì)節(jié)向調(diào)用方完全隱藏(調(diào)用方調(diào)用的是代理類中的方法碳竟,而不是實(shí)現(xiàn)類中的方法)草丧。
這就是面向接口編程,利用java的多態(tài)特性莹桅,實(shí)現(xiàn)程序代碼的解耦昌执。
(2)創(chuàng)建代理類的過程
如果你了解靜態(tài)代理,那么你會(huì)發(fā)現(xiàn)動(dòng)態(tài)代理的實(shí)現(xiàn)其實(shí)與靜態(tài)代理類似诈泼,都需要?jiǎng)?chuàng)建代理類懂拾,但是不同之處也很明顯,創(chuàng)建方式不同铐达!
不同之處體現(xiàn)在靜態(tài)代理我們知根知底岖赋,我們知道要對(duì)哪個(gè)接口、哪個(gè)實(shí)現(xiàn)類來創(chuàng)建代理類瓮孙,所以我們在編譯前就直接實(shí)現(xiàn)與實(shí)現(xiàn)類相同的接口唐断,直接在實(shí)現(xiàn)的方法中調(diào)用實(shí)現(xiàn)類中的相應(yīng)(同名)方法即可;而動(dòng)態(tài)代理不同杭抠,我們不知道它什么時(shí)候創(chuàng)建脸甘,也不知道要?jiǎng)?chuàng)建針對(duì)哪個(gè)接口、實(shí)現(xiàn)類的代理類(因?yàn)樗窃谶\(yùn)行時(shí)因需實(shí)時(shí)創(chuàng)建的)偏灿。
雖然二者創(chuàng)建時(shí)機(jī)不同丹诀,創(chuàng)建方式也不相同,但是原理是相同的,不同之處僅僅是:靜態(tài)代理可以直接編碼創(chuàng)建忿墅,而動(dòng)態(tài)代理是利用反射機(jī)制來抽象出代理類的創(chuàng)建過程扁藕。
讓我們來分析一下之前的代碼來驗(yàn)證一下上面的說辭:
第一點(diǎn):靜態(tài)代理需要實(shí)現(xiàn)與實(shí)現(xiàn)類相同的接口,而動(dòng)態(tài)代理需要實(shí)現(xiàn)的是固定的Java提供的內(nèi)置接口(一種專門提供來創(chuàng)建動(dòng)態(tài)代理的接口)InvocationHandler接口疚脐,因?yàn)閖ava在接口中提供了一個(gè)可以被自動(dòng)調(diào)用的方法invoke,這個(gè)之后再說邢疙。
第二點(diǎn):private Object object;
public UserProxy(Object obj){this.object = obj;}
這幾行代碼與靜態(tài)代理之中在代理類中定義的接口指向具體實(shí)現(xiàn)類的實(shí)例的代碼異曲同工棍弄,通過這個(gè)構(gòu)造器可以創(chuàng)建代理類的實(shí)例,創(chuàng)建的同時(shí)還能將具體實(shí)現(xiàn)類的實(shí)例與之綁定(object指的就是實(shí)現(xiàn)類的實(shí)例疟游,這個(gè)實(shí)例需要在測試類中創(chuàng)建并作為參數(shù)來創(chuàng)建代理類的實(shí)例)呼畸,實(shí)現(xiàn)了靜態(tài)代理類中private Iuser user = new UserImpl();一行代碼的作用相近,這里為什么不是相同颁虐,而是相近呢蛮原,主要就是因?yàn)殪o態(tài)代理的那句代碼中包含的實(shí)現(xiàn)類的實(shí)例的創(chuàng)建,而動(dòng)態(tài)代理中實(shí)現(xiàn)類的創(chuàng)建需要在測試類中完成另绩,所以此處是相近儒陨。
第三點(diǎn):invoke(Object proxy, Method method, Object[] args)方法,該方法是InvocationHandler接口中定義的唯一方法笋籽,該方法在調(diào)用指定的具體方法時(shí)會(huì)自動(dòng)調(diào)用蹦漠。其參數(shù)為:代理實(shí)例、調(diào)用的方法车海、方法的參數(shù)列表
在這個(gè)方法中我們定義了幾乎和靜態(tài)代理相同的內(nèi)容笛园,僅僅是在方法的調(diào)用上不同,不同的原因與之前分析的一樣(創(chuàng)建時(shí)機(jī)的不同侍芝,創(chuàng)建的方式的不同研铆,即反射),Method類是反射機(jī)制中一個(gè)重要的類州叠,用于封裝方法棵红,該類中有一個(gè)方法那就是invoke(Object object,Object...args)方法,其參數(shù)分別表示:所調(diào)用方法所屬的類的對(duì)象和方法的參數(shù)列表留量,這里的參數(shù)列表正是從測試類中傳遞到代理類中的invoke方法三個(gè)參數(shù)中最后一個(gè)參數(shù)(調(diào)用方法的參數(shù)列表)中窄赋,在傳遞到method的invoke方法中的第二個(gè)參數(shù)中的(此處有點(diǎn)啰嗦)。
第四點(diǎn):測試類中的異同
靜態(tài)代理中我們測試類中直接創(chuàng)建代理類的對(duì)象楼熄,使用代理類的對(duì)象來調(diào)用其方法即可忆绰,若是別的接口(這里指的是別的調(diào)用方)要調(diào)用Iuser的方法,也可以使用此法
動(dòng)態(tài)代理中要復(fù)雜的多可岂,首先我們要將之前提到的實(shí)現(xiàn)類的實(shí)例創(chuàng)建(補(bǔ)充完整)错敢,然后利用這個(gè)實(shí)例作為參數(shù),調(diào)用代理來的帶參構(gòu)造器來創(chuàng)建“代理類實(shí)例對(duì)象”,這里加引號(hào)的原因是因?yàn)樗⒉皇钦嬲拇眍惖膶?shí)例對(duì)象稚茅,而是創(chuàng)建真正代理類實(shí)例的一個(gè)參數(shù)纸淮,這個(gè)實(shí)現(xiàn)了InvocationHandler接口的類嚴(yán)格意義上來說并不是代理類,我們可以將其看作是創(chuàng)建代理類的必備中間環(huán)節(jié)亚享,這是一個(gè)調(diào)用處理器咽块,也就是處理方法調(diào)用的一個(gè)類,不是真正意義上的代理類欺税,可以這么說:創(chuàng)建一個(gè)方法調(diào)用處理器實(shí)例侈沪。
下面才是真正的代理類實(shí)例的創(chuàng)建,之前創(chuàng)建的”代理類實(shí)例對(duì)象“僅僅是一個(gè)參數(shù)
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
這里使用了動(dòng)態(tài)代理所依賴的第二個(gè)重要類Proxy晚凿,此處使用了其靜態(tài)方法來創(chuàng)建一個(gè)代理實(shí)例亭罪,其參數(shù)分別是:類加載器(可為父類的類加載器)、接口數(shù)組歼秽、方法調(diào)用處理器實(shí)例
這里同樣使用了多態(tài)应役,使用接口指向代理類的實(shí)例,最后會(huì)用該實(shí)例來進(jìn)行具體方法的調(diào)用即可燥筷。
(3)InvocationHandler
InvocationHandler是JDK中提供的專門用于實(shí)現(xiàn)基于接口的動(dòng)態(tài)代理的接口箩祥,主要用于進(jìn)行方法調(diào)用模塊,而代理類和實(shí)例的生成需要借助Proxy類完成荆责。
每個(gè)代理類的實(shí)例的調(diào)用處理器都是實(shí)現(xiàn)該接口實(shí)現(xiàn)的滥比,而且是必備的,即每個(gè)動(dòng)態(tài)代理實(shí)例的實(shí)現(xiàn)都必須擁有實(shí)現(xiàn)該接口的調(diào)用處理器做院,也可以這么說盲泛,每個(gè)動(dòng)態(tài)代理實(shí)例都對(duì)應(yīng)一個(gè)調(diào)用處理器。
這里要區(qū)分兩個(gè)概念键耕,代理類和代理實(shí)例寺滚,調(diào)用處理器是在創(chuàng)建代理實(shí)例的時(shí)候才與其關(guān)聯(lián)起來的,所以它與代理實(shí)例是一一對(duì)應(yīng)的屈雄,而不是代理類村视。
(4)Proxy
Proxy類是JDK提供的用于生成動(dòng)態(tài)代理類和其實(shí)例的類。
我們可以通過Proxy中的靜態(tài)方法getProxyClass來生成代理類酒奶,需要的參數(shù)為類加載器和接口列表(數(shù)組)蚁孔,然后再通過反射調(diào)用代理類的構(gòu)造器來生成代理實(shí)例,需要以一個(gè)InvocationHandler作為參數(shù)(體現(xiàn)出方法調(diào)用是與實(shí)例相關(guān)的惋嚎,而非類)杠氢。
```
? ? InvocationHandler handler = new MyInvocationHandler(...);
? ? Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
? ? Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
```
我們也可以直接通過Proxy中的靜態(tài)方法newProxyInstance方法來直接生產(chǎn)代理實(shí)例,需要提供參數(shù)為上面的三個(gè)參數(shù)另伍,即類加載器鼻百,接口數(shù)組,InvocationHandler。
```? Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);```
(5)温艇、總結(jié)
我們總結(jié)下JDK動(dòng)態(tài)代理的實(shí)現(xiàn)步驟:
第一步:創(chuàng)建接口因悲,JDK動(dòng)態(tài)代理基于接口實(shí)現(xiàn),所以接口必不可少(準(zhǔn)備工作)
第二步:實(shí)現(xiàn)InvocationHandler接口勺爱,重寫invoke方法(準(zhǔn)備工作)
第三步:調(diào)用Proxy的靜態(tài)方法newProxyInstance方法生成代理實(shí)例(生成實(shí)例時(shí)需要提供類加載器晃琳,我們可以使用接口類的加載器即可)
第四步:使用新生成的代理實(shí)例調(diào)用某個(gè)方法實(shí)現(xiàn)功能。
我們的動(dòng)態(tài)代理實(shí)現(xiàn)過程中根本沒有涉及到真實(shí)類實(shí)例琐鲁。
#參考文獻(xiàn)
https://www.cnblogs.com/V1haoge/p/5860749.html