spring-jms的接收消息功能的设计思考
程序员文章站
2022-06-17 15:46:28
...
1、前言
JMS即Java消息服务,面向消息中间件的API。本文研究过源码,试着分析一下如何从基本的使用方式,到整合进spring的方式的思考,来提高自己的系统设计能力。
本文只分析消息接收处理过程,话说spring-jms是整合jms的使用,而springcloud-stream是自己实现了一个发送接收过程,通过设计binding对接消息中间件上。这部分内容在前面的博客中有过介绍。另外提一下,用@enable***,加载特定的注解类,实现特定的功能非常常见。
本文分析的源码是基于spring-framework中的spring-jms模块中。先是分析了消息接收相关的jms连接与消息监听处理的关联关系,再分析了这种关联下产生一个消息处理容器的功能与结构,最后分析如何让一个后置处理器来一步步产生这样的功能结构。
最近文章又补上了核心功能类:ListenerContainer 的设计分析。
2、jms接收消息
jsm是标准接口,具体的产品要实现 连接工厂ConnectionFactory、Connection、Session、MessageConsumer等接口。通常我们的使用方式如下图上部分所示。用户真正关心的是连接哪个消息中间件,消息处理逻辑。
3、初步设计
一个消息中间件上,可以有多个消息处理,分别接收不同目的地的消息。而系统考虑多个消息中间件。
初步的设计如上图的虚线所示的类,包括:
有了上面的设计,可以利用上图下面部分的虚线的类,开发出一个简单的系统,运行起来。
4、重构
本着功能单一,界限明确的原则和一些常用的套路。 一些配置与功能是要分开的,配置类往往是功能类的工厂。通常产生的一批对象,是要统一进行管理的。
4.1 类的重构
4.2 后置处理器BeanPostProcessor处理过程
4.3 listenerContainer的说明
简单的话可以是一个线程polling消息,但可以是多线程,所以可以共享连接,但session,consummer每个线程都是不一样的。所以包含了一个内部类AsyncMessageListenerInvoker,每个实例对象持有自己的相关对象,它也是runable接口。
即然是多个,也要被listenerContainer统一管理。具体的线程数控制,线程的等待与通知管理还是比较复杂的,后面6章节已经补上了详细设计分析。
5、源码中的设计
下面的图,简单汇总了源码相关的类中的引用与功能。与标准的类图不同,这里简化了细节,只为记在大脑中,设计开发时思路连贯,不偏离大方向。
6、核心功能类ListenerContainer的设计分析
每一个注解的@jsmListener将由工厂产生一个ListenerContainer,这里核心功能类,实现了特定消息中间件,特定目的地,特定消息处理器下,多线程获取消息并处理消息的过程,这一切都纳入spring容器的生命周期管理。
7、总结
通过对spring-jms的分析,有助于:
JMS即Java消息服务,面向消息中间件的API。本文研究过源码,试着分析一下如何从基本的使用方式,到整合进spring的方式的思考,来提高自己的系统设计能力。
本文只分析消息接收处理过程,话说spring-jms是整合jms的使用,而springcloud-stream是自己实现了一个发送接收过程,通过设计binding对接消息中间件上。这部分内容在前面的博客中有过介绍。另外提一下,用@enable***,加载特定的注解类,实现特定的功能非常常见。
本文分析的源码是基于spring-framework中的spring-jms模块中。先是分析了消息接收相关的jms连接与消息监听处理的关联关系,再分析了这种关联下产生一个消息处理容器的功能与结构,最后分析如何让一个后置处理器来一步步产生这样的功能结构。
最近文章又补上了核心功能类:ListenerContainer 的设计分析。
2、jms接收消息
jsm是标准接口,具体的产品要实现 连接工厂ConnectionFactory、Connection、Session、MessageConsumer等接口。通常我们的使用方式如下图上部分所示。用户真正关心的是连接哪个消息中间件,消息处理逻辑。
3、初步设计
一个消息中间件上,可以有多个消息处理,分别接收不同目的地的消息。而系统考虑多个消息中间件。
初步的设计如上图的虚线所示的类,包括:
- 持有当前JMS产品连接工厂的类A:可以有多个,每个都是spring容器中的默认单例bean,还可以设置一些连接相关的属性。
- 包含消息处理方法的类B:每个消息的处理就是类的一个方法,要注解,标识对应哪个JMS产品(A),对应哪个目的地。
- 每一个消息处理方法都拆解出来,与相对应的JMS产品配置,成为一个组合©。在这个组合中,一但组合启动,可以产生或共享connection,产生session,consummer,有一个线程一直轮询,拿到消息,可以反射调用消息处理方法。利用spring的bean生命周期进行作业线程的启动与停止。
- C类会产生很多,需要D类管理起来
- 利用spring的扩展点进行组装,于是需要一个后置处理器类E
有了上面的设计,可以利用上图下面部分的虚线的类,开发出一个简单的系统,运行起来。
4、重构
本着功能单一,界限明确的原则和一些常用的套路。 一些配置与功能是要分开的,配置类往往是功能类的工厂。通常产生的一批对象,是要统一进行管理的。
4.1 类的重构
- A是持有JMS产品的连接工厂的,同一个JMS产品的连接下,可能有多个destination的消息的接收,主要由用户配置。就是一个持有JMS产品连接工厂的类,暂时无切分/合并要求。最后叫:ListenerContainerFactory。配置类叫工厂也是合理的。
- B是每一个注解的方法,所以应该有一个类把方法、注解信息、所在的类,对应的JMS产品,相应的destination都记录下来。最后是叫:Endpoint
- A与B的个数不定,A与B一般是一对多的关系的,必须有一个关联,可以是B关联A,因为注解上也是这样。这样的一组组关联必然记录在一个单例对象中,这就是C。这个一组AB叫:descriptor
- AB组合会产生一个工作单元,是动态的内容,也放在C中是可以的。不过这一组静态属性,一个动态工作单元明显不同,C可以拆成两个类C1/C2,分别管理静态与动态部分。最后C1叫:Registrar,C2叫:Registry。
- 先有C1,后有C2,C2存着所有的动态单元,每个单元都有启停操作,正好由C2统一启停,C2可以实现spring的lifeCycle接口。
- 一个个动态单元称为listenerContainer,肯定由配置类来生成。由谁产生,又在什么时间生成呢?A可以,B可以,时间可以是找到B产生配置组时,也可以在所有的配置组都找到了统一再产生。源码是由A产生,所以A叫ListenerContainerFactory。A正好还存放所有listenerContainer公共用的对象,连接工厂,事务管理器,公共线程池等。
- 最后,所有的关系需要由spring的后置处理器来完成,这就是JmsListenerAnnotationBeanPostProcessor,由@EnableJms引入到spring中。在处理前,spring容器中应该有用户提供在config类中相关的配置bean,分别是JMS连接工厂,ListenerContainerFactory,以及方法上有@JmsListener注解的类。
4.2 后置处理器BeanPostProcessor处理过程
- 找出所有的@listener的方法,产生Endpoint。以及根据注解中的ListenerContainerFactory名字,从spring容器中找到对象。
- Endpoint与ListenerContainerFactory组成一组,放置在Registrar中统一管理。
- 用其管理类Registrar的方法,上面每产生的一组,都产生ListenerContainer对象,放置在Registry的map中统一管理。
- Registrar与Registry都是单例,后置处理器也是单例,它们之间要有引用。后置处理器BeanPostProcessor中有Registrar,Registrar中有Registry,它们不需要反向引用。源码中Registrar是BeanPostProcessor所new出来的,BeanPostProcessor又aware下spring容器,从中拿到Registry,并设置给Registrar用。为何不都在@EnableJms中引入并关联好呢?
4.3 listenerContainer的说明
简单的话可以是一个线程polling消息,但可以是多线程,所以可以共享连接,但session,consummer每个线程都是不一样的。所以包含了一个内部类AsyncMessageListenerInvoker,每个实例对象持有自己的相关对象,它也是runable接口。
即然是多个,也要被listenerContainer统一管理。具体的线程数控制,线程的等待与通知管理还是比较复杂的,后面6章节已经补上了详细设计分析。
5、源码中的设计
下面的图,简单汇总了源码相关的类中的引用与功能。与标准的类图不同,这里简化了细节,只为记在大脑中,设计开发时思路连贯,不偏离大方向。
6、核心功能类ListenerContainer的设计分析
每一个注解的@jsmListener将由工厂产生一个ListenerContainer,这里核心功能类,实现了特定消息中间件,特定目的地,特定消息处理器下,多线程获取消息并处理消息的过程,这一切都纳入spring容器的生命周期管理。
- - 如图所示:这是这个核心类的继承关系简图,类设计的层次感很好,这个类多层继承过来,并实现了多个接口。上两层是特定消息中间件连接工厂和目的地。接下来是比较抽象的生命周期管理过程。接着是消息的处理,消息的轮询获取,最后是多线程管理。
- - 层次感比如:消息处理是获取消息的上层,因为轮询可以换push,但消息处理可以不变。同样是轮询,底层的多线程管理可也是换成其它方式。
- - 注意:连接工厂是共享的,连接可能是共享的,消息处理Invoker是共享的,可session,MessageConsumer这些是线程独享的。
7、总结
通过对spring-jms的分析,有助于:
- 可以把自己的产品模仿此风格(功能注解与enable整合很多),方便的集成到spring中,当然进一步可以自动配置
- 合理的分配类的功能,包含配置类,工厂类,管理类,功能类,内部类...
- 合理的建立类/对象之间的引用关系
- 即使不用spring,在bean功能上也区分出构造,初始化等标准阶段
- 对于动态的功能,多线程内容,还有启/停/等阶段的设计
- 包括命名也模仿,包括上面的常用方法的名字