本文通过对观察者模式以及消息订阅模式以及此类思想在一些经典框架中的运用进行归纳总结对比,然后着重梳理了jetLinks框架里EventBus的设计流程,让我们能看清楚这些设计的背后思想,从而更好的进行后续的开发工作
1、观察者模式
观察者模式UML如下,Subject充当被观察者的角色,在其内部会聚合0或者多个Observer(观察者列表) ,并在被观察者 Subject 内部状态发生变化时通过 notifyObservers() 方法调用 观察者的 notify() 方法,从而实现了 被观察者状态变更实时通知到观察者, 从依赖关系上看是一种比较强或者说比较直接的依赖关系在观察者与被观察者之间。

一个简单的例子:
csharp
/**
* 观察者
*/
public interface Observer {
void notify();
}
/**
* 具体观察者
*/
public class ConcreteObserver implements Observer{
@Override
public void notify() {
System.out.println(" ConcreteObserver 接受到信息,并进行处理");
}
}
/**
* 被观察者
*/
public abstract class Subject {
// 定义一个被观察者数组
private List<Observer> obsList = new ArrayList<>();
// 增加一个观察者
public void addObserver(Observer observer){
obsList.add(observer);
}
// 删除一个观察者
public void removeObserver(Observer observer){
obsList.remove(observer);
}
// 通知所有观察者
public void notifyObservers(){
for (Observer observer : obsList){
observer.notify();
}
}
}
/**
* 具体被观察者
*/
public class ConcreteSubject extends Subject{
// 一个改变被观察内部状态的方法
public void doSomething(){
//1、 做 改变内部状态 动作
//2. 通知观察者
super.notifyObservers();
}
}
public class ObserverClient {
public static void main(String[] args) {
// 创建一个被观察者
ConcreteSubject subject = new ConcreteSubject();
// 定义一个观察者
Observer observer = new ConcreteObserver();
// 观察者观察被观察者
subject.addObserver(observer);
subject.doSomething();
}
}
2、Spring Event 事件监听机制
在 Spring 容器中通过ApplicationEven
类和 ApplicationListener
接口来实现事件监听机制,每次Event 被发布到Spring容器中时都会通知该Listener。需要注意的是,Spring 的事件默认是同步的,调用 publishEvent
方法发布事件后,它会处于阻塞状态,直到Listener接收到事件并处理返回之后才继续执行下去。
异步支持
Spring 事件机制默认是同步阻塞的,如果 ApplicationEventPublisher 发布事件之后他会一直阻塞等待listener 响应,多个 listener 的情况下前面的没有执行完后面的会一直被阻塞。这时候我们可以利用 Spring 提供的线程池注解 @Async
来实现异步线程
3、发布订阅模式
发布订阅模式通过弱化发布者与订阅者之间的关系,来使系统组件之间更加的解耦,这种解耦好处是发布者和订阅者模块逻辑更加独立,从而更有利于各自模块的后续发展,以及可以分别对两个模块做不同的技术设计和优化设计,比如针对生产者的消息发布速率的控制,发布消息的同步异步策略,以及发布消息路由选择等,针对消费者可以设计消费策略,ack机制等等,所以这种模式或者思想也是使用的最多的一种

4、EventBus 发布订阅机制
此处主要解析 jetlinks 中 EventBus 的设计逻辑,因为jetLinks 中大量使用了Event Bus 的发布订阅,深入研究jetLinks 中EventBus的源码设计 对于理解jetLinks 组件之间通信以及业务流程流转 以及后续 业务开发都大有裨益。
EventBus 的大致处理流程如下:其实思路和Spring 的处理思路也很类似,就是在容器加载的时候收集所有被@Subscribe注解标注的方法,并把这些方法构造成订阅关系模型,之后保存到 EventBus的内存中,之后在EventBus 发布消息的时候,通过匹配Toptic ,找到相应的订阅关系模型,这个模型里存储有相应的 订阅方法,之后调用即可
其中 ProxyMessageListener#proxy 会通过 javassist 包动态生成一个 BiFunction<Object, Object, Object> 的实现,具体的实现如下,可以看到通过动态生成实现类的方式直接调用了我们业务里自定义的订阅方法:
typescript
public class proxy.java.util.function.BiFunction$Proxy19 implements BiFunction <Object, Object, Object> {
//说明:org.jetlinks.community.standalone.test.SubscribeAnnotationMethodTest 是自定义类名通过 ProxyMessageListener中type拿到
//param 是调用方传入的动态参数值
// handleEvent 是 ProxyMessageListener#method 获取的方法名称
public Object apply(Object target,Object param){
org.jetlinks.community.standalone.test.SubscribeAnnotationMethodTest _target = (org.jetlinks.community.standalone.test.SubscribeAnnotationMethodTest)target;
org.jetlinks.core.event.TopicPayload _param = (org.jetlinks.core.event.TopicPayload)param;
return _target.handleEvent(_param);
}
.....省略其他object方法
}
5、分布式消息中间件
分布式消息中间件生产者 Broker 以及 消费者之间 进程间基于 Toptic 通过网路进行交互

6、总结思考:
编程组件之间的关联关系的强弱或者依赖层级决定了所创建系统的复杂度,依赖层次越多,关联关系越弱,系统越复杂。观察者和被观察者之间通过不断调整两者的依赖层级,就会变成基于主题的发布订阅模式。而这个关系的纽带也从原先的直接强依赖变成了基于主题的关联关系。
所以对于一个复杂的大型分布式系统,一方面要通过分布式的横向扩展来支持海量的处理,另一方面也要和这种深依赖层级做抗争控制核心领域复杂度。
7、参考
rocketmq.apache.org/zh/docs/dom...
learn.microsoft.com/en-us/azure...
zh.wikipedia.org/wiki/%E8%A7...