JAVA代理模式的理解和應用

代理模式:

代理模式通俗一點的解釋就是在操做一個對象和對象中的方法時,不是直接操做這個對象,仍是經過一個代理對象來操做這個實際的目標對象。應用場景通常是須要在執行某個已經寫好的方法先後再添加一段邏輯,好比執行方法前打印日誌,或者在執行方法以前和以後打時間戳來計算方法的執行時間,諸如此類的。固然這些操做能夠在寫方法的時候就去寫好,可是這樣的話效率很是低,重複代碼複製了一遍又一遍,若是要統一改點什麼數量多起來的話基本上是個不可能完成的任務,而代理模式就是專門解決這種問題的。java


靜態代理:

靜態代理其實代理類Proxy中定義了一個方法,這個方法來調用被代理類Target中的方法,這樣咱們就能夠在執行這個方法的先後增長邏輯了,代理類和被代理類是組合關係。這裏實現一個接口是爲了有更好的擴展性,代理類Proxy中聲明接受這個接口類型,那麼被代理類只要實現了這個接口就可使用代理類Proxy進行代理操做了,這裏是JAVA的多態特性。spring

  • 被代理的目標的實現接口編程

    public interface TargetImpl {
    
      void doSomething();
    
    }
  • 被代理的目標類maven

    public class Target implements TargetImpl {
    
      public void doSomething(){
          System.out.println("target do something!!!");
      }
    }
  • 代理類ide

    public class Proxy implements TargetImpl {
    
      private TargetImpl baseObject;
    
      public Proxy(TargetImpl baseObject) {
          this.baseObject = baseObject;
      }
    
      public void doSomething(){
          System.out.println("before method");
          baseObject.doSomething();
          System.out.println("after method");
      }
    }
  • 測試類:測試

    public class TestMain {
    
      public static void main(String[] args){
          staticProxy();
      }
    
      public static void staticProxy(){
          Target target = new Target();
          Proxy proxy = new Proxy(target);
          proxy.doSomething();
      }
    }

動態代理:

上面靜態代理類已經幫咱們解決了不少冗餘代碼,可是存在的問題仍是不少,好比一個代理類只能對一種類型目標類有效,換一種類型要新增一個代理類,並且若是有不少地方使用目標類就得在每一個地方調用代理類,很麻煩,而動態代理則能夠解決這種問題。this

  • 代理類,須要實現InvocationHandler接口,這個接口是JAVA自帶的,實現invoke()方法,被代理的目標類會在invoke()方法中被調用,只須要在這個方法中添加邏輯便可。而proxy()方法則是調用了Proxy.newProxyInstance()方法,這個是JAVA原生類Proxy中的方法,接收目標類的類型參數和目標類的對象參數。代理

    // 這個接口是JDK自帶的,全部的代理類都要實現這個接口
    // 這樣才能調用Proxy.newProxyInstance()這個生成代理類的靜態方法
    public class MyProxy implements InvocationHandler {
      private Object proxy;
    
      public MyProxy(Object proxy) {
          this.proxy = proxy;
      }
    
      // 代理類實現接口中的一個方法,接收參數分別是被代理的類,要執行的方法,執行方法的參數,返回則是執行方法返回的參數
      // 代理對象的全部方法調用都會轉到這個方法中
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          System.out.println("before invoke");
          Object rTarget = method.invoke(this.proxy, args);
          System.out.println("after invoke");
          return rTarget;
      }
    
      // JDK自帶的生成代理類的靜態方法,第一個參數是類加載器 第二個參數是被代理類的接口 第三個參數是被代理的對象
      // 這個方法內部的大體原理就是動態的加載這個類,而後放到內存中,因此不是編譯時期生成的,是運行的時候生成的
      public static Object proxy(Class interfaceClazz, Object proxy) {
          return Proxy.newProxyInstance(interfaceClazz.getClassLoader(), new Class[]{interfaceClazz},
                  new MyProxy(proxy));
      }
    }
  • 被代理的目標類的實現接口日誌

    public interface TargetImpl {
    
      void doSomething1();
    
      void doSomething2();
    
      String doSomething3();
    }
  • 被代理的目標類code

    public class Target implements TargetImpl {
    
      private String text;
    
      public Target(String text) {
          this.text = text;
      }
    
      public void doSomething1(){
          System.out.println("doSomething1-" + text);
      }
    
      public void doSomething2(){
          System.out.println("doSomething2-" + text);
      }
    
      public String doSomething3(){
          System.out.println("doSomething3-" + text);
          String result = "doSomething3-" + text;
         return result;
      }
    }
  • 測試類,調用proxy()方法,把目標類實現接口的字節碼和目標類的對象傳入,得到返回的一個代理類對象,而後就能夠調用對應的方法,這個時候會發現方法執行前會執行前面在invoke()方法中添加的邏輯。

    public class TestMain {
    
      public static void main(String[] args){
          jdkProxy();
      }
    
      public static void jdkProxy(){
          TargetImpl target = (TargetImpl) MyProxy.proxy(TargetImpl.class, new Target("target"));
          target.doSomething1();
          target.doSomething2();
          System.out.println(target.doSomething3());
      }
    
    }
  • 大體原理

    • 動態代理之因此叫動態代理就是由於代理類不是在編譯時生成的,而是代碼運行後動態生成的。
    • 在調用了Proxy.newProxyInstance()方法以後,由於把目標類實現接口的字節碼和目標類的對象出入進行了,因此這個方法的源碼作的大體操做就是根據這個字節碼和對象來獲取目標類中的方法等各類信息而後動態的生成一個代理類,而代理類中全部的方法調用又會中轉到invoke()方法中,invoke()方法又再去調用目標類中的方法,因此只須要在invoke()方法中添加須要添加的邏輯便可。
    • 注意,若是使用JAVA自帶的動態代理,目標類是必定要實現一個接口才能夠的。

cglib代理:

cglib是一個開源的庫,能夠在運行時動態的修改和生成字節碼,原理其實和JAVA原生的動態代理差很少,可是不一樣的地方是它是基於被代理的目標類生成一個子類,而後在在子類中重載父類的方法,因此它能夠代理沒有接口實現的目標類,這點是和JAVA原生的動態代理最大的不一樣之處。

  • 引入maven

    <dependency>
       <groupId>cglib</groupId>
       <artifactId>cglib</artifactId>
       <version>3.2.12</version>
    </dependency>
  • 代理類,實現MethodInterceptor接口,在intercept方法中添加額外的邏輯並調用methodProxy.invokeSuper()方法來執行目標類中的方法得到代理類的對象。

    public class Proxy implements MethodInterceptor {
    
      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
          System.out.println("before invoke");
          Object object = methodProxy.invokeSuper(o,objects);
          System.out.println("after invoke");
          return object;
      }
    }
  • 被代理的目標類的實現接口

    public interface TargetImpl {
    
      void doSomething1();
    
      void doSomething2();
    
      String doSomething3();
    }
  • 被代理的目標類

    public class Target implements TargetImpl {
    
      private String text;
    
      public Target(String text) {
          this.text = text;
      }
    
      public void doSomething1(){
          System.out.println("doSomething1-" + text);
      }
    
      public void doSomething2(){
          System.out.println("doSomething2-" + text);
      }
    
      public String doSomething3(){
          System.out.println("doSomething3-" + text);
          String result = "doSomething3-" + text;
         return result;
      }
  • 測試類

    public class TestMain {
    
      public static void main(String[] args){
          jdkProxy();
      }
    
      public static void jdkProxy(){
          TargetImpl target = (TargetImpl) MyProxy.proxy(TargetImpl.class, new Target("target"));
          target.doSomething1();
          target.doSomething2();
          System.out.println(target.doSomething3());
      }
    }
  • 大體原理

    • 能夠看到除了代理類和JAVA原生的動態代理略有不一樣其餘的地方基本是相同的,也是在運行時動態的生成代理類。
    • 注意,由於前面說了cglib生成的代理類實際上是目標類的一個子類,因此被final聲明的類是沒辦法使用cglib的,會拋出java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcret異常,而被final聲明的方法也是沒辦法被重載的,因此會被忽略。

總結:

  • 能夠看到在這三種代理方式中都有使用到JAVA中多態的特性。
  • 靜態代理就是單純簡單的使用了多態和組合的特性。
  • JAVA動態代理和cglib則是再這個基礎上使用了動態編譯的方式使得擴展性更強,只不過二者的動態生成的方式不一樣,因此注意事項也有所不一樣。

擴展:

  • spring中就大量使用了兩種動態代理,AOP切面就是使用了動態代理,之因此叫切面就是好比說原來A方法、B方法是依次調用,而如今配置Spring AOP就能夠動態的在A、B方法的先後添加邏輯,這樣就能夠在原本依次調用的A、B方法之間插入新的邏輯,因此叫面向切面編程。
相關文章
相關標籤/搜索