Java基礎(chǔ)系列-靜態(tài)代理和動態(tài)代理


原創(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ù)處理
我要吃葡萄
后處理
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坐慰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子用僧,更是在濱河造成了極大的恐慌结胀,老刑警劉巖赞咙,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異糟港,居然都是意外死亡攀操,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門秸抚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來速和,“玉大人,你說我怎么就攤上這事剥汤〉叻牛” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵吭敢,是天一觀的道長碰凶。 經(jīng)常有香客問我,道長鹿驼,這世上最難降的妖魔是什么痒留? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蠢沿,結(jié)果婚禮上伸头,老公的妹妹穿的比我還像新娘。我一直安慰自己舷蟀,他們只是感情好恤磷,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著野宜,像睡著了一般扫步。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匈子,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天河胎,我揣著相機與錄音,去河邊找鬼虎敦。 笑死游岳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的其徙。 我是一名探鬼主播胚迫,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼唾那!你這毒婦竟也來了访锻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎期犬,沒想到半個月后河哑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡龟虎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年灾馒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遣总。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖轨功,靈堂內(nèi)的尸體忽然破棺而出旭斥,到底是詐尸還是另有隱情,我是刑警寧澤古涧,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布垂券,位于F島的核電站,受9級特大地震影響羡滑,放射性物質(zhì)發(fā)生泄漏菇爪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一柒昏、第九天 我趴在偏房一處隱蔽的房頂上張望凳宙。 院中可真熱鬧,春花似錦职祷、人聲如沸氏涩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽是尖。三九已至,卻和暖如春泥耀,著一層夾襖步出監(jiān)牢的瞬間饺汹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工痰催, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兜辞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓夸溶,卻偏偏與公主長得像弦疮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蜘醋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一胁塞、基本概念 1.什么是代理? 在闡述JDK動態(tài)代理之前,我們很有必要先來弄明白代理的概念啸罢。代理這個詞本身并不是計...
    小李彈花閱讀 16,440評論 2 40
  • https://blog.csdn.net/luanlouis/article/details/24589193 ...
    小陳阿飛閱讀 864評論 1 1
  • 一编检、代理概念 為某個對象提供一個代理,以控制對這個對象的訪問扰才。 代理類和委托類有共同的父類或父接口允懂,這樣在任何使用...
    wyatt_plus閱讀 791評論 0 5
  • #靜態(tài)代理 靜態(tài)代理是在編譯時就將接口、實現(xiàn)類衩匣、代理類一股腦兒全部手動完成蕾总,但如果我們需要很多的代理,每一個都...
    歲月無痕_a71d閱讀 444評論 0 0
  • 這次評論的作者是我的好朋友倩瑩女士琅捏。云南人生百,從不久的幾年前開始系統(tǒng)創(chuàng)作,曾下南洋執(zhí)教柄延,為民謠作詞蚀浆,大部分作品為現(xiàn)代...
    徐子為閱讀 257評論 0 1