Dubbo中都用到哪些设计模式:
- 责任链模式:
责任链模式为请求创建了一个接收者对象的链
。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)处理请求事务【责任链模式实际上是一种处理请求的模式,它让多个处理器(对象节点)都有机会处理该请求,直到其中某个处理成功为止。】
,如果某个对象节点处理完了,就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕
。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
- 责任链模式在Dubbo中发挥的作用举足轻重,就像是Dubbo框架的骨架。Dubbo的调用链组织是用责任链模式串连起来的。责任链中的每个节点实现Filter接口,然后由ProtocolFilterWrapper,将所有Filter串连起来。Dubbo的许多功能都是通过Filter扩展实现的,比如监控、日志、缓存、安全、telnet以及RPC本身都是。
- 常见的责任链模式的业务场景就是下订单,
下订单接口基本的逻辑,一般有参数非空校验、安全校验、黑名单校验、规则拦截等等,而咱们实现这些通常通过使用异常来实现
~阿里开发手册规定:禁止用异常做逻辑判断【异常不要用来做流程控制,条件控制。说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。】
public class Order { //使用了异常来做逻辑条件判断 public void checkNullParam(Object param){ //参数非空校验 throw new RuntimeException(); } public void checkSecurity(){ //安全校验 throw new RuntimeException(); } public void checkBackList(){ //黑名单校验 throw new RuntimeException(); } public void checkRule(){ //规则拦截 throw new RuntimeException(); } public static void main(String[] args) { Order order= new Order(); try{ order.checkNullParam(); order.checkSecurity (); order.checkBackList(); order2.checkRule(); System.out.println("order success"); }catch (RuntimeException e){ System.out.println("order fail"); } } }
- 使用了异常来做逻辑条件判断问题就在于,如果后续逻辑越来越复杂的话,如异常只能返回异常信息,不能返回更多的字段,这时候需要自定义异常类。并且,阿里开发手册规定:禁止用异常做逻辑判断。所以我们可以用责任链模式优化这段代码【当你想要让
一个以上的对象有机会能够处理某个请求
的时候,就使用责任链模式【使用责任链模式需要咱们搞一个接口或者抽象类+每个对象差异化处理+对象链(数组)初始化(连起来)
】。】-
这个接口或者抽象类
需要有一个指向责任下一个对象的属性【你不搞个指针上个链点怎么找到下一个呢】
、一个设置下一个对象的set方法、给子类对象差异化实现的方法(如以下代码的doFilter方法)public abstract class AbstractHandler { //责任链中的下一个对象 private AbstractHandler nextHandler; /** * 责任链的下一个对象 */ public void setNextHandler(AbstractHandler nextHandler){ this.nextHandler = nextHandler; } /** * 具体参数拦截逻辑,给子类去实现 */ public void filter(Request request, Response response) { doFilter(request, response); if (getNextHandler() != null) { getNextHandler().filter(request, response); } } public AbstractHandler getNextHandler() { return nextHandler; } abstract void doFilter(Request filterRequest, Response response); }
-
责任链上,每个对象的差异化处理,比如业务场景中就有参数校验对象、安全校验对象、黑名单校验对象、规则拦截对象等多个不同的对象,正好够咱们玩:
/** * 参数校验对象 **/ @Component @Order(1) //顺序排第1,最先校验 public class CheckParamFilterObject extends AbstractHandler { @Override public void doFilter(Request request, Response response) { System.out.println("非空参数检查"); } } /** * 安全校验对象 */ @Component @Order(2) //校验顺序排第2 public class CheckSecurityFilterObject extends AbstractHandler { @Override public void doFilter(Request request, Response response) { //invoke Security check System.out.println("安全调用校验"); } } /** * 黑名单校验对象 */ @Component @Order(3) //校验顺序排第3 public class CheckBlackFilterObject extends AbstractHandler { @Override public void doFilter(Request request, Response response) { //invoke black list check System.out.println("校验黑名单"); } } /** * 规则拦截对象 */ @Component @Order(4) //校验顺序排第4 public class CheckRuleFilterObject extends AbstractHandler { @Override public void doFilter(Request request, Response response) { //check rule System.out.println("check rule"); } }
-
最后一步就是对象链连起来(初始化)&& 使用
@Component("ChainPatternDemo") public class ChainPatternDemo { //自动注入各个责任链的对象 @Autowired private List<AbstractHandler> abstractHandleList; private AbstractHandler abstractHandler; //spring注入后自动执行,责任链的对象连接起来 @PostConstruct public void initializeChainFilter(){ for(int i = 0;i<abstractHandleList.size();i++){ if(i == 0){ abstractHandler = abstractHandleList.get(0); }else{ AbstractHandler currentHander = abstractHandleList.get(i - 1); AbstractHandler nextHander = abstractHandleList.get(i); currentHander.setNextHandler(nextHander); } } } //直接调用这个方法使用 public Response exec(Request request, Response response) { abstractHandler.filter(request, response); return response; } public AbstractHandler getAbstractHandler() { return abstractHandler; } public void setAbstractHandler(AbstractHandler abstractHandler) { this.abstractHandler = abstractHandler; } }
-
- 责任链设计模式的基本组成:
- 抽象处理者(Handler)角色: 定义一个处理请求的接口,包含抽象处理方法和一个后继连接
- 具体处理者(Concrete Handler)角色: 实现抽象处理者的处理方法,判断
能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者
- 客户类(Client)角色: 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
- Netty中也用到了责任链模式。
- ChannlPipeline 和 ChannelHandler,ChannlPipeline 内部是由一组 ChannelHandler 实例组成的,内部通过双向链表将不同的 ChannelHandler 链接在一起
- 对于 Netty 中责任链模式的实现,也遵循了责任链模式的四个基本要素:
- 责任处理器接口:
- ChannelHandler 对应的就是责任处理器接口,
ChannelHandler 有两个重要的子接口:ChannelInboundHandler和ChannelOutboundHandler,分别拦截入站和出站的各种 I/O 事件
。
- ChannelHandler 对应的就是责任处理器接口,
- 动态创建责任链,添加、删除责任处理器
- ChannelPipeline 负责创建责任链,其内部采用双向链表实现,ChannelPipeline 的内部结构定义如下所示:
- ChannelPipeline 提供了一系列 add 和 remove 相关接口用于动态添加和删除 ChannelHandler 处理器:
- ChannelPipeline 负责创建责任链,其内部采用双向链表实现,ChannelPipeline 的内部结构定义如下所示:
- 上下文:ChannelHandlerContext 提供了 fire 系列的方法用于事件传播
- 从 ChannelPipeline 内部结构定义可以看出,ChannelHandlerContext 负责保存责任链节点上下文信息。ChannelHandlerContext 是对 ChannelHandler 的封装,每个 ChannelHandler 都对应一个 ChannelHandlerContext,实际上 ChannelPipeline 维护的是与 ChannelHandlerContext 的关系。
- 责任传播和终止机制
- 责任处理器接口:
- ChannlPipeline 和 ChannelHandler,ChannlPipeline 内部是由一组 ChannelHandler 实例组成的,内部通过双向链表将不同的 ChannelHandler 链接在一起
- 常见的责任链模式的业务场景就是下订单,
- 建造者模式:
经常碰见的 XxxBuilder 的类,通常都是建造者模式的产物,我们的使用通常都是一个模式的:就是先 new 一个 Builder,然后可以链式地调用一堆方法,最后再调用一次 build() 方法,我们需要的对象就有了
。Food food = new FoodBuilder().a().b().c().build(); Food food = Food.builder().a().b().c().build();
- 使用场景就是当咱们有
有些必填,有些选填的时候
,这个模式会使代码清晰很多。我们可以在 Builder 的构造方法中强制让调用者提供必填字段,还有,在 build() 方法中校验各个参数比在 User 的构造方法中校验 - 建造者模式非常简单,通过链式调用来设置对象的属性,在对象属性繁多的场景下非常有用。建造者模式的优势就是可以像搭积木一样自由选择需要的属性,并不是强绑定的。对于使用者来说,必须清楚需要设置哪些属性,在不同场景下可能需要的属性也是不一样的。Netty 中 ServerBootStrap 和 Bootstrap 引导器是最经典的建造者模式实现,在构建过程中需要设置非常多的参数,例如配置线程池 EventLoopGroup、设置 Channel 类型、注册 ChannelHandler、设置 Channel 参数、端口绑定等。
- 使用场景就是当咱们有
- 观察者模式:
- Dubbo中使用观察者模式最典型的例子是RegistryService。消费者在初始化的时候回调用subscribe方法,注册一个观察者,如果观察者引用的服务地址列表发生改变,就会通过NotifyListener通知消费者。此外,Dubbo的InvokerListener、ExporterListener 也实现了观察者模式,只要实现该接口,并注册,就可以接收到consumer端调用refer和provider端调用export的通知。
- Dubbo的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,运行 NotifyListener 的 notify 方法,执行监听器方法
- 修饰器模式:
- Dubbo中还大量用到了修饰器模式。比如ProtocolFilterWrapper类是对Protocol类的修饰。在export和refer方法中,配合责任链模式,把Filter组装成责任链,实现对Protocol功能的修饰。其他还有ProtocolListenerWrapper、 ListenerInvokerWrapper、InvokerWrapper等。
- 工厂方法模式:
- CacheFactory的实现采用的是工厂方法模式。CacheFactory接口定义getCache方法,然后定义一个AbstractCacheFactory抽象类实现CacheFactory,并将实际创建cache的createCache方法分离出来,并设置为抽象方法。这样具体cache的创建工作就留给具体的子类去完成。
- Provider 在 export 服务时,会调用 ServiceConfig 的 export 方法。ServiceConfig中有个字段:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()
;- Dubbo里有很多这种代码。
这也是一种工厂模式,只是实现类的获取采用了 JDKSPI 的机制
。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外
,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
- Dubbo里有很多这种代码。
- 抽象工厂模式:
- ProxyFactory及其子类是Dubbo中使用抽象工厂模式的典型例子。ProxyFactory提供两个方法,分别用来生产Proxy和Invoker(这两个方法签名看起来有些矛盾,因为getProxy方法需要传入一个Invoker对象,而getInvoker方法需要传入一个Proxy对象,看起来会形成循环依赖,但其实两个方式使用的场景不一样)。AbstractProxyFactory实现了ProxyFactory接口,作为具体实现类的抽象父类。然后定义了JdkProxyFactory和JavassistProxyFactory两个具体类,分别用来生产基于jdk代理机制和基于javassist代理机制的Proxy和Invoker。
- 适配器模式:
- 为了让用户根据自己的需求选择日志组件,Dubbo自定义了自己的Logger接口,并为常见的日志组件(包括jcl, jdk, log4j, slf4j)提供相应的适配器。并且利用简单工厂模式提供一个LoggerFactory,客户可以创建抽象的Dubbo自定义Logger,而无需关心实际使用的日志组件类型。在LoggerFactory初始化时,客户通过设置系统变量的方式选择自己所用的日志组件,这样提供了很大的灵活性。
- 代理模式:
- 用一个代理来隐藏具体实现类的实现细节,通常还用于在真实的实现的前后添加一部分逻辑。既然说是代理,那就要对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,
代理只是个代理,它不会完成实际的业务逻辑,而是一层皮而已
,但是对于客户端来说,它必须表现得就是客户端需要的真实实现。 - Dubbo consumer使用Proxy类创建远程服务的本地代理,本地代理实现和远程服务一样的接口,并且屏蔽了网络通信的细节,使得用户在使用本地代理的时候,感觉和使用本地服务一样。
- 用一个代理来隐藏具体实现类的实现细节,通常还用于在真实的实现的前后添加一部分逻辑。既然说是代理,那就要对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,
- 装饰器模式:
- Dubbo在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:
EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->ExceptionFilter
- 这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。
- Dubbo在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:
- 动态代理模式
- Dubbo扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key
未完待续…
巨人的肩膀:
moon聊技术
设计模式之禅
head first设计模式
程序员田螺的设计模式文章