消息传递基础架构(Messaging infrastructure)
消息传递基础架构(Messaging infrastructure)
目的
这个文档的目的是为开发人员介绍IntelliJ Platform 中的消息传递基础架构,帮助开发人员知道 什么时候,为什么,怎么用它
基本原理
消息传递实现了常见发布、订阅模型,并提供了额外的特性,比如在层次结构上广播和特殊的嵌套事件处理(嵌套事件指的是一个事件触发了另一个事件)
设计
下面介绍 messaging API的主要组件
topic-主题
提供了一个endpoint,允许用户订阅指定消息总线中的topic,和发送消息到指定消息总线中的topic
组合关系
displayName 主题的名字,用来记录上场和监控
broadcastDirection 广播方向. 默认值 = TO_CHILDREN;
listener class 是订阅了topic的业务接口,负责接收消息
Topic需要指定消息总线,可以使用注解com.intellij.util.messages.Topic.AppLevel(消息发布到应用级消息总线) 或 com.intellij.util.messages.Topic.ProjectLevel(消息发布到项目级消息总线)
@Topic.AppLevel
Topic<KeymapManagerListener> TOPIC = new Topic<>(KeymapManagerListener.class, Topic.BroadcastDirection.NONE);
提示
扩展点和监听器(Extension Point and Listener List 文档中的监听器部分列举了所有可用topic和监听器
消息总线
连接
管理所有订阅的连接
保存主题与 handler(接收topic中的消息的代码)的映射关系,注意:在同一个连接中,同一个topic下,只能有一个handler
可以指定一个默认handler,不需要指定调用方法,就可以订阅topic,当存储该映射关系的时候,将使用默认的handler
可以释放获取的资源(disconnect() 方法). 资源回收
总结
public interface ChangeActionNotifier {
Topic<ChangeActionNotifier> CHANGE_ACTION_TOPIC = Topic.create("custom name", ChangeActionNotifier.class);
void beforeAction(Context context);
void afterAction(Context context);
}
订阅
public void init(MessageBus bus) {
bus.connect().subscribe(ActionTopics.CHANGE_ACTION_TOPIC, new ChangeActionNotifier() {
@Override
public void beforeAction(Context context) {
// Process 'before action' event.
}
@Override
public void afterAction(Context context) {
// Process 'after action' event.
}
});
}
提示
在 IntelliJ Platform 2019.3及以后的版本,处方使用声明式注册(在plugin.xml里注册,参考监听器)
发布消息
public void doChange(Context context) {
ChangeActionNotifier publisher = myBus.syncPublisher(ActionTopics.CHANGE_ACTION_TOPIC);
publisher.beforeAction(context);
try {
// Do action
// ...
} finally {
publisher.afterAction(context)
}
}
现有资源
通过ComponentManager.getMessageBus() 可以获取到消息总线,很多标准接口实现了消息总线接口,例如Application 和 Project
IntelliJ 平台使用了许多公共topic , 例如: AppTopics , ProjectTopics 订阅它们,将会收到相关的信息
广播
提示
聚合关系,应用消息总线(application bus) 拥有多个 项目消息总线(project bus),每个项目消息总线(project bus)有多个 模块消息总线(module bus)
在一个消息总线里的订阅者,可以把消息发往另一个消息总线,
例如:
提示
消息总线(bus)之间是聚合关系,消息总线和连接,连接和topic-handle之间是组合关系
上图表示一个简单的等级关系,application bus是project bus的父亲,并且在同一个topic里有3个订阅者
如果我们设置的广播方向是 TO_CHILDREN,我们将得到以下信息:
一个消息通过application消息总线发往 topic1
handler1收到了该消息
这个消息被发送到了 project消息总线里的handler2和handler3订阅者
好处
订阅了 子消息总线的消息者 不需要再去订阅父消息总线,即可以收到父消息总线里的消息。 在上面的例子中,订阅者订阅了project消息总线,同时也想要监听应用级(application-level)的事件,这时我们不需要存储应用级(application-level)消息总线和订阅者之间的强引用(hard reference)关系,从而避免了项目重新打开时的内存泄漏
选项
每个主题都定义了自己的广播方向配置,可以设置为下面几个选项:
TO_CHILDREN (默认);
NONE;
TO_PARENT;
嵌套消息
嵌套消息指的是在消息处理过程中直接或间接的发了另一条消息,IntelliJ Platform 保证了消息能按照顺序投递成功
例子:
假如我们的配置如下:
提示
消息总线(bus)和连接(connection)之间是组合关系,连接(connection)和handler之间是组合关系
当用户向topic发送了一条消息后,接收消息流程如下:
发送 message1
handler1 接收到message1并发送message2到相同的topic
handler2 接收 message1
handler2 接收 message2
handler1 接收 message2;
提示和技巧
减轻监听器的配置难度
消息传递基础架构(Messaging infrastructure) 是轻量级的,可以在本地系统中重用它来减轻订阅者关系的管理
通过下面2步自动完成监听器的配置:
定义业务接口
创建使用上述业务接口的消息总线和topic
让我们和手动配置监听器的步骤比较一下:
定义业务接口
给主题和所有订阅者配置引用关系
在主题中增加监听器的存储和方法调用
在接收到事件的时候,手动遍历所有的监听器和调用指定的回调方法
通过上述比较,用消息传递基础架构(Messaging infrastructure)配置监听器更方便
避免修改订阅者之前的共享数据
当2个订阅者共同修改一份文档的时候,会出现一个问题 IDEA-71701
每次修改文档都会执行以下步骤:
在遍历所有监听器处理文档修改事件的过程中,已经完成修改事件的监听器会发布新消息
发布文档修改完成
文档修改完成事件发送给所有监听器
这时候我们程序出现的情况描述如下:
topic1拥有2个订阅者,message1发送给了这2个订阅者
message1按顺序发给2个订阅者
发送message1
subscriber1收到message1
subscriber1 根据message1 发出了文档修改的请求 (例如:document.delete(startOffset, endOffset))
在第5步中发送的文档修改请求到达文档监听器之前
subscriber2收到message1 开始修改文档
这时候 subscriber1发送的文档修改请求也开始修改文档
这时候会导致如下问题:
如果subscriber2在subscriber1之前修改了文档,subscriber1修改文档的请求是无效的