代理模式詳解:靜態代理、JDK動態代理與Cglib動態代理

列兵許三多發表於2021-03-09
  1. 代理模式簡介分類

    • 概念

      ​ 代理,是為了在不修改目標物件的基礎上,增強目標方法的業務邏輯。

      ​ 客戶類需要執行的是目標物件的目標方法,但是真正執行的是代理物件的代理方法,客戶類對目標物件的訪問是通過代理物件來實現的。當然,代理類與目標類需要實現同一個介面。

    • 舉例

      生活中遇到了官司,我們平常老百姓對法律的瞭解不全面,所以一般都會請律師處理。

      目標物件:法庭上我們一般稱為當事人即目標物件

      代理類:律師稱為代理律師即代理類

      共同介面:都為了一個共同的目標努力贏得官司即為共同介面

      目標方法:我們所做的提供證據各種努力成為目標方法

      代理方法:再此過程中我們可能做不全面,律師對證據材料等進行整理收集,結合法律法規進行辯證等等,此過程稱為代理方法

    • 代理分類

      代理模式一般分為靜態代理與動態代理,動態代理又分為JDK動態代理與CGLIB動態代理

  2. 靜態代理

    • 概念

      靜態代理是指,代理類在程式執行前就已經定義好,其與目標類等關係在程式執行前就已經確立。

      靜態代理類似於富翁於私人律師的代理關係,並不是在發生官司之後才去請律師,而是在此之前已經確立好的代理關係。

    • 實現與解析

      a、定義業務介面

      package com.rangers.proxy.staticProxy;
      
      /**
       * @Author Rangers
       * @Description
       * @Date 2021-03-09
       **/
      public interface IAccountService {
          // 轉賬業務介面
          void transfer();
      }
      

      b、定義目標類與目標方法

      package com.rangers.proxy.staticProxy;
      
      /**
       * @Author Rangers
       * @Description
       * @Date 2021-03-09
       **/
      public class AccountServiceImpl implements IAccountService {
          // 轉賬業務實現即目標方法
          @Override
          public void transfer() {
              System.out.println("進行轉賬操作");
          }
      }
      

      c、定義代理類AccountServiceImplProxy,實現IAccountService介面。在有參構造方法中傳入目標物件,將目標物件引入代理類,以便代理類呼叫目標方法,進行增強

      package com.rangers.proxy.staticProxy;
      
      /**
       * @Author Rangers
       * @Description
       * @Date 2021-03-09
       **/
      public class AccountServiceImplProxy implements IAccountService {
      
          // 宣告目標介面物件
          private IAccountService target;
      
          public AccountServiceImplProxy() {
          }
          // 業務介面物件作為構造器,用於接收目標物件
          public AccountServiceImplProxy(IAccountService target) {
              this.target = target;
          }
      
          @Override
          public void transfer() {
              // 此處對目標方法進行增強
              System.out.println("對轉賬人身份校驗。。");
              target.transfer();
              System.out.println("進行日誌記錄。。");
          }
      }
      

      d、編寫測試類TransferServiceTest

      package com.rangers.proxy.staticProxy;
      
      /**
       * @Author Rangers
       * @Description
       * @Date 2021-03-09
       **/
      public class TransferServiceTest {
          public static void main(String[] args) {
              // 建立目標物件
              IAccountService target = new AccountServiceImpl();
              // 建立代理物件,傳入目標物件進行初始化
              IAccountService proxy = new AccountServiceImplProxy(target);
              // 執行代理物件的方法
              proxy.transfer();
          }
      }
      
  3. 動態代理

    ​ 動態代理,程式在整個執行過程中根本就不存在目標類的代理類,目標物件的代理物件只是由代理工具在程式執行時由JVM根據反射等機制動態生成。代理物件與目標物件的代理關係在程式執行時才確立。

    ​ 動態代理類似於普通人在有官司之後,再聘請律師的,即代理關係是在官司發生後確立的。

    ​ 動態代理的實現方式有兩種:JDK的動態代理、CGLIB動態代理

    a、JDK動態代理

    • 概念

      ​ JDK動態代理是通過JDK提供的 java.lang.reflect.Proxy類實現動態大力,使用其靜態方法newProxyInstance(),對目標物件、業務介面及業務增強邏輯,自動生成一個動態代理物件。

      public static newProxyInstance(ClassLoader classLoader,Class<?> interfaces,InvocationHandler handler)
        classLoader:傳入目標類的類載入器,通過目標物件的反射獲取
        interfaces:目標物件實現的介面陣列,通過目標物件的反射獲取
      	handler:業務增強邏輯,需要具體實現
      

      ​ InvocationHandler是個介面,實現InvocationHandler介面的類用於增加目標類的業務邏輯。需要實現invoke()方法,具體的增強邏輯就是在此方法中進行實現,程式呼叫住業務邏輯時會自動呼叫invoke()方法

      public Object invoke(Object proxy,Method method,Object[] args)
        proxy:生成的代理物件
      	method:目標方法
      	args:目標方法的引數
      

      ​ Method類物件,invoke()方法進行執行目標物件的目標方法

      public Object invoke(Object obj,Object args)
          method.invoke(Object target,Object...args)執行目標方法
          target:目標物件
          args:目標方法的執行引數
      
    • 實現與解析

      1. 定義業務介面與實現類
      package com.rangers.proxy.jdkProxy;
      
      /**
       * @Author Rangers
       * @Description
       * @Date 2021-03-09
       **/
      public interface IAccountService {
          // 轉賬業務介面
          void transfer();
      }
      
      package com.rangers.proxy.jdkProxy;
      
      /**
       * @Author Rangers
       * @Description
       * @Date 2021-03-09
       **/
      public class AccountServiceImpl implements IAccountService {
          // 轉賬業務實現即目標方法
          @Override
          public void transfer() {
              System.out.println("進行轉賬操作");
          }
      }
      
      1. 定義JdkProxy類實現InvocationHandler介面,實現invoke()方法,並對業務邏輯進行增強
      package com.rangers.proxy.jdkProxy;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      
      /**
       * @Author Rangers
       * @Description
       * @Date 2021-03-09
       **/
      public class JdkProxy implements InvocationHandler {
      
          private Object target;
      
          public JdkProxy() {
          }
      
          public JdkProxy(Object target) {
              this.target = target;
          }
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              // 此處對目標方法進行增強
              System.out.println("對轉賬人身份校驗。。");
              Object result = method.invoke(target, args);
              System.out.println("進行日誌記錄。。");
              return result;
          }
      }
      
      1. 新建測試類JDKProxyTest
      package com.rangers.proxy.jdkProxy;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      
      /**
       * @Author Rangers
       * @Description
       * @Date 2021-03-09
       **/
      public class JDKProxyTest {
          public static void main(String[] args) {
              // 建立目標物件
              IAccountService target = new AccountServiceImpl();
              // 建立代理物件,傳入目標物件進行初始化
              IAccountService proxyService =
                      (IAccountService) Proxy.newProxyInstance(
                                                          target.getClass().getClassLoader(),
                                                          target.getClass().getInterfaces(),
                                                          new JdkProxy(target)
                                                      );
                      // 亦可使用匿名類進行實現
                      /*(IAccountService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                              target.getClass().getInterfaces(), new InvocationHandler() {
                                  @Override
                                  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                      // 此處對目標方法進行增強
                                      System.out.println("對轉賬人身份校驗。。");
                                      Object result = method.invoke(target, args);
                                      System.out.println("進行日誌記錄。。");
                                      return result;
                                  }
                              });*/
              // 此處執行的業務方法就是代理物件的增強過的邏輯
              proxyService.transfer();
          }
      }
      

    注:使用JDK動態代理時需要目標類目標方法必須在實現的介面中,否則不能使用此方式進行動態打擊。對於無介面的類需要實現動態代理,就要使用CGLIB方式來進行實現

    b、CGLIB動態代理

    • 概念

      ​ CGLIB是一個開源的第三方程式碼生成類庫,對於無介面的類,要為其建立動態代理,就要使用CGLIB進行實現。CGLIB代理的生成原理是生成目標類的子類,子類是增強過的,就是目標類的代理類。所以,使用CGLIB生成動態代理,要求目標類必須能夠被繼承,即不能是final修飾的類。

      ​ CGLIB包的底層是通過使用一個小兒快的位元組碼處理框架ASM(java位元組碼操控框架),來轉換位元組碼並生成新的類,通過對位元組碼進行增強來生成代理類。

      ​ 我們靜態代理理解為私人律師,JDK動態代理成為代理律師,CGLIB動態代理可以理解為老父親的兒子。老父親是被需要增強對目標類,兒子則是用於增強父親對代理類,事先不需要約定。父親需要兒子增強什麼,兒子就增強什麼,即他們之間的關係不要介面來進行約束。

    • 注意要點

      使用CGLIB動態代理時,生成代理類的類需要實現MethodInterceptor介面及intercept()方法

      public Object intercept(Object proxy,Method method,Objectp[] args,MethodProxy methodProxy)
        proxy:代理物件
        method:代理物件的方法,即增強後的方法
        args:方法引數
        methodProxy:代理方法的物件
      

      建立代理物件時使用Enhancer類

      // 建立增強器
      Enhancer enhancer = new Enhancer();
      // 初始化增強器:將目標類指定為父類
      enhancer.setSuperclass(target.class);
      // 初始化增強器:設定回撥至本類中的intercept()方法
      enhancer.setCallback(this);
      // 使用增強器建立代理物件進行返回
      enhancer.create();
      
    • 實現與解析

      1. 引入CGLIB依賴

            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib-full</artifactId>
                <version>2.0.2</version>
            </dependency>
        
      2. 建立目標類AccountService

        package com.rangers.proxy.cglibProxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class AccountService  {
        
            // 轉賬業務 即目標方法
            public void transfer() {
                System.out.println("進行轉賬操作");
            }
        
            // 查詢餘額 即目標方法
            public void getBalance() {
                System.out.println("查詢餘額操作");
            }
        }
        
      3. 建立代理類AccountServiceCglibProxy實現MethodInterceptor介面,完善intercept()方法進行增強,建立生成代理物件createProxy()方法

        package com.rangers.proxy.cglibProxy;
        
        import net.sf.cglib.proxy.Enhancer;
        import net.sf.cglib.proxy.MethodInterceptor;
        import net.sf.cglib.proxy.MethodProxy;
        
        import java.lang.reflect.Method;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class AccountServiceCglibProxy implements MethodInterceptor {
        
            // 宣告目標類的成員變數,並建立以目標類為引數的構造器,用於接收目標物件
            private AccountService target;
        
            public AccountServiceCglibProxy(AccountService accountService) {
                this.target = accountService;
            }
        
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                // 此處對目標方法進行增強
                Object result = new Object();
                if ("transfer".equals(method.getName())){
                    System.out.println("對轉賬人身份校驗。。");
                    result = method.invoke(target, args);
                    System.out.println("進行日誌記錄。。");
                }else{
                    // 直接執行目標物件的目標方法
                    result = methodProxy.invokeSuper(target,args);
                }
                return result;
            }
        
            // 建立代理物件
            public AccountService createProxy(){
                // 建立增強器
                Enhancer enhancer = new Enhancer();
                // 初始化增強器:將目標類指定為父類
                enhancer.setSuperclass(AccountService.class);
                // 初始化增強器:設定回撥至本類中的intercept()方法
                enhancer.setCallback(this);
                // 使用增強器建立代理物件
                return (AccountService) enhancer.create();
            }
        }
        
      4. 建立測試類CglibProxyTest

        package com.rangers.proxy.cglibProxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class CglibProxyTest {
            public static void main(String[] args) {
                // 目標物件
                AccountService target = new AccountService();
                // 建立代理物件,傳入目標物件進行初始化
                AccountService accountService = new AccountServiceCglibProxy(target).createProxy();
                accountService.transfer();
                accountService.getBalance();
            }
        }
        

相關文章