JAVA代理模式的理解和应用

42次阅读

共计 4975 个字符,预计需要花费 13 分钟才能阅读完成。

代理模式:

代理模式通俗一点的解释就是在操作一个对象和对象中的方法时,不是直接操作这个对象,还是通过一个代理对象来操作这个实际的目标对象。应用场景一般是需要在执行某个已经写好的方法前后再添加一段逻辑,比如执行方法前打印日志,或者在执行方法之前和之后打时间戳来计算方法的执行时间,诸如此类的。当然这些操作可以在写方法的时候就去写好,但是这样的话效率非常低,重复代码复制了一遍又一遍,如果要统一改点什么数量多起来的话基本上是个不可能完成的任务,而代理模式就是专门解决这种问题的。


静态代理:

静态代理其实代理类 Proxy 中定义了一个方法,这个方法来调用被代理类 Target 中的方法,这样我们就可以在执行这个方法的前后增加逻辑了,代理类和被代理类是组合关系。这里实现一个接口是为了有更好的扩展性,代理类 Proxy 中声明接受这个接口类型,那么被代理类只要实现了这个接口就可以使用代理类 Proxy 进行代理操作了,这里是 JAVA 的多态特性。

  • 被代理的目标的实现接口

    public interface TargetImpl {void doSomething();
    
    }
  • 被代理的目标类

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

    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();}
    }

动态代理:

上面静态代理类已经帮我们解决了很多冗余代码,但是存在的问题还是很多,比如一个代理类只能对一种类型目标类有效,换一种类型要新增一个代理类,而且如果有很多地方使用目标类就得在每个地方调用代理类,很麻烦,而动态代理则可以解决这种问题。

  • 代理类,需要实现 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();}
  • 被代理的目标类

    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 方法之间插入新的逻辑,所以叫面向切面编程。

正文完
 0