1. gzyueqian
      13352868059

      java工程師是怎么對Java Proxy 和 CGLIB 動態代理原理進行分解的?-學習分享

      更新時間: 2018-09-15 10:36:29來源: java培訓瀏覽量:3210

          這次小編不講別的就給大家說一下動態代理的原理。希望能幫到大家!

          什么是動態原理?

          動態代理在Java中有著廣泛的應用,例如Spring AOP,Hibernate數據查詢、測試框架的后端mock、RPC,Java注解對象獲取等。靜態代理的代理關系在編譯時就確定了,而動態代理的代理關系是在編譯期確定的。靜態代理實現簡單,適合于代理類較少且確定的情況,而動態代理則給我們提供了更大的靈活性。今天我們來探討Java中兩種常見的動態代理方式:JDK原生動態代理和CGLIB動態代理。

          一、JDK原生動態代理
          先從直觀的示例說起,假設我們有一個接口Hello和一個簡單實現HelloImp:

      // 接口
      interface Hello{
          String sayHello(String str);
      }
      // 實現
      class HelloImp implements Hello{
          @Override
          public String sayHello(String str) {
              return "HelloImp: " + str;
          }
      }

      這是Java種再常見不過的場景,使用接口制定協議,然后用不同的實現來實現具體行為。假設你已經拿到上述類庫,如果我們想通過日志記錄對sayHello()的調用,使用靜態代理可以這樣做:

      // 靜態代理方式
      class StaticProxiedHello implements Hello{
          ...
          private Hello hello = new HelloImp();
          @Override
          public String sayHello(String str) {
              logger.info("You said: " + str);
              return hello.sayHello(str);
          }
      }

          上例中靜態代理類StaticProxiedHello作為HelloImp的代理,實現了相同的Hello接口。用Java動態代理可以這樣做:

          首先實現一個InvocationHandler,方法調用會被轉發到該類的invoke()方法。
          然后在需要使用Hello的時候,通過JDK動態代理獲取Hello的代理對象。

      // Java Proxy
      // 1. 首先實現一個InvocationHandler,方法調用會被轉發到該類的invoke()方法。
      class LogInvocationHandler implements InvocationHandler{
          ...
          private Hello hello;
          public LogInvocationHandler(Hello hello) {
              this.hello = hello;
          }
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              if("sayHello".equals(method.getName())) {
                  logger.info("You said: " + Arrays.toString(args));
              }
              return method.invoke(hello, args);
          }
      }
      // 2. 然后在需要使用Hello的時候,通過JDK動態代理獲取Hello的代理對象。
      Hello hello = (Hello)Proxy.newProxyInstance(
          getClass().getClassLoader(), // 1. 類加載器
          new Class<?>[] {Hello.class}, // 2. 代理需要實現的接口,可以有多個
          new LogInvocationHandler(new HelloImp()));// 3. 方法調用的實際處理者
      System.out.println(hello.sayHello("I love you!"));

          運行上述代碼輸出結果:

      日志信息: You said: [I love you!]
      HelloImp: I love you!

          上述代碼的關鍵是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法,該方法會根據指定的參數動態創建代理對象。三個參數的意義如下:
          loader,指定代理對象的類加載器;
          interfaces,代理對象需要實現的接口,可以同時指定多個接口;
          handler,方法調用的實際處理者,代理對象的方法調用都會轉發到這里(*注意1)。
          newProxyInstance()會返回一個實現了指定接口的代理對象,對該對象的所有方法調用都會轉發給InvocationHandler.invoke()方法。理解上述代碼需要對Java反射機制有一定了解。動態代理神奇的地方就是:

          代理對象是在程序運行時產生的,而不是編譯期;
          對代理對象的所有接口方法調用都會轉發到InvocationHandler.invoke()方法,在invoke()方法里我們可以加入任何邏輯,比如修改方法參數,加入日志功能、安全檢查功能等;之后我們通過某種方式執行真正的方法體,示例中通過反射調用了Hello對象的相應方法,還可以通過RPC調用遠程方法。
          注意1:對于從Object中繼承的方法,JDK Proxy會把hashCode()、equals()、toString()這三個非接口方法轉發給InvocationHandler,其余的Object方法則不會轉發。詳見JDK Proxy官方文檔。

          如果對JDK代理后的對象類型進行深挖,可以看到如下信息:

      # Hello代理對象的類型信息
      class=class jdkproxy.$Proxy0
      superClass=class java.lang.reflect.Proxy
      interfaces: 
      interface jdkproxy.Hello
      invocationHandler=jdkproxy.LogInvocationHandler@a09ee92

          代理對象的類型是jdkproxy.$Proxy0,這是個動態生成的類型,類名是形如$ProxyN的形式;父類是java.lang.reflect.Proxy,所有的JDK動態代理都會繼承這個類;同時實現了Hello接口,也就是我們接口列表中指定的那些接口。
          如果你還對jdkproxy.$Proxy0具體實現感興趣,它大致長這個樣子:

      // JDK代理類具體實現
      public final class $Proxy0 extends Proxy implements Hello
      {
        ...
        public $Proxy0(InvocationHandler invocationhandler)
        {
          super(invocationhandler);
        }
        ...
        @Override
        public final String sayHello(String str){
          ...
          return super.h.invoke(this, m3, new Object[] {str});// 將方法調用轉發給invocationhandler
          ...
        }
        ...
      }

          這些邏輯沒什么復雜之處,但是他們是在運行時動態產生的,無需我們手動編寫。更多詳情,可參考BrightLoong的Java靜態代理&動態代理筆記
          Java動態代理為我們提供了非常靈活的代理機制,但Java動態代理是基于接口的,如果對象沒有實現接口我們該如何代理呢?CGLIB登場。

          二、CGLIB動態代理
          CGLIB(Code Generation Library)是一個基于ASM的字節碼生成庫,它允許我們在運行時對字節碼進行修改和動態生成。CGLIB通過繼承方式實現代理。來看示例,假設我們有一個沒有實現任何接口的類HelloConcrete:

      public class HelloConcrete {
          public String sayHello(String str) {
              return "HelloConcrete: " + str;
          }
      }

          因為沒有實現接口該類無法使用JDK代理,通過CGLIB代理實現如下:
          首先實現一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法。
          然后在需要使用HelloConcrete的時候,通過CGLIB動態代理獲取代理對象。

      // CGLIB動態代理
      // 1. 首先實現一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法。
      class MyMethodInterceptor implements MethodInterceptor{
        ...
          @Override
          public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
              logger.info("You said: " + Arrays.toString(args));
              return proxy.invokeSuper(obj, args);
          }
      }
      // 2. 然后在需要使用HelloConcrete的時候,通過CGLIB動態代理獲取代理對象。
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(HelloConcrete.class);
      enhancer.setCallback(new MyMethodInterceptor());
       
      HelloConcrete hello = (HelloConcrete)enhancer.create();
      System.out.println(hello.sayHello("I love you!"));

          運行上述代碼輸出結果:

      日志信息: You said: [I love you!]
      HelloConcrete: I love you!

          上述代碼中,我們通過CGLIB的Enhancer來指定要代理的目標對象、實際處理代理邏輯的對象,終通過調用create()方法得到代理對象,對這個對象所有非final方法的調用都會轉發給MethodInterceptor.intercept()方法,在intercept()方法里我們可以加入任何邏輯,比如修改方法參數,加入日志功能、安全檢查功能等;通過調用MethodProxy.invokeSuper()方法,我們將調用轉發給原始對象,具體到本例,就是HelloConcrete的具體方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很類似,都是方法調用的中轉站。
          注意:對于從Object中繼承的方法,CGLIB代理也會進行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不會,因為它是final方法,CGLIB無法代理。如果對CGLIB代理之后的對象類型進行深挖,可以看到如下信息:

      # HelloConcrete代理對象的類型信息
      class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
      superClass=class lh.HelloConcrete
      interfaces: 
      interface net.sf.cglib.proxy.Factory
      invocationHandler=not java proxy class

          我們看到使用CGLIB代理之后的對象類型是cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52,這是CGLIB動態生成的類型;父類是HelloConcrete,印證了CGLIB是通過繼承實現代理;同時實現了net.sf.cglib.proxy.Factory接口,這個接口是CGLIB自己加入的,包含一些工具方法。
          注意,既然是繼承就不得不考慮final的問題。我們知道final類型不能有子類,所以CGLIB不能代理final類型,遇到這種情況會拋出類似如下異常:

      java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete

          同樣的,final方法是不能重載的,所以也不能通過CGLIB代理,遇到這種情況不會拋異常,而是會跳過final方法只代理其他方法。
          如果你還對代理類cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52具體實現感興趣,它大致長這個樣子:

      // CGLIB代理類具體實現
      public class HelloConcrete$$EnhancerByCGLIB$$e3734e52
        extends HelloConcrete
        implements Factory
      {
        ...
        private MethodInterceptor CGLIB$CALLBACK_0; // ~~
        ...
         
        public final String sayHello(String paramString)
        {
          ...
          MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
          if (tmp17_14 != null) {
            // 將請求轉發給MethodInterceptor.intercept()方法。
            return (String)tmp17_14.intercept(this, 
                    CGLIB$sayHello$0$Method, 
                    new Object[] { paramString }, 
                    CGLIB$sayHello$0$Proxy);
          }
          return super.sayHello(paramString);
        }
        ...
      }

          上述代碼我們看到,當調用代理對象的sayHello()方法時,首先會嘗試轉發給MethodInterceptor.intercept()方法,如果沒有MethodInterceptor就執行父類的sayHello()。這些邏輯沒什么復雜之處,但是他們是在運行時動態產生的,無需我們手動編寫。如何獲取CGLIB代理類字節碼可參考Access the generated byte[] array directly。
      想了解更多關于CGLIB的介紹可以來咨詢我們噢!

          三、結語
          本文介紹了Java兩種常見動態代理機制的用法和原理,JDK原生動態代理是Java原生支持的,不需要任何外部依賴,但是它只能基于接口進行代理;CGLIB通過繼承的方式進行代理,無論目標對象有沒有實現接口都可以代理,但是無法處理final的情況。
          動態代理是Spring AOP(Aspect Orient Programming, 面向切面編程)的實現方式,了解動態代理原理,對理解Spring AOP大有幫助。

      免費預約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 久久极品免费视频 | 最新国产精品久久精品 | 亚洲春色中文字幕我是洋洋 | 中国A级片在线观看 | 一本久久精品久久 | 精品免费人成视久久久 |