Spring源码深度解析笔记(1)——Spring消息

写在前面的话:新项目基本完结了,为了充实自己,决定再读一次Spring源码,直到有新项目,还是希望能养成习惯吧。加油!!对了,这个笔记是完全就是抄书,书名是《Spring源码深度解析》作者是郝佳,有兴趣的同学可以买实体书看看,讲解的挺透彻的。
Java消息系统(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于两个应用程序之间或分布式系统中发送消息,进行异步通信。Java消息服务是一个具有平台无关的API,绝大多数MOM提供商都对JMS提供支持。
Java消息服务的规范包括两种消息模式,点对点和发布者/订阅者。许多提供商支持这一通用框架。因此,程序员可以在分布式软件中实现面向消息的操作,这些操作将具有不同面向消息中间产品的可移植性。
Java消息服务支持同步和异步的消息处理,在某些场景下,异步消息是必要的,而且比同步消息更加便利。
Java消息服务支持面向事件的方法接收消息,**事件驱动的程序设计**在被广泛认为是一种富有成效的程序设计范例。
在应用系统开发时,Java消息服务可以推迟选择面向消息中间件产品,也可以在不同的面对消息中间件切换。

13.1 JMS的独立使用

尽管大多数的Java消息服务都会跟Spring相结合,但是,还是非常有必要了解消息的独立使用方法,这对于了解消息的实现原理以及后续的与Spring整合实现分析都非常重要。在消息服务使用前,需要先开启消息服务。
消息服务的使用除了开启消息服务外,还需要构建消息的发送端和接收端,发送端主要用来将包含业务逻辑的消息发送至消息服务器,而消息接收端则用于将服务器中的消息提取并进行相应的处理。
ps:测试用例(略)。

13.2 Spring整合ActiveMQ

整个消息的发送和接受过程非常简单,单其中掺杂这大量的冗余代码,比如connection的创建与关闭,Session的创建与关闭等等,为了消除这一冗余工作量,Spring进行了进一步封装。
## 13.2.1 Spring配置文件
配置文件是Spring的核心,Spring整合消息服务的使用也是从配置文件开始的,类似数据库操作,Spring也将ActivitMQ中的操作统一封装至JmsTemplate中,以便我们统一使用,所以,在Spring的核心配置文件中首先要注册JmsTemplate类型的Bean,然后注册ActiveMQConnectionFactory用于连接消息服务器,是消息服务的基础,ActiveMQQueue则用于指定消息的目的地。
ps:源码(略)
到这里我们已经完成了Spring消息的发送与接收操作,在接收端使用jmsTemplate.receive(destination)方法只能接收一次消息,如果未接收到消息则会一直等待,用户可以设置timeout属性来控制等待时间,但是一旦接收到消息本次接收任务就会结束,虽然用户可以通过while(true)的方式来实现循环监听消息服务器上的消息,还有一种更好的解决办法:创建消息监听器。

 1. 创建消息监听器,用于监听消息,一旦有新消息Spring将消息引导至消息监听器以便用户进行相应的逻辑处理。(implement MessageListener接口并重写onMessage方法)
 2. 修改配置文件,注册消息容器,并将消息监听器注入到容器中。
 通过以上修改便可以进行消息监听的功能了,一旦消息传入至消息服务器,则会被消息监听器监听到,并有Spring将消息内容引导至消息监听器的处理函数中等待用户的进一步逻辑处理。

13.3 源码分析

尽管消息接收可以使用消息监听的方式替代模板方法,但是在发送的时候是无法替代的,在Spring中必须使用jmsTemplate提供的方法进行发送操作,可见jmsTemplate类的重要性。

13.3.1 JmsTemplate

在代码与Spring整合的实例中,Spring采用了与JDBC等一贯套路,提供了JmsTemplate来封装常用操作。

1. 通用代码抽取(execute(...)方法):该方法中封装了Connection以及Session的创建操作,为了发送一条消息需要做很多工作,需要很多的辅助代码,而这些代码是千篇一律的,没有任何的差别,所以execute方法的目的就是帮助我们抽离这些冗余代码使我们更加专注与业务逻辑的实现。从函数来看,这些冗余代码包括创建/关闭Connection,创建/关闭Session。而在准备工作结束后,调用回调函数将程序引入用户自定义实现的个性化处理(doInJms(...))。
2. 发送消息的实现(doSend(...)):有了基类辅助实现,使Spring更加专注于个性的处理,也就是说Spring使用execute方法封装了冗余代码,而将个性化代码实现放在了回调函数doInJms函数中,在发送消息的功能中回调函数通过局部类实现。消息发送的基本套路是:Destination创建MessageProducer、创建Message,并使用MessageProducer实例来发送消息。
3. 接收消息的实现(doReceive(...)):实现套路和发送差不多,同样还是使用execute函数封装冗余的公共操作,而最终目标还是通过consumer.receive()来接收消息,其中的过程就是对于messageConsumer的创建以及一些辅助操作。
13.3.2 监听器容器

消息监听容器是一个用于查看JMS目标等待消息达到的特殊Bean,一旦消息到达他就可以获取到消息,并通过调用onMessgae()方法将消息传递给一个MessageListener实现,Spring中的消息监听容器的类型如下

1. SimpleMessageListenerContainer:最简单的消息监听容器,只能处理固定数量的JMS会话,且不支持事务。
2. DefaultMessgaeListenerContainer:这个消息监听容器建立在SimpleMessageListenerContainer容器之上,添加对事务的支持。
3. serversession.ServerSessionMessage.ListenerContainer:这是功能最强大的消息事件监听器,与DefaultMessgaeListenerContainer相同,它支持事务,但它还能允许动态的管理JMS会话。
13.3.3 监听器源码

监听器容器的初始化只包含三句代码其中前两句只用于属性的验证,比如connectionFactory或者destination等属性是否为空,而真正用于初始化的操作委托在initialize()中执行。

消息监听器允许创建多个Session和MessgaeConsumer来接收信息,具体个数由concurrentConsumers属性指定,需要注意的是Destination为Queue的时候才能使用多个MessgaeConsumer(Queue中的一个消息只能被一个Consumer接收),虽然使用多个MessgaeConsumer会提高消息处理性能,但是消息处理的顺序却得不到保证,消息被接收的顺序仍然是消息发送时的顺序,但由于消息可能会被并发处理,因此消息处理的顺序可能和消息发送的顺序不同。此外,不应该在Destination为Topic的时候使用多个MessageConsumer,因为多个MessageConsumer接收到同样的消息。

分析源码得知,Spring根据concurrentConsumers数量建立对应数量的线程,而每一个线程都作为一个独立的接受者在循环接收信息(doRescheduleTask(AsyncMessageListenerInvoker task)),由于AsyncMessageListenerInvoker 作为一个Runnable角色去执行,所以对这个类的分析从run方法开始。

1. 并发控制synchronied(...)。
2. 根据每个任务设置的最大处理消息数量而做不同的处理,小于0默认无限制,一直接收消息(executeOngoingLoop());消息数量控制,一旦超出数量则停止循环(invokeListener())。
3. 清理操作,关闭connection,session,异常处理等。

其中核心的处理就是调用invokeListener来接收消息并激活消息监听器,但是之所以两种情况分开处理,这是考虑到无限制循环接收消息的情况下,用户可以通过设置标记位running来控制消息接收的恢复与暂停,并维护当前消息监听器的数量。

1. executeOngoingLoop():while((active = isActive() && !isRunning())),如果按照正常的流程是不会进入while循环中,而是直接进入invokeListener()来接收消息并激活监听器,但是我们不可能让循环一直持续下去,我们要考虑到暂停线程或者恢复线程的情况,这时,isRunning()函数就派上用场了。
2. isRunning用来检测标记位this.running状态进而判断是否进入while循环。由于维护当前线程数量,所以引入wasWaiting变量,用于判断线程是否处于等待状态。如果线程处于首次进入等待状态,则需要减少线程激活数量计数器。
3. 线程等待不是一味的采用while循环来控制,因为如果单纯的使用while循环会浪费CPU的始终周期,给资源造成巨大浪费,这里,采用的是全局控制变量lifecycleMonitor的wait()方法来暂停线程,所以,如果终止线程需要再次恢复的话,除了更改this.running标记外,还需要调用lifecycleMonitor.notify或者lifecycleMonitor.notifyAll来使线程恢复。

接下来就是消息的接收处理了(invokeListener())。

1. 初始化资源包:首次创建的时候创建session与consumer。
2. receiveAndExcute(...)接收消息并激活监听器。
3. 改变标记位信息处理成功。

消息监听器中事务处理(doReceiveAndExecute(…))。

1. receiveMessage(...):接收消息。
2. doExecuteListener(...):激活监听器。

激活监听器处理。

1. invokeListener(...):激活监听器。
2. commitIfNecessary(...):完成的功能是session.commit(),完成消息服务的事务提交,涉及两个事务,也就是说我们在消息接收过程中如果产生其他操作,比如想数据库中插入一条数据,一旦出现异常时就需要全部回滚,包括回滚插入数据库中的数据。但是除了我们常说的事务之外,对于消息本身还有一个事务,当接收一个消息时,必须使用事务提交的方式,这是在告诉消息服务器本地已经正常接收消息,消息服务接收到本地事务提交后便可以将此消息删除,否则当前消息会被其他接收者重新接收。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值