spring-jms的接收消息功能的设计思考

时间:2024-04-02 17:15:35

1、前言

JMS即Java消息服务,面向消息中间件的API。本文研究过源码,试着分析一下如何从基本的使用方式,到整合进spring的方式的思考,来提高自己的系统设计能力。
只分析消息接收处理过程,话说spring-jms是整合jms的使用,而springcloud-stream是自己实现了一个发送接收过程,通过设计binding对接消息中间件上。

2、jms接收消息

jsm是标准接口,具体的产品要实现 连接工厂ConnectionFactory、Connection、Session、MessageConsumer等接口。通常我们的使用方式如图上所示。用户真正关心的是连接哪个消息中间件,消息处理逻辑。
spring-jms的接收消息功能的设计思考

3、初步设计

一个消息中间件上,可以有多个消息处理,分别接收不同目的地的消息。而系统考虑多个消息中间件。
初步的设计如上图的虚线所示的类,包括:

  1. 持有当前JMS产品连接工厂的类A:可以有多个,每个都是spring容器中的默认单例bean,还可以设置一些连接相关的属性。

  2. 包含消息处理方法的类B:每个消息的处理就是类的一个方法,要注解,标识对应哪个JMS产品(A),对应哪个目的地。

  3. 每一个消息处理方法都拆解出来,与相对应的JMS产品配置,成为一个组合©。在这个组合中,一但组合启动,可以产生或共享connection,产生session,consummer,有一个线程一直轮询,拿到消息,可以反射调用消息处理方法。利用spring的bean生命周期进行作业线程的启动与停止。

  4. C类会产生很多,需要D类管理起来

  5. 利用spring的扩展点进行组装,于是需要一个后置处理器类E

    有了上面的设计,可以利用上图下面部分的虚线的类,开发出一个简单的系统,运行起来。

4、重构

本着功能单一,界限明确的原则和一些常用的套路。 一些配置与功能是要分开的,配置类往往是功能类的工厂。通常产生的一批对象,是要统一进行管理的。

4.1 类的重构

  1. A是持有JMS产品的连接工厂的,同一个JMS产品的连接下,可能有多个destination的消息的接收,主要由用户配置。就是一个持有JMS产品连接工厂的类,暂时无切分/合并要求。最后叫:ListenerContainerFactory。配置类叫工厂也是合理的。
  2. B是每一个注解的方法,所以应该有一个类把方法、注解信息、所在的类,对应的JMS产品,相应的destination都记录下来。最后是叫:Endpoint
  3. A与B的个数不定,A与B一般是一对多的关系的,必须有一个关联,可以是B关联A,因为注解上也是这样。这样的一组组关联必然记录在一个单例对象中,这就是C。这个一组AB叫:descriptor
  4. AB组合会产生一个工作单元,是动态的内容,也放在C中是可以的。不过这一组静态属性,一个动态工作单元明显不同,C可以拆成两个类C1/C2,分别管理静态与动态部分。最后C1叫:Registrar,C2叫:Registry
  5. 先有C1,后有C2,C2存着所有的动态单元,每个单元都有启停操作,正好由C2统一启停,C2可以实现spring的lifeCycle接口。
  6. 一个个动态单元称为listenerContainer,肯定由配置类来生成。由谁产生,又在什么时间生成呢?A可以,B可以,时间可以是找到B产生配置组时,也可以在所有的配置组都找到了统一再产生。源码是由A产生,所以A叫ListenerContainerFactory。A正好还存放所有listenerContainer公共用的对象,连接工厂,事务管理器,公共线程池等。
  7. 最后,所有的关系需要由spring的后置处理器来完成,这就是JmsListenerAnnotationBeanPostProcessor,由@EnableJms引入到spring中。在处理前,spring容器中应该有用户提供在config类中相关的配置bean,分别是JMS连接工厂,ListenerContainerFactory,以及方法上有@JmsListener注解的类。

4.2 后置处理器BeanPostProcessor处理过程

  1. 找出所有的@listener的方法,产生Endpoint。以及根据注解中的ListenerContainerFactory名字,从spring容器中找到对象。
  2. Endpoint与ListenerContainerFactory组成一组,放置在Registrar中统一管理。
  3. 用其管理类Registrar的方法,上面每产生的一组,都产生ListenerContainer对象,放置在Registry的map中统一管理。
  4. Registrar与Registry都是单例,后置处理器也是单例,它们之间要有引用。后置处理器BeanPostProcessor中有Registrar,Registrar中有Registry,它们不需要反向引用。源码中Registrar是BeanPostProcessor所new出来的,BeanPostProcessor又aware下spring容器,从中拿到Registry,并设置给Registrar用。为何不都在@EnableJms中引入并关联好呢?

4.3 listenerContainer的说明

简单的话可以是一个线程polling消息,但可以是多线程,所以可以共享连接,但session,consummer每个线程都是不一样的。所以包含了一个内部类AsyncMessageListenerInvoker,每个实例对象持有自己的相关对象,它也是runable接口。
即然是多个,也要被listenerContainer统一管理。具体的线程数控制,线程的等待与通知管理还是比较复杂的,但这是细节技术。本文主要是整体上的设计思考。

5、源码中的设计

下面的图,简单汇总了源码相关的类中的引用与功能。与标准的类图不同,这里简化了细节,只为记在大脑中,设计开发时思路连贯,不偏离大方向。
spring-jms的接收消息功能的设计思考