<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          10分鐘帶你搞懂代理模式、靜態(tài)代理、JDK+CGLIB動(dòng)態(tài)代理

          共 9227字,需瀏覽 19分鐘

           ·

          2020-09-24 10:56


          • 1. 代理模式

          • 2. 靜態(tài)代理

          • 3. 動(dòng)態(tài)代理

            • 3.1. JDK 動(dòng)態(tài)代理機(jī)制

            • 3.2. CGLIB 動(dòng)態(tài)代理機(jī)制

            • 3.3. JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理對(duì)比

          • 4. 靜態(tài)代理和動(dòng)態(tài)代理的對(duì)比

          • 5. 總結(jié)


          1. 代理模式

          代理模式是一種比較好的理解的設(shè)計(jì)模式。簡(jiǎn)單來(lái)說(shuō)就是 我們使用代理對(duì)象來(lái)代替對(duì)真實(shí)對(duì)象(real object)的訪問(wèn),這樣就可以在不修改原目標(biāo)對(duì)象的前提下,提供額外的功能操作,擴(kuò)展目標(biāo)對(duì)象的功能。

          代理模式的主要作用是擴(kuò)展目標(biāo)對(duì)象的功能,比如說(shuō)在目標(biāo)對(duì)象的某個(gè)方法執(zhí)行前后你可以增加一些自定義的操作。

          舉個(gè)例子:你的找了一小紅來(lái)幫你問(wèn)話,小紅就看作是代理我的代理對(duì)象,代理的行為(方法)是問(wèn)話。

          Understanding the Proxy Design Pattern | by Mithun Sasidharan | Medium

          https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a

          代理模式有靜態(tài)代理和動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式,我們 先來(lái)看一下靜態(tài)代理模式的實(shí)現(xiàn)。

          2. 靜態(tài)代理

          靜態(tài)代理中,我們對(duì)目標(biāo)對(duì)象的每個(gè)方法的增強(qiáng)都是手動(dòng)完成的(后面會(huì)具體演示代碼),非常不靈活(比如接口一旦新增加方法,目標(biāo)對(duì)象和代理對(duì)象都要進(jìn)行修改)且麻煩(需要對(duì)每個(gè)目標(biāo)類(lèi)都單獨(dú)寫(xiě)一個(gè)代理類(lèi))。 實(shí)際應(yīng)用場(chǎng)景非常非常少,日常開(kāi)發(fā)幾乎看不到使用靜態(tài)代理的場(chǎng)景。

          上面我們是從實(shí)現(xiàn)和應(yīng)用角度來(lái)說(shuō)的靜態(tài)代理,從 JVM 層面來(lái)說(shuō), 靜態(tài)代理在編譯時(shí)就將接口、實(shí)現(xiàn)類(lèi)、代理類(lèi)這些都變成了一個(gè)個(gè)實(shí)際的 class 文件。

          靜態(tài)代理實(shí)現(xiàn)步驟:

          1. 定義一個(gè)接口及其實(shí)現(xiàn)類(lèi);
          2. 創(chuàng)建一個(gè)代理類(lèi)同樣實(shí)現(xiàn)這個(gè)接口
          3. 將目標(biāo)對(duì)象注注入進(jìn)代理類(lèi),然后在代理類(lèi)的對(duì)應(yīng)方法調(diào)用目標(biāo)類(lèi)中的對(duì)應(yīng)方法。這樣的話,我們就可以通過(guò)代理類(lèi)屏蔽對(duì)目標(biāo)對(duì)象的訪問(wèn),并且可以在目標(biāo)方法執(zhí)行前后做一些自己想做的事情。

          下面通過(guò)代碼展示!

          1.定義發(fā)送短信的接口

          public?interface?SmsService?{
          ????String?send(String?message);
          }

          2.實(shí)現(xiàn)發(fā)送短信的接口

          public?class?SmsServiceImpl?implements?SmsService?{
          ????public?String?send(String?message)?{
          ????????System.out.println("send?message:"?+?message);
          ????????return?message;
          ????}
          }

          3.創(chuàng)建代理類(lèi)并同樣實(shí)現(xiàn)發(fā)送短信的接口

          public?class?SmsProxy?implements?SmsService?{

          ????private?final?SmsService?smsService;

          ????public?SmsProxy(SmsService?smsService)?{
          ????????this.smsService?=?smsService;
          ????}

          ????@Override
          ????public?String?send(String?message)?{
          ????????//調(diào)用方法之前,我們可以添加自己的操作
          ????????System.out.println("before?method?send()");
          ????????smsService.send(message);
          ????????//調(diào)用方法之后,我們同樣可以添加自己的操作
          ????????System.out.println("after?method?send()");
          ????????return?null;
          ????}
          }

          4.實(shí)際使用

          public?class?Main?{
          ????public?static?void?main(String[]?args)?{
          ????????SmsService?smsService?=?new?SmsServiceImpl();
          ????????SmsProxy?smsProxy?=?new?SmsProxy(smsService);
          ????????smsProxy.send("java");
          ????}
          }

          運(yùn)行上述代碼之后,控制臺(tái)打印出:

          before?method?send()
          send?message:java
          after?method?send()

          可以輸出結(jié)果看出,我們已經(jīng)增加了 SmsServiceImplsend()方法。

          3. 動(dòng)態(tài)代理

          相比于靜態(tài)代理來(lái)說(shuō),動(dòng)態(tài)代理更加靈活。我們不需要針對(duì)每個(gè)目標(biāo)類(lèi)都單獨(dú)創(chuàng)建一個(gè)代理類(lèi),并且也不需要我們必須實(shí)現(xiàn)接口,我們可以直接代理實(shí)現(xiàn)類(lèi)( CGLIB 動(dòng)態(tài)代理機(jī)制)。

          從 JVM 角度來(lái)說(shuō),動(dòng)態(tài)代理是在運(yùn)行時(shí)動(dòng)態(tài)生成類(lèi)字節(jié)碼,并加載到 JVM 中的。

          說(shuō)到動(dòng)態(tài)代理,Spring AOP、RPC 框架應(yīng)該是兩個(gè)不得不的提的,它們的實(shí)現(xiàn)都依賴了動(dòng)態(tài)代理。

          動(dòng)態(tài)代理在我們?nèi)粘i_(kāi)發(fā)中使用的相對(duì)較小,但是在框架中的幾乎是必用的一門(mén)技術(shù)。學(xué)會(huì)了動(dòng)態(tài)代理之后,對(duì)于我們理解和學(xué)習(xí)各種框架的原理也非常有幫助。

          就 Java 來(lái)說(shuō),動(dòng)態(tài)代理的實(shí)現(xiàn)方式有很多種,比如 JDK 動(dòng)態(tài)代理CGLIB 動(dòng)態(tài)代理等等。

          guide-rpc-framework[1] 使用的是 JDK 動(dòng)態(tài)代理,我們先來(lái)看看 JDK 動(dòng)態(tài)代理的使用。

          另外,雖然 guide-rpc-framework[2] 沒(méi)有用到 CGLIB 動(dòng)態(tài)代理 ,我們這里還是簡(jiǎn)單介紹一下其使用以及和JDK 動(dòng)態(tài)代理的對(duì)比。

          3.1. JDK 動(dòng)態(tài)代理機(jī)制

          3.1.1. 介紹

          在 Java 動(dòng)態(tài)代理機(jī)制中 InvocationHandler 接口和 Proxy 類(lèi)是核心。

          Proxy 類(lèi)中使用頻率最高的方法是:newProxyInstance() ,這個(gè)方法主要用來(lái)生成一個(gè)代理對(duì)象。

          ????public?static?Object?newProxyInstance(ClassLoader?loader,
          ??????????????????????????????????????????Class[]?interfaces,
          ??????????????????????????????????????????InvocationHandler?h)

          ????????throws?IllegalArgumentException
          ????
          {
          ????????......
          ????}

          這個(gè)方法一共有 3 個(gè)參數(shù):

          1. loader :類(lèi)加載器,用于加載代理對(duì)象。
          2. interfaces : 被代理類(lèi)實(shí)現(xiàn)的一些接口;
          3. h : 實(shí)現(xiàn)了 InvocationHandler 接口的對(duì)象;

          要實(shí)現(xiàn)動(dòng)態(tài)代理的話,還必須需要實(shí)現(xiàn)InvocationHandler 來(lái)自定義處理邏輯。當(dāng)我們的動(dòng)態(tài)代理對(duì)象調(diào)用一個(gè)方法時(shí)候,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)到實(shí)現(xiàn)InvocationHandler 接口類(lèi)的 invoke 方法來(lái)調(diào)用。

          public?interface?InvocationHandler?{

          ????/**
          ?????*?當(dāng)你使用代理對(duì)象調(diào)用方法的時(shí)候?qū)嶋H會(huì)調(diào)用到這個(gè)方法
          ?????*/

          ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)
          ????????throws?Throwable
          ;
          }

          invoke() 方法有下面三個(gè)參數(shù):

          1. proxy :動(dòng)態(tài)生成的代理類(lèi)
          2. method : 與代理類(lèi)對(duì)象調(diào)用的方法相對(duì)應(yīng)
          3. args : 當(dāng)前 method 方法的參數(shù)

          也就是說(shuō):你通過(guò)Proxy 類(lèi)的 newProxyInstance() 創(chuàng)建的代理對(duì)象在調(diào)用方法的時(shí)候,實(shí)際會(huì)調(diào)用到實(shí)現(xiàn)InvocationHandler 接口的類(lèi)的 invoke()方法。 你可以在 invoke() 方法中自定義處理邏輯,比如在方法執(zhí)行前后做什么事情。

          3.1.2. JDK 動(dòng)態(tài)代理類(lèi)使用步驟

          1. 定義一個(gè)接口及其實(shí)現(xiàn)類(lèi);
          2. 自定義 InvocationHandler 并重寫(xiě)invoke方法,在 invoke 方法中我們會(huì)調(diào)用原生方法(被代理類(lèi)的方法)并自定義一些處理邏輯;
          3. 通過(guò) Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) 方法創(chuàng)建代理對(duì)象;

          3.1.3. 代碼示例

          這樣說(shuō)可能會(huì)有點(diǎn)空洞和難以理解,我上個(gè)例子,大家感受一下吧!

          1.定義發(fā)送短信的接口

          public?interface?SmsService?{
          ????String?send(String?message);
          }

          2.實(shí)現(xiàn)發(fā)送短信的接口

          public?class?SmsServiceImpl?implements?SmsService?{
          ????public?String?send(String?message)?{
          ????????System.out.println("send?message:"?+?message);
          ????????return?message;
          ????}
          }

          3.定義一個(gè) JDK 動(dòng)態(tài)代理類(lèi)

          import?java.lang.reflect.InvocationHandler;
          import?java.lang.reflect.InvocationTargetException;
          import?java.lang.reflect.Method;

          /**
          ?*?@author?shuang.kou
          ?*?@createTime?2020年05月11日?11:23:00
          ?*/

          public?class?DebugInvocationHandler?implements?InvocationHandler?{
          ????/**
          ?????*?代理類(lèi)中的真實(shí)對(duì)象
          ?????*/

          ????private?final?Object?target;

          ????public?DebugInvocationHandler(Object?target)?{
          ????????this.target?=?target;
          ????}


          ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?InvocationTargetException,?IllegalAccessException?{
          ????????//調(diào)用方法之前,我們可以添加自己的操作
          ????????System.out.println("before?method?"?+?method.getName());
          ????????Object?result?=?method.invoke(target,?args);
          ????????//調(diào)用方法之后,我們同樣可以添加自己的操作
          ????????System.out.println("after?method?"?+?method.getName());
          ????????return?result;
          ????}
          }

          invoke() 方法: 當(dāng)我們的動(dòng)態(tài)代理對(duì)象調(diào)用原生方法的時(shí)候,最終實(shí)際上調(diào)用到的是 invoke() 方法,然后 invoke() 方法代替我們?nèi)フ{(diào)用了被代理對(duì)象的原生方法。

          4.獲取代理對(duì)象的工廠類(lèi)

          public?class?JdkProxyFactory?{
          ????public?static?Object?getProxy(Object?target)?{
          ????????return?Proxy.newProxyInstance(
          ????????????????target.getClass().getClassLoader(),?//?目標(biāo)類(lèi)的類(lèi)加載
          ????????????????target.getClass().getInterfaces(),??//?代理需要實(shí)現(xiàn)的接口,可指定多個(gè)
          ????????????????new?DebugInvocationHandler(target)???//?代理對(duì)象對(duì)應(yīng)的自定義?InvocationHandler
          ????????);
          ????}
          }

          getProxy() :主要通過(guò)Proxy.newProxyInstance()方法獲取某個(gè)類(lèi)的代理對(duì)象

          5.實(shí)際使用

          SmsService?smsService?=?(SmsService)?JdkProxyFactory.getProxy(new?SmsServiceImpl());
          smsService.send("java");

          運(yùn)行上述代碼之后,控制臺(tái)打印出:

          before?method?send
          send?message:java
          after?method?send

          3.2. CGLIB 動(dòng)態(tài)代理機(jī)制

          3.2.1. 介紹

          JDK 動(dòng)態(tài)代理有一個(gè)最致命的問(wèn)題是其只能代理實(shí)現(xiàn)了接口的類(lèi)。

          為了解決這個(gè)問(wèn)題,我們可以用 CGLIB 動(dòng)態(tài)代理機(jī)制來(lái)避免。

          CGLIB[3](Code Generation Library)是一個(gè)基于ASM[4]的字節(jié)碼生成庫(kù),它允許我們?cè)谶\(yùn)行時(shí)對(duì)字節(jié)碼進(jìn)行修改和動(dòng)態(tài)生成。CGLIB 通過(guò)繼承方式實(shí)現(xiàn)代理。很多知名的開(kāi)源框架都使用到了CGLIB[5], 例如 Spring 中的 AOP 模塊中:如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,則默認(rèn)采用 JDK 動(dòng)態(tài)代理,否則采用 CGLIB 動(dòng)態(tài)代理。

          在 CGLIB 動(dòng)態(tài)代理機(jī)制中 MethodInterceptor 接口和 Enhancer 類(lèi)是核心。

          你需要自定義 MethodInterceptor 并重寫(xiě) intercept 方法,intercept 用于攔截增強(qiáng)被代理類(lèi)的方法。

          public?interface?MethodInterceptor
          extends?Callback
          {
          ????//?攔截被代理類(lèi)中的方法
          ????public?Object?intercept(Object?obj,?java.lang.reflect.Method?method,?Object[]?args,
          ???????????????????????????????MethodProxy?proxy)
          ?throws?Throwable
          ;
          }

          1. obj :被代理的對(duì)象(需要增強(qiáng)的對(duì)象)
          2. method :被攔截的方法(需要增強(qiáng)的方法)
          3. args :方法入?yún)?/section>
          4. methodProxy :用于調(diào)用原始方法

          你可以通過(guò) Enhancer類(lèi)來(lái)動(dòng)態(tài)獲取被代理類(lèi),當(dāng)代理類(lèi)調(diào)用方法的時(shí)候,實(shí)際調(diào)用的是 MethodInterceptor 中的 intercept 方法。

          3.2.2. CGLIB 動(dòng)態(tài)代理類(lèi)使用步驟

          1. 定義一個(gè)類(lèi);
          2. 自定義 MethodInterceptor 并重寫(xiě) intercept 方法,intercept 用于攔截增強(qiáng)被代理類(lèi)的方法,和 JDK 動(dòng)態(tài)代理中的 invoke 方法類(lèi)似;
          3. 通過(guò) Enhancer 類(lèi)的 create()創(chuàng)建代理類(lèi);

          3.2.3. 代碼示例

          不同于 JDK 動(dòng)態(tài)代理不需要額外的依賴。CGLIB[6](Code Generation Library) 實(shí)際是屬于一個(gè)開(kāi)源項(xiàng)目,如果你要使用它的話,需要手動(dòng)添加相關(guān)依賴。

          <dependency>
          ??<groupId>cglibgroupId>
          ??<artifactId>cglibartifactId>
          ??<version>3.3.0version>
          dependency>

          1.實(shí)現(xiàn)一個(gè)使用阿里云發(fā)送短信的類(lèi)

          package?github.javaguide.dynamicProxy.cglibDynamicProxy;

          public?class?AliSmsService?{
          ????public?String?send(String?message)?{
          ????????System.out.println("send?message:"?+?message);
          ????????return?message;
          ????}
          }

          2.自定義 MethodInterceptor(方法攔截器)

          import?net.sf.cglib.proxy.MethodInterceptor;
          import?net.sf.cglib.proxy.MethodProxy;

          import?java.lang.reflect.Method;

          /**
          ?*?自定義MethodInterceptor
          ?*/

          public?class?DebugMethodInterceptor?implements?MethodInterceptor?{


          ????/**
          ?????*?@param?o???????????被代理的對(duì)象(需要增強(qiáng)的對(duì)象)
          ?????*?@param?method??????被攔截的方法(需要增強(qiáng)的方法)
          ?????*?@param?args????????方法入?yún)?br>?????*?@param?methodProxy?用于調(diào)用原始方法
          ?????*/

          ????@Override
          ????public?Object?intercept(Object?o,?Method?method,?Object[]?args,?MethodProxy?methodProxy)?throws?Throwable?{
          ????????//調(diào)用方法之前,我們可以添加自己的操作
          ????????System.out.println("before?method?"?+?method.getName());
          ????????Object?object?=?methodProxy.invokeSuper(o,?args);
          ????????//調(diào)用方法之后,我們同樣可以添加自己的操作
          ????????System.out.println("after?method?"?+?method.getName());
          ????????return?object;
          ????}

          }

          3.獲取代理類(lèi)

          import?net.sf.cglib.proxy.Enhancer;

          public?class?CglibProxyFactory?{

          ????public?static?Object?getProxy(Class?clazz)?{
          ????????//?創(chuàng)建動(dòng)態(tài)代理增強(qiáng)類(lèi)
          ????????Enhancer?enhancer?=?new?Enhancer();
          ????????//?設(shè)置類(lèi)加載器
          ????????enhancer.setClassLoader(clazz.getClassLoader());
          ????????//?設(shè)置被代理類(lèi)
          ????????enhancer.setSuperclass(clazz);
          ????????//?設(shè)置方法攔截器
          ????????enhancer.setCallback(new?DebugMethodInterceptor());
          ????????//?創(chuàng)建代理類(lèi)
          ????????return?enhancer.create();
          ????}
          }

          4.實(shí)際使用

          AliSmsService?aliSmsService?=?(AliSmsService)?CglibProxyFactory.getProxy(AliSmsService.class);
          aliSmsService.send("java");

          運(yùn)行上述代碼之后,控制臺(tái)打印出:

          before?method?send
          send?message:java
          after?method?send

          3.3. JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理對(duì)比

          1. JDK 動(dòng)態(tài)代理只能只能代理實(shí)現(xiàn)了接口的類(lèi),而 CGLIB 可以代理未實(shí)現(xiàn)任何接口的類(lèi)。 另外, CGLIB 動(dòng)態(tài)代理是通過(guò)生成一個(gè)被代理類(lèi)的子類(lèi)來(lái)攔截被代理類(lèi)的方法調(diào)用,因此不能代理聲明為 final 類(lèi)型的類(lèi)和方法。
          2. 就二者的效率來(lái)說(shuō),大部分情況都是 JDK 動(dòng)態(tài)代理更優(yōu)秀,隨著 JDK 版本的升級(jí),這個(gè)優(yōu)勢(shì)更加明顯。

          4. 靜態(tài)代理和動(dòng)態(tài)代理的對(duì)比

          1. 靈活性 :動(dòng)態(tài)代理更加靈活,不需要必須實(shí)現(xiàn)接口,可以直接代理實(shí)現(xiàn)類(lèi),并且可以不需要針對(duì)每個(gè)目標(biāo)類(lèi)都創(chuàng)建一個(gè)代理類(lèi)。另外,靜態(tài)代理中,接口一旦新增加方法,目標(biāo)對(duì)象和代理對(duì)象都要進(jìn)行修改,這是非常麻煩的!
          2. JVM 層面 :靜態(tài)代理在編譯時(shí)就將接口、實(shí)現(xiàn)類(lèi)、代理類(lèi)這些都變成了一個(gè)個(gè)實(shí)際的 class 文件。而動(dòng)態(tài)代理是在運(yùn)行時(shí)動(dòng)態(tài)生成類(lèi)字節(jié)碼,并加載到 JVM 中的。

          5. 總結(jié)

          這篇文章中主要介紹了代理模式的兩種實(shí)現(xiàn):靜態(tài)代理以及動(dòng)態(tài)代理。涵蓋了靜態(tài)代理和動(dòng)態(tài)代理實(shí)戰(zhàn)、靜態(tài)代理和動(dòng)態(tài)代理的區(qū)別、JDK 動(dòng)態(tài)代理和 Cglib 動(dòng)態(tài)代理區(qū)別等內(nèi)容。

          文中涉及到的所有源碼,你可以在這里找到:https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy[7]

          參考資料

          [1]

          guide-rpc-framework: https://github.com/Snailclimb/guide-rpc-framework

          [2]

          guide-rpc-framework: https://github.com/Snailclimb/guide-rpc-framework

          [3]

          CGLIB: https://github.com/cglib/cglib

          [4]

          ASM: http://www.baeldung.com/java-asm

          [5]

          CGLIB: https://github.com/cglib/cglib

          [6]

          CGLIB: https://github.com/cglib/cglib

          [7]

          https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy: https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy

          瀏覽 138
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲无码视频免费看 | 日本午夜福利片 | 99热亚洲 | 青青草久草 | 深夜福利男女 |