《跟二师兄学Nacos吧》EXT-04篇 Nacos竟然是这样使用代理模式的?

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 《跟二师兄学Nacos吧》EXT-04篇 Nacos竟然是这样使用代理模式的?

学习不用那么功利,二师兄带你从更高维度轻松阅读源码~


随着对Nacos源码的深入阅读,感觉越来越有意思了,大量的设计模式和基础知识点都在其中被运用。不论你是否阅读源码,都值得借鉴一下Nacos的运用案例。


今天这篇文章,给大家介绍一下Nacos Client中对代理模式的运用。阅读这篇文章,你可以不懂Nacos源码,但能够学到代理模式的运用;如果你准备阅读Nacos源码,不仅可以学到代理模式的案例,还可以更加深刻的感知到Nacos中的设计思想。


代理模式简介

通俗的来讲,代理模式就是让别人(代理)帮忙做你并不关心的事,作用就相当于日常生活中的中介。


比如,日常生活中,你想买辆车,你可以直接去自己挑选、质检等,但这个过程会耗费你大量的时间和精力。那么,此时你就可以找一个代理,来帮忙实现挑选、质检的事情。


对于软件设计来说,代理模式的定义为:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。


代理模式的结构

在不使用代理模式时,我们大概是这样使用一个接口:


image.png客户端在使用CarService接口时需要创建CarServiceImpl类的实例,然后进行业务逻辑处理。


但在某些场景下,一个客户类不想或者不能直接引用一个委托对象(CarServiceImpl),此时代理类对象可以在客户类和委托对象之间起到中介的作用,并提供相同的功能。


如果提供相同的功能,那么代理类和委托类就需要实现相同的接口。此时,上图就演变成了代理模式:


image.png在代理模式的图中,对比普通的直接使用,新增了代理类,并且代理类持有了委托类(真实对象)的引用。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务,所以要持有真实类的引用。


代理类可以在业务功能执行的前后加入一些公共的服务,比如负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。


代理模式中的角色:


抽象主题类(Subject):声明了目标对象和代理对象的共同接口,在任何可以使用目标对象的地方都可以使用代理对象。

具体主题类(RealSubject):也称为委托角色或者被代理角色。定义了代理对象所代表的目标对象。

代理类(Proxy):也叫委托类、代理类。代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。

代理模式实现

以上面的结构图为例,来看看代理模式的代码实现。


定义抽象主题类(CarService)、具体主题类(CarServiceImpl)、代理类(CarServiceProxy):


// 抽象主题类
public interface CarService {
    // 选车
    Car chooseCar();
    // 质量检查
    boolean qualityCheck();
}
// 具体主题类
public class CarServiceImpl implements CarService {
    @Override
    public Car chooseCar() {
        System.out.println("真实操作:选车");
        return new Car();
    }
    @Override
    public boolean qualityCheck() {
        System.out.println("真实操作:质量检测");
        return true;
    }
}
// 代理类
public class CarServiceProxy implements CarService {
    private CarServiceImpl real;
    public CarServiceProxy() {
        real = new CarServiceImpl();
    }
    @Override
    public Car chooseCar() {
        System.out.println("代理类CarServiceProxy选车:先添加一些日志");
        return real.chooseCar();
    }
    @Override
    public boolean qualityCheck() {
        System.out.println("代理类CarServiceProxy质量检测:先添加一些日志");
        return real.qualityCheck();
    }
}

对应的客户端测试类:

public class Client {
    public static void main(String[] args) {
        CarService carService = new CarServiceProxy();
        carService.chooseCar();
        carService.qualityCheck();
    }
}

直接使用代理类,就可以完成预期的工作。


执行程序,打印日志如下:


代理类CarServiceProxy选车:先添加一些日志

真实操作:选车

代理类CarServiceProxy质量检测:先添加一些日志

真实操作:质量检测

1

2

3

4

可以看出,在真实的操作之前,可以通过代理类添加一些其他的操作。


Nacos的代理模式实践

上面了解了代理模式的基本知识以及实例,下面就来看看Nacos中是如何实现代理模式的。


Nacos Client与注册中心进行通信采用了两种通信协议:HTTP协议和gRPC协议。这两个协议实现了共同的抽象主题类NamingClientProxy,具体主题类有NamingHttpClientProxy和NamingGrpcClientProxy,分别对应Http协议和gRPC协议实现。


此时,Nacos考虑到要支持通过配置来灵活选择具体的通信协议,而这个功能呢又没办法让这两个具体的主题类来实现,因此就产生了一个代理类NamingClientProxyDelegate来完成一些预先的处理和判断。


整个代理模式的使用类图如下:


image.png通过上图可以发现,Nacos的代理模式使用与标准的代理模式还有一些区别。


首先,NamingClientProxyDelegate同时代理了具体主题类,这可能考虑的是方便通信协议的配置切换。同时,在代理类中还处理了一些事件监听等额外功能。


其次,说话Nacos这块的命名并不友好,比如抽象主题直接以Proxy为后缀,容易让人混淆。这就导致与代理模式中的代理类命名冲突,于是将代理类的后缀替换为了Delegate。


上图中的客户类便是NacosNamingService,在其中实现了代理类的初始化操作,具体代码实现如下:


public class NacosNamingService implements NamingService {
    // ...
    private NamingClientProxy clientProxy;
    private void init(Properties properties) throws NacosException {
        // ...
        this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
    }
    @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        NamingUtils.checkInstanceIsLegal(instance);
        clientProxy.registerService(serviceName, groupName, instance);
    }
    // ...
}  

抽象主题类NamingClientProxy为接口,部分代码如下:

public interface NamingClientProxy extends Closeable {
    void registerService(String serviceName, String groupName, Instance instance) throws NacosException;
    void deregisterService(String serviceName, String groupName, Instance instance) throws NacosException;
   // ...
}

代理类NamingClientProxyDelegate部分实现如下:

public class NamingClientProxyDelegate implements NamingClientProxy {
    // ...
    private final NamingHttpClientProxy httpClientProxy;
    private final NamingGrpcClientProxy grpcClientProxy;
    public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfoHolder, Properties properties,
            InstancesChangeNotifier changeNotifier) throws NacosException {
         // ...       
        this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties,
                serviceInfoHolder);
        this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties,
                serviceInfoHolder);
    }
  // ...
}

可以看出,代理类实现了NamingClientProxy接口,同时持有了NamingHttpClientProxy和NamingGrpcClientProxy的对象引用,并且对它们进行了初始化操作。


关于NamingHttpClientProxy和NamingGrpcClientProxy的代码我们就不再展示,它们首先继承了AbstractNamingClientProxy抽象类,该抽象类实现NamingClientProxy接口。


从整体上来说,Nacos中对代理模式的运用还是比较灵的,结合场景一个代理类代理了两个具体实现类,但同时在命名方面的问题,还有待商榷。


代理模式和装饰器模式的区别

在学习使用代理模式时,经常会有朋友与装饰器模式相混淆。这里就简单聊一下它们直接的区别。


装饰器模式中,装饰者(decorator)和被装饰者(decoratee)都实现同一个接口。代理模式中,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。而且两者都对类的方法进行扩展,看起来边界的确比较模糊。


但还是有一些区别点的:


装饰器模式强调的是增强自身,比如增加之后可提供更多的属性和方法;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;

装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;

小结

代理模式在日常业务代码中还是比较少见的,本文我们重点介绍了静态代理模式及在Nacos中的运用。关于动态代理,在Spring的框架中可以看到很多实例,有机会我们再进行讲解。而Nacos中对代理模式的运用算是比较灵活,同时也并不是那么完美。这或许也提供了我们对代理模式认知的另外一个视角。


如果文章内容有问题或想技术讨论请联系我(微信:zhuan2quan,备注Nacos),如果觉得写的还不错,值得一起学习,那就关注一下吧。


Nacos系列文章

01 《《跟二师兄学Nacos吧》第1篇 Nacos客户端服务注册源码分析》


EXT-01《《跟二师兄学Nacos吧》EXT-01篇 看看Nacos是怎么活学活用简单工厂模式的!》


EXT-02《《跟二师兄学Nacos吧》EXT-02篇 面试官问工厂模式,你理解的对吗?》


EXT-03《《跟二师兄学Nacos吧》EXT-03篇 Nacos中此处为什么采用反射机制?》


EXT-04《《跟二师兄学Nacos吧》EXT-04篇 Nacos竟然是这样使用代理模式的?》



相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
存储 Kubernetes 关系型数据库
在Kubernetes中,helm是什么?如何使用?
【4月更文挑战第9天】在Kubernetes中,helm是什么?如何使用?
1140 5
|
安全 Java Spring
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
187 0
|
3月前
|
存储 NoSQL Dubbo
Java主流分布式解决方案多场景设计与实战
本文介绍了Java领域的主流分布式技术,涵盖分布式服务框架(如Dubbo、Spring Cloud)、分布式数据存储(如Redis、MongoDB)、分布式锁(如ZooKeeper、Redisson)及分布式事务(如Seata、Hmily),并通过电商项目案例分析了这些技术在实际开发中的应用,帮助开发者应对高并发与大数据挑战。
122 0
|
3月前
|
人工智能 安全 Java
Spring Boot 中使用 Function 和异步线程池处理列表拆分任务并汇总结果
在Java开发中,处理大规模数据时常常需要将列表拆分为多个子列表进行异步处理并汇总结果。本文介绍如何在Spring Boot中使用Function和异步线程池实现高效且可维护的代码,涵盖结果封装、线程池配置、列表拆分处理及结果汇总等关键步骤。
136 0
|
5月前
|
缓存 Java Nacos
如何优雅上线、下线?原来 大厂应用 是这样 优雅发布的!
如何优雅上线、下线?原来 大厂应用 是这样 优雅发布的!
如何优雅上线、下线?原来 大厂应用 是这样 优雅发布的!
|
5月前
|
SQL 关系型数据库 MySQL
凌晨2点报警群炸了:一条sql 执行200秒!搞定之后,我总结了一个慢SQL查询、定位分析解决的完整套路
凌晨2点报警群炸了:一条sql 执行200秒!搞定之后,我总结了一个慢SQL查询、定位分析解决的完整套路
凌晨2点报警群炸了:一条sql 执行200秒!搞定之后,我总结了一个慢SQL查询、定位分析解决的完整套路
|
5月前
|
Arthas 监控 Java
Arthas dashboard(当前系统的实时数据面板)
Arthas dashboard(当前系统的实时数据面板)
259 12
|
9月前
|
数据采集 人工智能 监控
《数据质量:人工智能模型的成败关键》
在人工智能快速发展的时代,数据质量对模型的性能、准确性和可靠性至关重要。准确、完整、多样且具代表性的数据能提升模型泛化能力;一致、及时的数据有助于提高训练效率;避免偏差和噪声可防止模型产生不公平结果或错误学习。因此,确保数据质量是构建高效、可靠AI模型的关键。
745 12
|
11月前
|
存储 缓存 NoSQL
京东面试:亿级黑名单 如何设计?亿级查重 呢?(答案含:布隆过滤器、布谷鸟过滤器)
尼恩,40岁的老架构师,近期在读者交流群中分享了几个大厂面试题及其解决方案。这些问题包括亿级数据查重、黑名单存储、电话号码判断、安全网址判断等。尼恩给出了三种解决方案:使用BitMap位图、BloomFilter布隆过滤器和CuckooFilter布谷鸟过滤器。这些方法不仅高效,还能显著提升面试表现。尼恩还建议大家系统化学习,刷题《尼恩Java面试宝典PDF》,并提供简历修改和面试辅导,帮助大家实现“offer自由”。更多技术资料和PDF可在公众号【技术自由圈】获取。
|
11月前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
178 3