1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Java中的靜態(tài)代理和動態(tài)代理

        共 7262字,需瀏覽 15分鐘

         ·

        2020-09-06 15:34

        點擊上方藍色字體,選擇“標(biāo)星公眾號”

        優(yōu)質(zhì)文章,第一時間送達

        ? 作者?|????沙漠駱駝

        來源 |? urlify.cn/rIv26v

        66套java從入門到精通實戰(zhàn)課程分享?

        最近在學(xué)習(xí)MyBatis源碼,了解到MyBatis里之所以只需要開發(fā)者編寫Mapper接口即可執(zhí)行SQL,就是因為JDK的動態(tài)代理在背后默默為我們做了很多事情。但是我自己對動態(tài)代理還只是一知半解,于是手機整理資料學(xué)習(xí),整理了這篇筆記。

        說到動態(tài)代理,首先要講的就是設(shè)計模式中的代理模式,而對于代理,根據(jù)創(chuàng)建代理類的時間點,又可以分為靜態(tài)代理動態(tài)代理

        1. 代理模式

        代理模式(Proxy),為其他對象提供一種代理以控制對這個對象的訪問。他的特征是代理類與委托類實現(xiàn)相同的接口,代理類主要負責(zé)為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類以及事后處理消息等。代理類與委托類質(zhì)檢通常會存在關(guān)聯(lián)關(guān)系,一個代理類的對象與一個委托類的對象關(guān)聯(lián),代理類的對象本身并不真正實現(xiàn)服務(wù),而是通過調(diào)用委托類的對象的相關(guān)方法,來提供特定的服務(wù)。簡單來說就是,我們訪問實際對象時,是通過代理對象來訪問的,代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,使我們可以附加多種用途。

        Tips:

        • 委托類:指的是代理模式中的被代理對象

        • 代理類:指的是生成的代表委托類的一個角色

        Java代理模式實現(xiàn)方式,主要有以下五種方法

        1. 靜態(tài)代理,由開發(fā)者編輯代理類代碼,實現(xiàn)代理模式。在編譯器就生成了代理類。

        2. 基于JDK實現(xiàn)動態(tài)代理,通過JDK提供的工具方法Proxy.newProxyInstance()動態(tài)構(gòu)建全新的代理類字節(jié)碼文件并實例化對象返回,這個代理類繼承Proxy類,并持有InvocationHandler接口引用。JDK動態(tài)代理是由Java內(nèi)部的反射機制來實例化代理對象,并代理的調(diào)用委托類方法。

        3. 基于CGLib動態(tài)代理模式,原理是繼承被代理類生成字代理類,不用實現(xiàn)接口,只需要被代理類是非final類即可。CGLib動態(tài)代理底層是借助asm字節(jié)碼技術(shù)。

        4. 基于AspectJ實現(xiàn)動態(tài)代理。修改目標(biāo)類的字節(jié),織入代理的字節(jié),在程序編譯的時候插入動態(tài)代理的字節(jié)碼,不會生成全新的Class文件。

        5. 基于instrumentation實現(xiàn)動態(tài)代理。修改目標(biāo)類的字節(jié)碼、類加載的時候動態(tài)攔截去修改,基于javaagent實現(xiàn)-javaagent:spring-instrument-4.3.8.RELEASE.jar,類加載的時候插入動態(tài)代理的字節(jié)碼,不會生成全新的Class文件。

        2. 靜態(tài)代理

        靜態(tài)代理是代理類在編譯器就創(chuàng)建好了,不是編譯器生成的代理類,而是我們手動創(chuàng)建的類。在編譯時就已經(jīng)將接口、本代理類和代理類確定下來。軟件設(shè)計模式中所指的代理一般就是說的靜態(tài)代理。

        Subject類,定義了RealSubject和Proxy的共用接口,這樣就在任何使用RealSubject的地方都可以使用Proxy。

        public?interface?Subject?{

        ????/**
        ?????* doSomething()
        ?????*/

        ????void?doSomething();

        }

        RealSubject類,定義Proxy所代表的真實實體。

        public?class?RealSubject?implements?Subject?{
        ????@Override
        ????public?void?doSomething()?{
        ????????// 委托類執(zhí)行操作
        ????????System.out.println("RealSubject.doSomething()");
        ????}
        }

        ProxySubject類,保存一個引用使得代理可以訪問實體,并提供一個與Subject的接口相同的接口,這樣代理就可以用來替代實體。

        public?class?ProxySubject?implements?Subject?{

        ????private?RealSubject realSubject;

        ????/**
        ?????* 向代理類中注入委托類對象
        ?????*
        ?????* @param realSubject 委托類對象
        ?????*/

        ????public?ProxySubject(RealSubject realSubject){
        ????????this.realSubject = realSubject;
        ????}

        ????/**
        ?????* 代理類執(zhí)行操作
        ?????*/

        ????@Override
        ????public?void?doSomething()
        {
        ????????System.out.println("代理類調(diào)用委托類方法之前");
        ????????realSubject.doSomething();
        ????????System.out.println("代理類調(diào)用委托類方法之后");
        ????}
        }

        測試第一種方式,不使用代理類,直接使用簡單委托類執(zhí)行。

        public?static?void?main(String[] args)?{
        ????????RealSubject realSubject = new?RealSubject();
        ????????
        ????????realSubject.doSomething();
        ????}

        輸出:

        RealSubject.doSomething()

        測試第二種方式,使用代理類,執(zhí)行增強邏輯。

        public?static?void?main(String[] args)?{
        ????????RealSubject realSubject = new?RealSubject();

        ????????ProxySubject proxySubject = new?ProxySubject(realSubject);
        ????????
        ????????proxySubject.doSomething();
        ????}

        輸出:

        代理類調(diào)用委托類方法之前
        RealSubject.doSomething()
        代理類調(diào)用委托類方法之后

        我們在創(chuàng)建代理對象時,通過構(gòu)造器塞入一個目標(biāo)對象,然后在代理對象的方法內(nèi)部調(diào)用目標(biāo)對象同名方法,并在調(diào)用前后做增強邏輯。也就是說,代理對象 = 增強代碼 + 目標(biāo)對象。有了代理對象后,就不用原對象了。

        靜態(tài)代理的缺陷
        開發(fā)者需要手動為目標(biāo)類編寫對應(yīng)的代理類,而且要對類中的每個方法都編寫增強邏輯的代碼,如果當(dāng)前系統(tǒng)中已經(jīng)存在成百上千個類,工作量太大了,且重復(fù)代碼過多。所以,有沒有什么方法能讓我們少寫或者不寫代理類,卻能完成代理功能?

        3. 動態(tài)代理

        靜態(tài)代理是代理類在代碼運行前已經(jīng)創(chuàng)建好,并生成class文件;動態(tài)代理類是代理類在程序運行時創(chuàng)建的代理模式。動態(tài)代理類的代理類并不是在Java代碼中定義的,而是在運行時根據(jù)我們在Java代碼中的“指示”動態(tài)生成的。相比于靜態(tài)代理,動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理,而不用修改每個代理類中的方法。

        3.1 JDK動態(tài)代理

        基于接口實現(xiàn)。Java的java.lang.reflect包下提供了Proxy類和一個 InvocationHandler 接口,這個類Proxy定義了生成JDK動態(tài)代理類的方法getProxyClass(ClassLoader loader,Class... interfaces)生成動態(tài)代理類,返回class實例代表一個class文件??梢员4嬖?class 文件查看jdk生成的代理類文件長什么樣。該生成的動態(tài)代理類繼承Proxy類,(重要特性) ,并實現(xiàn)公共接口。InvocationHandler這個接口,是被動態(tài)代理類回調(diào)的接口,我們所有需要增加的針對委托類的統(tǒng)一增強邏輯都增加到invoke()方法里面,在調(diào)用委托類接口方法之前或之后。

        例子。任然使用上面靜態(tài)代理里的類,只不過這次我們不會再用到代理類ProxySubject,而是讓JDK去幫我們生成代理類。方法如下:

        public?static?void?main(String[] args) {

        ????????// 實例化目標(biāo)對象
        ????????Subject subject = new?RealSubject();

        ????????// 獲取代理對象
        ????????Subject proxyInstance = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(),
        ????????????????new?InvocationHandler() {
        ????????????????????@Override
        ????????????????????public?Object?invoke(Object?proxy, Method method, Object[] args) throws Throwable {
        ????????????????????????// 增強邏輯
        ????????????????????????System.out.println("動態(tài)代理調(diào)用委托類方法之前");
        ????????????????????????Object?invoke = method.invoke(subject, args);
        ????????????????????????System.out.println("動態(tài)代理調(diào)用委托類方法之后");
        ????????????????????????return?invoke;
        ????????????????????}
        ????????????????});

        ????????// 執(zhí)行目標(biāo)方法
        ????????proxyInstance.doSomething();
        ????}

        輸出:

        動態(tài)代理調(diào)用委托類方法之前
        RealSubject.doSomething()
        動態(tài)代理調(diào)用委托類方法之后

        Jdk為我們的生成了一個叫$Proxy0(這個名字后面的0是編號,有多個代理類會一次遞增)的代理類,這個類文件時默認不會保存在文件,放在內(nèi)存中的,我們在創(chuàng)建代理對象時,就是通過反射獲得這個類的構(gòu)造方法,然后創(chuàng)建代理對象實例。通過對這個生成的代理類源碼的查看,我們很容易能看出,動態(tài)代理實現(xiàn)的具體過程。

        我們可以對 InvocationHandler看做一個中介類,中介類持有一個被代理對象,被Proxy類回調(diào)。在invoke方法中調(diào)用了被代理對象的相應(yīng)方法。通過聚合方式持有被代理對象的引用,把客戶端對invoke的調(diào)用最終都轉(zhuǎn)為對被代理對象的調(diào)用。

        客戶端代碼通過代理類引用調(diào)用接口方法時,通過代理類關(guān)聯(lián)的中介類對象引用來調(diào)用中介類對象的invoke方法,從而達到代理執(zhí)行被代理對象的方法。也就是說,動態(tài)代理Proxy類提供了模板實現(xiàn),對外提供擴展點,外部通過實現(xiàn)InvocationHandler接口將被代理類納入JDK代理類Proxy。

        JDK動態(tài)代理特點總結(jié)

        1. 生成的代理類:$Proxy0 extends Proxy implements Subject,我們看到代理類繼承了Proxy類,Java的繼承機制決定了JDK動態(tài)代理類們無法實現(xiàn)對 類 的動態(tài)代理。所以也就決定了JDK動態(tài)代理只能對接口進行代理。

        2. 每個生成的動態(tài)代理實例都會關(guān)聯(lián)一個調(diào)用處理器對象,可以通過 Proxy 提供的靜態(tài)方法 getInvocationHandler去獲得代理類實例的調(diào)用處理器對象。在代理類實例上調(diào)用其代理的接口中所聲明的方法時,這些方法最終都會由調(diào)用處理器的 invoke 方法執(zhí)行。

        3. 代理類的根類 java.lang.Object 中有三個方法也同樣會被分派到調(diào)用處理器的 invoke 方法執(zhí)行,它們是 hashCode,equals 和 toString,可能的原因有:一是因為這些方法為 public 且非 final 類型,能夠被代理類覆蓋;二是因為這些方法往往呈現(xiàn)出一個類的某種特征屬性,具有一定的區(qū)分度,所以為了保證代理類與委托類對外的一致性,這三個方法也應(yīng)該被調(diào)用處理器分派到委托類執(zhí)行。

        JDK動態(tài)代理的不足

        JDK動態(tài)代理的代理類字節(jié)碼在創(chuàng)建時,需要實現(xiàn)業(yè)務(wù)實現(xiàn)類所實現(xiàn)的接口作為參數(shù)。如果業(yè)務(wù)實現(xiàn)類是沒有實現(xiàn)接口而是直接定義業(yè)務(wù)方法的話,就無法使用JDK動態(tài)代理了。(JDK動態(tài)代理重要特點是代理接口)并且,如果業(yè)務(wù)實現(xiàn)類中新增了接口中沒有的方法,這些方法是無法被代理的(因為無法被調(diào)用)。動態(tài)代理只能對接口產(chǎn)生代理,不能對類產(chǎn)生代理。

        3.2 CGLib動態(tài)代理

        基于繼承。CGlib是針對類來實現(xiàn)代理的,他的原理是對代理的目標(biāo)類生成一個子類,并覆蓋其中方法實現(xiàn)增強,因為底層是基于創(chuàng)建被代理類的一個子類,所以它避免了JDK動態(tài)代理類的缺陷。但因為采用的是繼承,所以不能對final修飾的類進行代理。final修飾的類不可繼承。

        例子。

        public?class?CGlibSubject implements?MethodInterceptor {

        ????private?Object?target;

        ????public?Object?getInstance(Object?target) {
        ????????this.target = target;
        ????????Enhancer enhancer = new?Enhancer();
        ????????enhancer.setSuperclass(target.getClass());
        ????????// 設(shè)置回調(diào)方法
        ????????enhancer.setCallback(this);
        ????????// 創(chuàng)建代理對象
        ????????return?enhancer.create();
        ????}

        ????@Override
        ????public?Object?intercept(Object?o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        ????????System.out.println("CGlib動態(tài)代理調(diào)用委托類方法之前");
        ????????Object?result = methodProxy.invokeSuper(o, objects);
        ????????System.out.println("CGlib動態(tài)代理調(diào)用委托類方法之后");
        ????????return?result;
        ????}
        }

        測試:

        public?static?void?main?(String[] args)?{
        ????????CGlibSubject cglibSubject = new?CGlibSubject();
        ????????RealSubject instance = (RealSubject) cglibSubject.getInstance(new?RealSubject());
        ????????instance.doSomething();
        ????}

        輸出:

        CGlib動態(tài)代理調(diào)用委托類方法之前
        RealSubject.doSomething()
        CGlib動態(tài)代理調(diào)用委托類方法之后

        CGlib動態(tài)代理特點總結(jié)

        1. CGlib可以傳入接口也可以傳入普通的類,接口使用實現(xiàn)的方式,普通類使用會使用繼承的方式生成代理類;

        2. 由于是繼承方式,如果是static方法,private方法,final方法等描述的方法是不能被代理的;

        3. 做了方法訪問優(yōu)化,使用建立方法索引的方式避免了傳統(tǒng)JDK動態(tài)代理需要通過Method方法反射調(diào)用;

        4. 提供callback 和filter設(shè)計,可以靈活地給不同的方法綁定不同的callback。編碼更方便靈活;

        5. CGLIB會默認代理Object中equals,toString,hashCode,clone等方法。比JDK代理多了clone。

        4. 總結(jié)

        1. 靜態(tài)代理是通過在代碼中顯式編碼定義一個業(yè)務(wù)實現(xiàn)類的代理類,在代理類中對同名的業(yè)務(wù)方法進行包裝,用戶通過代理類調(diào)用委托類的業(yè)務(wù)方法;

        2. JDK動態(tài)代理是通過接口中的方法名,在動態(tài)生成的代理類中調(diào)用業(yè)務(wù)實現(xiàn)類的同名方法;

        3. CGlib動態(tài)代理是通過繼承業(yè)務(wù)類,生成的動態(tài)代理類是業(yè)務(wù)類的子類,通過重寫業(yè)務(wù)方法進行代理;

        4. 靜態(tài)代理在編譯時產(chǎn)生class字節(jié)碼文件,可以直接使用,效率高。動態(tài)代理必須實現(xiàn)InvocationHandler接口,通過invoke調(diào)用被委托類接口方法是通過反射方式,比較消耗系統(tǒng)性能,但可以減少代理類的數(shù)量,使用更靈活。cglib代理無需實現(xiàn)接口,通過生成類字節(jié)碼實現(xiàn)代理,比反射稍快,不存在性能問題,但cglib會繼承目標(biāo)對象,需要重寫方法,所以目標(biāo)對象不能為final類。

        參考文章

        1. 太好了!總算有人把動態(tài)代理、CGlib、AOP都說清楚了!

        2. Java 動態(tài)代理作用是什么?

        5. 代碼倉庫

        https://github.com/goSilver/daydayup/tree/master/java/src/designpattern/proxy



        粉絲福利:108本java從入門到大神精選電子書領(lǐng)取

        ???

        ?長按上方鋒哥微信二維碼?2 秒
        備注「1234」即可獲取資料以及
        可以進入java1234官方微信群



        感謝點贊支持下哈?


        瀏覽 49
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美三级不卡在线观看 | 我用双乳夹住他的巨大 | ass中国女演员裸体pics | 日本理论片午伦夜理片在线观看 | 国产青青草自拍 | 人人射 | 《喜爱夜蒲》大尺度片段 | 欧美一级黃色a片免费看视频 | 色婷婷五月直播 | 精品国产美女AV天堂 |