1、前言
JMS即Java消息服务,面向消息中间件的API。本文研究过源码,试着分析一下如何从基本的使用方式,到整合进spring的方式的思考,来提高自己的系统设计能力。
只分析消息接收处理过程,话说spring-jms是整合jms的使用,而springcloud-stream是自己实现了一个发送接收过程,通过设计binding对接消息中间件上。
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统一管理。具体的线程数控制,线程的等待与通知管理还是比较复杂的,但这是细节技术。本文主要是整体上的设计思考。
5、源码中的设计
下面的图,简单汇总了源码相关的类中的引用与功能。与标准的类图不同,这里简化了细节,只为记在大脑中,设计开发时思路连贯,不偏离大方向。