• 你有被代理过吗?讲讲开源框架都在用的代理模式


    大家好,我是老三。

    这节我们来看一个非常重要的设计模式——代理模式,尽管我们工作中可能很少用到,但它是很多框架重要功能的基石,肘,我们开始吧。

    引言

    节假日的地铁站,你是否见过有人掏出电脑,原地输出,原来是群里被疯狂@……

    告诉自己不准哭

    用户提问题,客服@我们解决,可能很多开发同学都经历过这样的场景。

    用户找到客服,提出问题,客服又找到开发同学,让开发同学去解决问题,开发同学解决完,最后反馈给客服,客服再反馈到用户。

    站在用户的视角,感觉就是客服解决了这个问题,这其实就是一种代理。

    用户向客服提问题

    我们以这个例子,来看看Java怎么实现代理模式的吧。

    Java的三种代理模式实现

    代理模式的定义:

    Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)

    简单说,就是设置一个中间代理来控制访问原目标对象,达到增强原对象的功能和简化访问方式的目的。

    代理模式通用类图

    Java实现代理模式分为两类三种,两类是静态代理动态代理,动态代理又可以分为JDK动态代理CGLIB动态代理

    Java实现代理模式

    静态代理

    静态代理比较简单,代理类需要实现和目标接口类一样的接口。

    Solver静态代理类图

    • 接口类:ISolver

      复制代码
      public interface ISolver {
          void solve();
      }
      
    • 目标类:Solver

      复制代码
      public class Solver implements ISolver {
          @Override
          public void solve() {
              System.out.println("疯狂掉头发解决问题……");
          }
      }
      
    • 代理类:SolverProxy,代理类也要实现接口,并且还要维护一个目标对象。

      复制代码
      public class SolverProxy implements ISolver {
          //目标对象
          private ISolver target;
      
          public SolverProxy(ISolver target) {
              this.target = target;
          }
      
          @Override
          public void solve() {
              System.out.println("请问有什么能帮到您?");
              target.solve();
              System.out.println("问题已经解决啦!");
          }
      }
      
    • 客户端;Client

      复制代码
      public class Client {
          public static void main(String[] args) {
              //目标对象:程序员
              ISolver developer = new Solver();
              //代理:客服小姐姐
              SolverProxy csProxy = new SolverProxy(developer);
              //目标方法:解决问题
              csProxy.solve();
          }
      }
      
    • 运行结果

      复制代码
      请问有什么能帮到您?
      疯狂掉头发解决问题……
      问题已经解决啦!
      

    我们看到,通过静态代理,可以在不修改目标对象的前提下扩展目标对象的功能。

    但是,它也有一些问题:

    • 冗余:由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
    • 维护性不佳:一旦接口增加方法,目标对象与代理对象都要进行修改。

    JDK动态代理

    JDK动态代理利用了JDK反射机制,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。

    它主要用到了两个反射类的API:

    • java.lang.reflect Proxy| static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h):返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
    • java.lang.reflect InvocationHandler|Object invoke(Object proxy, Method method, Object[] args) :在代理实例上调用目标方法,并返回结果。

    我们来看看使用JDK动态代理之后的客服代理场景。

    JDK动态代理类图

    • 接口类:ISolver

      复制代码
      public interface ISolver {
          void solve();
      }
      
    • 目标类:Solver,目标类需要实现接口类。

      复制代码
      public class Solver implements ISolver {
          @Override
          public void solve() {
              System.out.println("疯狂掉头发解决问题……");
          }
      }
      
    • 动态代理工厂:ProxyFactory,这里的动态代理工厂,不需要实现接口,直接采用反射的方式生成一个目标对象的代理对象实例。

      ps:这里用了一个匿名内部类的方法,还有一种方法,动态代理类实现InvocationHandler接口,大体上类似,就不再给出例子了。

      复制代码
      public class ProxyFactory {
      
          // 维护一个目标对象
          private Object target;
      
          public ProxyFactory(Object target) {
              this.target = target;
          }
      
          // 为目标对象生成代理对象
          public Object getProxyInstance() {
              return 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 returnValue = method.invoke(target, args);
      
                              System.out.println("问题已经解决啦!");
                              return null;
                          }
                      });
          }
      }
      
      
    • 客户端:Client

      客户端生成一个代理对象实例,通过代理对象调用目标对象方法的时候,就会进入invoke()方法,最后是通过反射的方式调用目标对象的方法。

      复制代码
      public class Client {
          public static void main(String[] args) {
              //目标对象:程序员
              ISolver developer = new Solver();
              //代理:客服小姐姐
              ISolver csProxy = (ISolver) new ProxyFactory(developer).getProxyInstance();
              //目标方法:解决问题
              csProxy.solve();
          }
      }
      
    • 运行结果:

      复制代码
      请问有什么可以帮到您?
      疯狂掉头发解决问题……
      问题已经解决啦!
      

    我们简单总结一下静态代理和动态代理的主要区别:

    静态代理动态代理最主要区别

    • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
    • 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中

    我们也观察到,JDK动态代理,目标对象必须得实现接口,也就是说它是面向接口的,假如我们不想要接口怎么办呢?

    Cglib动态代理

    CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,它是通过继承来实现的。

    我们来看看使用Cglib之后,我们的客服代理是什么样的:

    Cglib动态代理类图

    • 引入依赖:Cglib是第三方类库,需要引入依赖

      复制代码
              <dependency>
                  <groupId>cglib</groupId>
                  <artifactId>cglib</artifactId>
                  <version>3.2.5</version>
              </dependency>
      
    • 目标类:Solver,这里目标类不用再实现接口。

      复制代码
      public class Solver {
      
          public void solve() {
              System.out.println("疯狂掉头发解决问题……");
          }
      }
      
    • 动态代理工厂:

      复制代码
      public class ProxyFactory implements MethodInterceptor {
      
         //维护一个目标对象
          private Object target;
      
          public ProxyFactory(Object target) {
              this.target = target;
          }
      
          //为目标对象生成代理对象
          public Object getProxyInstance() {
              //工具类
              Enhancer en = new Enhancer();
              //设置父类
              en.setSuperclass(target.getClass());
              //设置回调函数
              en.setCallback(this);
              //创建子类对象代理
              return en.create();
          }
      
          @Override
          public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
              System.out.println("请问有什么可以帮到您?");
              // 执行目标对象的方法
              Object returnValue = method.invoke(target, args);
              System.out.println("问题已经解决啦!");
              return null;
          }
      
      }
      
    • 客户端:Client

      复制代码
      public class Client {
          public static void main(String[] args) {
              //目标对象:程序员
              Solver developer = new Solver();
              //代理:客服小姐姐
              Solver csProxy = (Solver) new ProxyFactory(developer).getProxyInstance();
              //目标方法:解决问题
              csProxy.solve();
          }
      }
      
    • 运行结果

      复制代码
      请问有什么可以帮到您?
      疯狂掉头发解决问题……
      问题已经解决啦!
      

    我们可以看到Cglib动态代理和JDK动态代理最大的区别就是:

    • 使用JDK动态代理的对象必须实现一个或多个接口
    • 使用Cglib动态代理的对象则无需实现接口,达到代理类无侵入。

    我们还需要注意:

    • CGLib不能对声明为final的方法进行代理,因为是通过继承父类的方式实现,如果父类是final的,那么就无法继承父类。

    扩展:动态代理的应用

    标题里说了,开源框架都在用的代理模式,那么主流的开源框架哪些地方用到了代理模式呢?——确切说是动态代理呢?

    比如:

    • Spring AOP可以将一些非核心业务流程抽象成切面,帮助我们处理非主线的流程,它是怎么实现的呢?
    • 你平时用惯了的MyBatis,写了那么多Mapper接口,为什么它不用实现类就能执行SQL呢?

    后面两期是大家期待的面渣逆袭系列,将会揭晓这两个问题的答案。


    简单的事情重复做,重复的事情认真做,认真的事情有创造性地做!

    我是三分恶,一个能文能武的普通开发。点赞关注不迷路,咱们下期见!



    参考:

    [1].《设计模式之禅》

    [2].Java三种代理模式:静态代理、动态代理和cglib代理

  • 相关阅读:
    【Java】Java 并发常考题
    八股文死记硬背打脸记
    GPU架构与计算入门指南
    小米商城侧导航栏的实现
    MFC关闭预编译头文件后错误解决
    Springboot 搭建 WebSocket
    基于BP神经网络算法的性别识别
    【java学习】Annotation-Driven(注解驱动编程)-spring常用注解
    Linux C文件操作
    【好书推荐】C语言程序设计:现代方法(第二版)
  • 原文地址:https://www.cnblogs.com/three-fighter/p/15911261.html