消息传递基础架构(Messaging infrastructure)

走着路睡觉大约 6 分钟

消息传递基础架构(Messaging infrastructure)

目的

这个文档的目的是为开发人员介绍IntelliJ Platform 中的消息传递基础架构,帮助开发人员知道 什么时候,为什么,怎么用它

基本原理

消息传递实现了常见发布、订阅模型,并提供了额外的特性,比如在层次结构上广播和特殊的嵌套事件处理(嵌套事件指的是一个事件触发了另一个事件)

设计

下面介绍 messaging API的主要组件

topic-主题

提供了一个endpoint,允许用户订阅指定消息总线中的topic,和发送消息到指定消息总线中的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和监听器

消息总线

bus

连接

管理所有订阅的连接

connection

  • 保存主题与 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里注册,参考监听器

发布消息

publish

public void doChange(Context context) {
    ChangeActionNotifier publisher = myBus.syncPublisher(ActionTopics.CHANGE_ACTION_TOPIC);
    publisher.beforeAction(context);
    try {
        // Do action
        // ...
    } finally {
        publisher.afterAction(context)
    }
}

现有资源

广播

publish

提示

聚合关系,应用消息总线(application bus) 拥有多个 项目消息总线(project bus),每个项目消息总线(project bus)有多个 模块消息总线(module bus)

在一个消息总线里的订阅者,可以把消息发往另一个消息总线,

例如:

publish

提示

消息总线(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 保证了消息能按照顺序投递成功

例子:

假如我们的配置如下:

nested_config

提示

消息总线(bus)和连接(connection)之间是组合关系,连接(connection)和handler之间是组合关系

当用户向topic发送了一条消息后,接收消息流程如下:

  • 发送 message1

  • handler1 接收到message1并发送message2到相同的topic

  • handler2 接收 message1

  • handler2 接收 message2

  • handler1 接收 message2;

提示和技巧

减轻监听器的配置难度

消息传递基础架构(Messaging infrastructure) 是轻量级的,可以在本地系统中重用它来减轻订阅者关系的管理

通过下面2步自动完成监听器的配置:

  1. 定义业务接口

  2. 创建使用上述业务接口的消息总线和topic

让我们和手动配置监听器的步骤比较一下:

  1. 定义业务接口

  2. 给主题和所有订阅者配置引用关系

  3. 在主题中增加监听器的存储和方法调用

  4. 在接收到事件的时候,手动遍历所有的监听器和调用指定的回调方法

通过上述比较,用消息传递基础架构(Messaging infrastructure)配置监听器更方便

避免修改订阅者之前的共享数据

当2个订阅者共同修改一份文档的时候,会出现一个问题 IDEA-71701open in new window

每次修改文档都会执行以下步骤:

  1. 在遍历所有监听器处理文档修改事件的过程中,已经完成修改事件的监听器会发布新消息

  2. 发布文档修改完成

  3. 文档修改完成事件发送给所有监听器

这时候我们程序出现的情况描述如下:

  1. topic1拥有2个订阅者,message1发送给了这2个订阅者

  2. message1按顺序发给2个订阅者

  3. 发送message1

  4. subscriber1收到message1

  5. subscriber1 根据message1 发出了文档修改的请求 (例如:document.delete(startOffset, endOffset))

  6. 在第5步中发送的文档修改请求到达文档监听器之前

  1. subscriber2收到message1 开始修改文档

  2. 这时候 subscriber1发送的文档修改请求也开始修改文档

这时候会导致如下问题:

如果subscriber2在subscriber1之前修改了文档,subscriber1修改文档的请求是无效的

上次编辑于:
贡献者: zhaojingbo
Loading...