原創(chuàng)文章甥厦,轉(zhuǎn)載請標(biāo)注出處:《Java基礎(chǔ)系列-靜態(tài)代理和動態(tài)代理》
1纺铭、動態(tài)代理(Dynamic Proxy)
代理分為靜態(tài)代理和動態(tài)代理,靜態(tài)代理是在編譯時就將接口刀疙、實現(xiàn)類舶赔、代理類一股腦兒全部手動完成,但如果我們需要很多的代理谦秧,每一個都這么手動的去創(chuàng)建實屬浪費時間竟纳,而且會有大量的重復(fù)代碼撵溃,此時我們就可以采用動態(tài)代理,動態(tài)代理可以在程序運行期間根據(jù)需要動態(tài)的創(chuàng)建代理類及其實例蚁袭,來完成具體的功能征懈。
其實方法直接調(diào)用就可以完成功能,為什么還要加個代理呢揩悄?
原因是采用代理模式可以有效的將具體的實現(xiàn)與調(diào)用方進行解耦卖哎,通過面向接口進行編碼完全將具體的實現(xiàn)隱藏在內(nèi)部。
2删性、代理實現(xiàn)的一般模式
其實代理的一般模式就是靜態(tài)代理的實現(xiàn)模式:首先創(chuàng)建一個接口(JDK代理都是面向接口的)亏娜,然后創(chuàng)建具體實現(xiàn)類來實現(xiàn)這個接口,在創(chuàng)建一個代理類同樣實現(xiàn)這個接口蹬挺,不同之處在于维贺,具體實現(xiàn)類的方法中需要將接口中定義的方法的業(yè)務(wù)邏輯功能實現(xiàn),而代理類中的方法只要調(diào)用具體類中的對應(yīng)方法即可巴帮,這樣我們在需要使用接口中的某個方法的功能時直接調(diào)用代理類的方法即可溯泣,將具體的實現(xiàn)類隱藏在底層。
- 第一步:定義總接口Iuser.java
package ceshi1;
public interface Iuser {
void eat(String s);
}
- 第二步:創(chuàng)建具體實現(xiàn)類UserImpl.java
package ceshi1;
public class UserImpl implements Iuser {
@Override
public void eat(String s) {
System.out.println("我要吃"+s);
}
}
- 第三步:創(chuàng)建代理類UserProxy.java
package ceshi1;
public class UserProxy implements Iuser {
private Iuser user = new UserImpl();
@Override
public void eat(String s) {
System.out.println("靜態(tài)代理前置內(nèi)容");
user.eat(s);
System.out.println("靜態(tài)代理后置內(nèi)容");
}
}
- 第四步:創(chuàng)建測試類ProxyTest.java
package ceshi1;
public class ProxyTest {
public static void main(String[] args) {
UserProxy proxy = new UserProxy();
proxy.eat("蘋果");
}
}
運行結(jié)果:
靜態(tài)代理前置內(nèi)容
我要吃蘋果
靜態(tài)代理后置內(nèi)容
3榕茧、JDK動態(tài)代理的實現(xiàn)
JDK動態(tài)代理的思維模式與之前的一般模式是一樣的垃沦,也是面向接口進行編碼,創(chuàng)建代理類將具體類隱藏解耦用押,不同之處在于代理類的創(chuàng)建時機不同肢簿,動態(tài)代理需要在運行時因需實時創(chuàng)建。
- 第一步:定義總接口Iuser.java
package ceshi1;
public interface Iuser {
void eat(String s);
}
- 第二步:創(chuàng)建具體實現(xiàn)類UserImpl.java
package ceshi1;
public class UserImpl implements Iuser {
@Override
public void eat(String s) {
System.out.println("我要吃"+s);
}
}
- 第三步:創(chuàng)建實現(xiàn)InvocationHandler接口的代理類
package ceshi1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxy implements InvocationHandler {
private Object object;//用于接收具體實現(xiàn)類的實例對象
//使用帶參數(shù)的構(gòu)造器來傳遞具體實現(xiàn)類的對象
public DynamicProxy(Object obj){
this.object = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
System.out.println("前置內(nèi)容");
method.invoke(object, args);
System.out.println("后置內(nèi)容");
return null;
}
}
- 第四步:創(chuàng)建測試類ProxyTest.java
package ceshi1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
Iuser user = new UserImpl();
InvocationHandler h = new DynamicProxy(user);
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
proxy.eat("蘋果");
}
}
運行結(jié)果為:
動態(tài)代理前置內(nèi)容
我要吃蘋果
動態(tài)代理后置內(nèi)容
4蜻拨、實現(xiàn)分析
4.1 首先我要說的就是接口池充,為什么JDK的動態(tài)代理是基本接口實現(xiàn)的呢?
因為通過使用接口指向?qū)崿F(xiàn)類的實例的多態(tài)實現(xiàn)方式缎讼,可以有效的將具體的實現(xiàn)與調(diào)用之間解耦收夸,便于后期修改與維護。
再具體的說就是我們在代理類中創(chuàng)建一個私有成員變量(private修飾)血崭,使用接口來指向?qū)崿F(xiàn)類的對象(純種的多態(tài)體現(xiàn)卧惜,向上轉(zhuǎn)型的體現(xiàn)),然后在該代理類中的方法中使用這個創(chuàng)建的實例來調(diào)用實現(xiàn)類中的相應(yīng)方法來完成業(yè)務(wù)邏輯功能功氨。
這么說起來,我之前說的“將具體實現(xiàn)類完全隱藏”就不怎么正確了手幢,可以改成捷凄,將具體實現(xiàn)類的細(xì)節(jié)向調(diào)用方完全隱藏(調(diào)用方調(diào)用的是代理類中的方法,而不是實現(xiàn)類中的方法)围来。
這就是面向接口編程跺涤,利用java的多態(tài)特性匈睁,實現(xiàn)程序代碼的解耦。
4.2 創(chuàng)建代理類的過程
如果你了解靜態(tài)代理桶错,那么你會發(fā)現(xiàn)動態(tài)代理的實現(xiàn)其實與靜態(tài)代理類似航唆,都需要創(chuàng)建代理類,但是不同之處也很明顯院刁,創(chuàng)建方式不同糯钙!
不同之處體現(xiàn)在靜態(tài)代理我們知根知底,我們知道要對哪個接口退腥、哪個實現(xiàn)類來創(chuàng)建代理類任岸,所以我們在編譯前就直接實現(xiàn)與實現(xiàn)類相同的接口,直接在實現(xiàn)的方法中調(diào)用實現(xiàn)類中的相應(yīng)(同名)方法即可狡刘;而動態(tài)代理不同享潜,我們不知道它什么時候創(chuàng)建,也不知道要創(chuàng)建針對哪個接口嗅蔬、實現(xiàn)類的代理類(因為它是在運行時因需實時創(chuàng)建的)剑按。
雖然二者創(chuàng)建時機不同,創(chuàng)建方式也不相同澜术,但是原理是相同的艺蝴,不同之處僅僅是:靜態(tài)代理可以直接編碼創(chuàng)建,而動態(tài)代理是利用反射機制來抽象出代理類的創(chuàng)建過程瘪板。
讓我們來分析一下之前的代碼來驗證一下上面的說辭:
- 第一點:靜態(tài)代理需要實現(xiàn)與實現(xiàn)類相同的接口吴趴,而動態(tài)代理需要實現(xiàn)的是固定的Java提供的內(nèi)置接口(一種專門提供來創(chuàng)建動態(tài)代理的接口)InvocationHandler接口,因為java在接口中提供了一個可以被自動調(diào)用的方法invoke侮攀,這個之后再說锣枝。
- 第二點:
private Object object;
public UserProxy(Object obj){this.object = obj;}
這幾行代碼與靜態(tài)代理之中在代理類中定義的接口指向具體實現(xiàn)類的實例的代碼異曲同工,通過這個構(gòu)造器可以創(chuàng)建代理類的實例兰英,創(chuàng)建的同時還能將具體實現(xiàn)類的實例與之綁定(object指的就是實現(xiàn)類的實例撇叁,這個實例需要在測試類中創(chuàng)建并作為參數(shù)來創(chuàng)建代理類的實例),實現(xiàn)了靜態(tài)代理類中private Iuser user = new UserImpl();一行代碼的作用相近畦贸,這里為什么不是相同陨闹,而是相近呢,主要就是因為靜態(tài)代理的那句代碼中包含的實現(xiàn)類的實例的創(chuàng)建薄坏,而動態(tài)代理中實現(xiàn)類的創(chuàng)建需要在測試類中完成趋厉,所以此處是相近。
- 第三點:invoke(Object proxy, Method method, Object[] args)方法胶坠,該方法是InvocationHandler接口中定義的唯一方法君账,該方法在調(diào)用指定的具體方法時會自動調(diào)用。其參數(shù)為:代理實例沈善、調(diào)用的方法乡数、方法的參數(shù)列表椭蹄。
在這個方法中我們定義了幾乎和靜態(tài)代理相同的內(nèi)容,僅僅是在方法的調(diào)用上不同净赴,不同的原因與之前分析的一樣(創(chuàng)建時機的不同绳矩,創(chuàng)建的方式的不同,即反射)玖翅,Method類是反射機制中一個重要的類翼馆,用于封裝方法,該類中有一個方法那就是invoke(Object object,Object...args)方法烧栋,其參數(shù)分別表示:所調(diào)用方法所屬的類的對象和方法的參數(shù)列表写妥,這里的參數(shù)列表正是從測試類中傳遞到代理類中的invoke方法三個參數(shù)中最后一個參數(shù)(調(diào)用方法的參數(shù)列表)中,在傳遞到method的invoke方法中的第二個參數(shù)中的(此處有點啰嗦)审姓。
- 第四點:測試類中的異同
靜態(tài)代理中我們測試類中直接創(chuàng)建代理類的對象珍特,使用代理類的對象來調(diào)用其方法即可,若是別的接口(這里指的是別的調(diào)用方)要調(diào)用Iuser的方法魔吐,也可以使用此法扎筒。
動態(tài)代理中要復(fù)雜的多,首先我們要將之前提到的實現(xiàn)類的實例創(chuàng)建(補充完整)酬姆,然后利用這個實例作為參數(shù)嗜桌,調(diào)用代理來的帶參構(gòu)造器來創(chuàng)建“代理類實例對象”,這里加引號的原因是因為它并不是真正的代理類的實例對象辞色,而是創(chuàng)建真正代理類實例的一個參數(shù)骨宠,這個實現(xiàn)了InvocationHandler接口的類嚴(yán)格意義上來說并不是代理類,我們可以將其看作是創(chuàng)建代理類的必備中間環(huán)節(jié)相满,這是一個調(diào)用處理器层亿,也就是處理方法調(diào)用的一個類,不是真正意義上的代理類立美,可以這么說:創(chuàng)建一個方法調(diào)用處理器實例匿又。
下面才是真正的代理類實例的創(chuàng)建,之前創(chuàng)建的”代理類實例對象“僅僅是一個參數(shù):
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
這里使用了動態(tài)代理所依賴的第二個重要類Proxy建蹄,此處使用了其靜態(tài)方法來創(chuàng)建一個代理實例碌更,其參數(shù)分別是:類加載器(可為父類的類加載器)、接口數(shù)組洞慎、方法調(diào)用處理器實例痛单。
這里同樣使用了多態(tài),使用接口指向代理類的實例劲腿,最后會用該實例來進行具體方法的調(diào)用即可旭绒。
4.3 InvocationHandler
InvocationHandler是JDK中提供的專門用于實現(xiàn)基于接口的動態(tài)代理的接口,主要用于進行方法調(diào)用模塊,而代理類和實例的生成需要借助Proxy類完成快压。
每個代理類的實例的調(diào)用處理器都是實現(xiàn)該接口實現(xiàn)的,而且是必備的垃瞧,即每個動態(tài)代理實例的實現(xiàn)都必須擁有實現(xiàn)該接口的調(diào)用處理器蔫劣,也可以這么說,每個動態(tài)代理實例都對應(yīng)一個調(diào)用處理器个从。
這里要區(qū)分兩個概念脉幢,代理類和代理實例,調(diào)用處理器是在創(chuàng)建代理實例的時候才與其關(guān)聯(lián)起來的嗦锐,所以它與代理實例是一一對應(yīng)的嫌松,而不是代理類。
4.4 Proxy
Proxy類是JDK提供的用于生成動態(tài)代理類和其實例的類奕污。
我們可以通過Proxy中的靜態(tài)方法getProxyClass來生成代理類萎羔,需要的參數(shù)為類加載器和接口列表(數(shù)組),然后再通過反射調(diào)用代理類的構(gòu)造器來生成代理實例碳默,需要以一個InvocationHandler作為參數(shù)(體現(xiàn)出方法調(diào)用是與實例相關(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ù)髓废,即類加載器,接口數(shù)組该抒,InvocationHandler慌洪。
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);
4.5 總結(jié)
我們總結(jié)下JDK動態(tài)代理的實現(xiàn)步驟:
- 第一步:創(chuàng)建接口,JDK動態(tài)代理基于接口實現(xiàn)凑保,所以接口必不可少(準(zhǔn)備工作)
- 第二步:實現(xiàn)InvocationHandler接口冈爹,重寫invoke方法(準(zhǔn)備工作)
- 第三步:調(diào)用Proxy的靜態(tài)方法newProxyInstance方法生成代理實例(生成實例時需要提供類加載器,我們可以使用接口類的加載器即可)
- 第四步:使用新生成的代理實例調(diào)用某個方法實現(xiàn)功能愉适。
我們的動態(tài)代理實現(xiàn)過程中根本沒有涉及到真實類實例犯助。
5、Cglib動態(tài)代理的實現(xiàn)
JDK動態(tài)代理擁有局限性维咸,那就是必須面向接口編程剂买,沒有接口就無法實現(xiàn)代理,我們也不可能為了代理而為每個需要實現(xiàn)代理的類強行添加毫無意義的接口癌蓖,這時我們需要Cglib瞬哼,這種依靠繼承來實現(xiàn)動態(tài)代理的方式,不再要求我們必須要有接口租副。
- 第一步:添加Cglib的Maven依賴
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
- 第二步:創(chuàng)建具體實現(xiàn)類User.java
public class User {
public void eat(String s){
System.out.println("我要吃" + s);
}
}
- 第三步:創(chuàng)建實現(xiàn)MethodInterceptor接口的代理類
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class UserInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("預(yù)處理");
Object object = methodProxy.invokeSuper(o,objects);
System.out.println("后處理");
return object;
}
}
- 第四步:創(chuàng)建測試類ProxyTest.java
import net.sf.cglib.proxy.Enhancer;
public class ProxyTest {
public static void main(String[] args){
Enhancer enchancer = new Enhancer();//字節(jié)碼增強器
enchancer.setSuperclass(User.class);//設(shè)置被代理類為父類
enchancer.setCallback(new UserInterceptor());//設(shè)置回調(diào)
User user = (User)enchancer.create();//創(chuàng)建代理實例
user.eat("葡萄");
}
}
執(zhí)行結(jié)果:
預(yù)處理
我要吃葡萄
后處理