美文网首页
代理模式 - 结构型

代理模式 - 结构型

作者: 熊本极客 | 来源:发表于2020-05-01 12:00 被阅读0次

    一、代理模式的定义
    在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
    注意:附件的功能主要是目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

    二、代理模式的实现
    在原业务系统中,新增统计访问时间、处理时长等功能,即可以开发一个MetricsCollector类,用于统计接口的请求。

    public class UserController {
      //...省略其他属性和方法...
      private MetricsCollector metricsCollector; // 依赖注入
    
      public UserVo login(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
    
        // ... 省略login逻辑...
    
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
        metricsCollector.recordRequest(requestInfo);
    
        //...返回UserVo数据...
      }
    
      public UserVo register(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
    
        // ... 省略register逻辑...
    
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
        metricsCollector.recordRequest(requestInfo);
    
        //...返回UserVo数据...
      }
    }
    

    上面的代码有两个问题,第一,性能计数器框架代码侵入到业务代码中,跟业务代码高度耦合。如果未来需要替换这个框架,那替换的成本会比较大。第二,收集接口请求的代码跟业务代码无关,本就不应该放到一个类中。业务类最好职责更加单一,只聚焦业务处理。

    为了统计功能与业务代码解耦,可以采用代理模式。UserControllerProxy和原始类UserController实现相同的接口IUserController。UserController类只负责业务功能。

    public interface IUserController {
      UserVo login(String telephone, String password);
      UserVo register(String telephone, String password);
    }
    
    public class UserController implements IUserController {
      //...省略其他属性和方法...
    
      @Override
      public UserVo login(String telephone, String password) {
        //...省略login逻辑...
        //...返回UserVo数据...
      }
    
      @Override
      public UserVo register(String telephone, String password) {
        //...省略register逻辑...
        //...返回UserVo数据...
      }
    }
    
    public class UserControllerProxy implements IUserController {
      private MetricsCollector metricsCollector;
      private UserController userController;
    
      public UserControllerProxy(UserController userController) {
        this.userController = userController;
        this.metricsCollector = new MetricsCollector();
      }
    
      @Override
      public UserVo login(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
    
        // 委托
        UserVo userVo = userController.login(telephone, password);
    
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
        metricsCollector.recordRequest(requestInfo);
    
        return userVo;
      }
    
      @Override
      public UserVo register(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
    
        UserVo userVo = userController.register(telephone, password);
    
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
        metricsCollector.recordRequest(requestInfo);
    
        return userVo;
      }
    }
    
    //UserControllerProxy使用举例
    //因为原始类和代理类实现相同的接口,是基于接口而非实现编程
    //将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码
    IUserController userController = new UserControllerProxy(new UserController());
    

    问题:原始UserController不是自研的,而是第三方类库,没法替它设计一个接口。如何实现代理模式?
    解答:可以采用继承的方式。

    public class UserControllerProxy extends UserController {
      private MetricsCollector metricsCollector;
    
      public UserControllerProxy() {
        this.metricsCollector = new MetricsCollector();
      }
    
      @Override
      public UserVo login(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
    
        UserVo userVo = super.login(telephone, password);
    
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
        metricsCollector.recordRequest(requestInfo);
    
        return userVo;
      }
    
      @Override
      public UserVo register(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
    
        UserVo userVo = super.register(telephone, password);
    
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
        metricsCollector.recordRequest(requestInfo);
    
        return userVo;
      }
    }
    //UserControllerProxy使用举例
    UserController userController = new UserControllerProxy();
    

    三、动态代理
    问题:上述代理模式的实现还有问题,一方面,需要在代理类中,将原始类中的所有的方法,都重复实现一遍;另一方面,如果要添加的附加功能的类有不止一个,需要针对每个类都创建一个代理类。
    解答:动态代理,不需要事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。可以采用Java语言的动态代理(基于Java反射机制)。

    public class MetricsCollectorProxy {
      private MetricsCollector metricsCollector;
    
      public MetricsCollectorProxy() {
        this.metricsCollector = new MetricsCollector();
      }
    
      public Object createProxy(Object proxiedObject) {
        Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
        DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
        return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
      }
    
      private class DynamicProxyHandler implements InvocationHandler {
        private Object proxiedObject;
    
        public DynamicProxyHandler(Object proxiedObject) {
          this.proxiedObject = proxiedObject;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          long startTimestamp = System.currentTimeMillis();
          Object result = method.invoke(proxiedObject, args);
          long endTimeStamp = System.currentTimeMillis();
          long responseTime = endTimeStamp - startTimestamp;
          String apiName = proxiedObject.getClass().getName() + ":" + method.getName();
          RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp);
          metricsCollector.recordRequest(requestInfo);
          return result;
        }
      }
    }
    
    //MetricsCollectorProxy使用举例
    MetricsCollectorProxy proxy = new MetricsCollectorProxy();
    IUserController userController = (IUserController) proxy.createProxy(new UserController());
    

    四、代理模式的应用场景

    1. 业务系统的非功能性需求开发代理模式最常用的一个应用场景就 是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。将这些附加功能与业务功能解耦,放到代理类中统一处理,因此我们只需要关注业务方面的开发。Spring AOP底层的实现原理就是基于动态代理。

    2.RPC框架也可以看作一种代理模式,通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。

    3.代理模式在缓存中的应用。
    问题:假设我们要开发一个接口请求的缓存功能,对于某些接口请求,如果入参相同,在设定的过期时间内,直接返回缓存结果,而不用重新进行逻辑处理。
    解决:可以开发两个接口,一个支持缓存,一个支持实时查询。对于需要实时数据的需求,我们让其调用实时查询接口,对于不需要实时数据的需求,我们让其调用支持缓存的接口。那如何来实现接口请求的缓存功能呢?可以采用Spring AOP的动态代理。

    相关文章

      网友评论

          本文标题:代理模式 - 结构型

          本文链接:https://www.haomeiwen.com/subject/nlwfghtx.html