Spring消息(JMS)

7 篇文章 0 订阅
4 篇文章 0 订阅

1.异步消息简介

1.首先看一下同步模式和异步模式的区别:

 

客户端调用远程方法时,客户端必须等到远程方法完成后,才能继续执行。即使远程方法不向客户端返回任何信息,客户端也要被阻塞直到服务完成。

客户端不需要等待服务处理消息,甚至不需要等待消息投递完成。客户端发送消息,然后继续执行,这是因为客户端假定服务最终可以收到并处理这条消息。


1.发送消息

    在异步消息中有两个主要的概念:消息代理(message broker)和目的地(destination)。当一个应用发送消息时,会将消息交给一个消息代理。消息代理可以确保消息被投递到指定的目的地,同时解放发送者,使其能够继续进行其他的业务。

    不同的消息系统会提供不同的消息路由模式,但是有两种通用的目的地:队列(queue)和主题(topic)。每种类型都与特定的消息模型相关联,分别是点对点模型(队列)和发布/订阅模型(主题)。

点对点消息模型

    在点对点模型中,每一条消息都有一个发送者和一个接收者。当消息代理得到消息时,它将消息放入一个队列中。当接收者请求队列中的下一条消息时,消息会从队列中取出,并投递给接收者。因为消息投递后会从队列中删除,这样就可以保证消息只能投递给一个接收者。

    消息队列中的每一条消息只被投递给一个接收者,但是并不意味着只能使用一个接收者从队列中获取消息。事实上,通常可以使用几个接收者来处理队列中的消息。不过,每个接收者都会处理自己所接收到的消息。

发布—订阅消息模型

    在发布—订阅消息模型中,消息会发送给一个主题。与队列类似,多个接收者都可以监听一个主题。但是,与队列不同的是,消息不再是只投递给一个接收者,而是主题的所有订阅者都会接收到此消息的副本。

 2.评估异步消息的优点

采用同步通信机制访问远程服务的客户端存在几个限制,最主要的是:
1.同步通信意味着等待。当客户端调用远程服务的方法时,它必须等待远程方法结束后才能继续执行。如果客户端与远程服务频繁通信,或者远程服务响应很慢,就会对客户端应用的性能带来负面影响。
2.客户端通过服务接口与远程服务相耦合。如果服务的接口发生变化,此服务的所有客户端都需要做相应的改变。
3.客户端与远程服务的位置耦合。客户端必须配置服务的网络位置,这样它才知道如何与远程服务进行交互。如果网络拓扑进行调整,客户端也需要重新配置新的网络位置。
4.客户端与服务的可用性相耦合。如果远程服务不可用,客户端实际上也无法正常运行。

异步通信有这几个优势

1.无需等待

    使用JMS发送消息时,客户端不必等待消息被处理,甚至是被投递。客户端只需要将消息发送给消息代理,就可以确信消息会被投递给相应的目的地。

2.面向消息和解耦

    与面向方法调用的RPC通信不同,发送异步消息是以数据为中心的。这意味着客户端并没有与特定的方法签名绑定。任何可以处理数据的队列或主题订阅者都可以处理由客户端发送的消息,而客户端不必了解远程服务的任何规范。

3.位置独立

     消息客户端不必知道谁会处理它们的消息,或者服务的位置在哪里。客户端只需要了解需要通过哪个队列或主题来发送消息。因此,只要服务能够从队列或主题中获取消息即可,消息客户端根本不需要关注服务来自哪里。

4.确保投递

     当发送异步消息时,客户端完全可以相信消息会被投递。即使在消息发送时,服务无法使用,消息也会被存储起来,直到服务重新可以使用为止。

2.使用JMS发送消息

    Java消息服务(Java Message Service ,JMS)是一个Java标准,定义了使用消息代理的通用API。在此之前每个消息代理都有私有的API,这导致不同的代理之间的消息代码很难通用,但是借用JMS所有遵从规范的实现都使用通用的接口,这就类似于JDBC为数据库操作提供了通用的接口一样。

1.在Spring中搭建消息代理

首先需要下载amq然后启动服务。

创建连接工厂

    ActiveMQConnectionFactory是ActiveMQ自带的连接工厂,在Spring中可以使用如下方式进行配置:

    默认情况下,ActiveMQConnectionFactory会假设ActiveMQ代理监听localhost的61616端口。对于开发环境来说,这没有什么问题,但是在生产环境下,ActiveMQ可能会在不同的主机和/端口上。如果是这样的话,我们可以使用brokerURL属性来指定代理的URL:

配置连接工厂的第二种方式是:(首先在Spring配置文件中声明amq命名空间)

然后可以直接使用<amq:connectionFactory>元素声明连接工厂:

<amq:connectionFactory>是为ActiveMQ所准备的。如果我们使用不同的消息代理实现,它们不一定会提供Spring配置命名空间。如果没有提供的话,那我们就需要使用<bean>来装配连接工厂。在本例中,brokerURL属性中的URL指定连接工厂要连接到本地机器的61616端口(这个端口是ActiveMQ监听的默认端口)上的ActiveMQ。

声明ActiveMQ消息目的地

    除了连接工厂外,我们还需要消息传递的目的地。目的地可以是一个队列,也可以是一个主题,这取决于应用的需求。
    不论使用的是队列还是主题,我们都必须使用特定的消息代理实现类在Spring中配置目的地bean。例如,下面的<bean>声明定义了一个ActiveMQ队列:

主题模式:

 

在第一个示例中,构造器指定了队列的名称,这样消息代理就能获知该信息,而在接下来示例中,名称则为spitter.topic。

第二种声明方式是使用<amq:quence>和<amq:topic>

2.使用Spirng的JMS模板

使用传统的JMS发送消息,如:

使用传统的JMS接受消息:

还是有点冗余和重复的额。

使用JMS模板

     针对如何消除冗长和重复的JMS代码,Spring给出的解决方案是JmsTemplate。JmsTemplate可以创建连接、获得会话以及发送和接收消息。

    另外,JmsTemplate可以处理所有抛出的笨拙的JMSException异常。如果在使用JmsTemplate时抛出JMSException异常,JmsTemplate将捕获该异常,然后抛出一个非检查型异常,该异常是Spring自带的JmsException异常的子类。

    对于JMS API来说,JMSException的确提供了丰富且具有描述性的子类集合,让我们更清楚地知道发生了什么错误。不过,所有的JMSException异常的子类都是检查型异常,因此必须要捕获。
    JmsTemplate为我们捕获这些异常,并重新抛出对应非检查型JMSException异常的子类。为了使用JmsTemplate,我们需要在Spring的配置文件中将它声明为一个bean。如下的XML可以完成这项工作:

因为JmsTemplate需要知道如何连接到消息代理,所以我们必须为connection-Factory属性设置实现了JMS的ConnectionFactory接口的bean引用。

发送消息

使用JmsOperation(JmsTemplate所实现的接口)将Spittle对象发送给消息队列,而队列会在稍后得到处理。

JmsOperations的send()方法的第一个参数是JMS目的地名称,标识消息将发送给谁。当调用send()方法时,JmsTemplate将负责获得JMS连接、会话并代表发送者发送消息。

我们使用MessageCreator(在这里的实现是作为一个匿名内部类)来构造消息。在MessageCreator的createMessage()方法中,我们通过session创建了一个对象消息:传入一个Spittle对象,返回一个对象消息。

设置默认目的地

在上面的代码中,我们明确指定了一个目的地,在send()方法中将Spittle消息发向此目的地。当我们希望通过程序选择一个目的地时,这种形式的send()方法很适用。

下面设置一个默认的目的地:

     将目的地的名称设置为spittle.alert.queue,但它只是一个名称:它并没有说明你所处理的目的地是什么类型。如果已经存在该名称的队列或主题的话,就会使用已有的。如果尚未存在的话,将会创建一个新的目的地(通常会是队列)。但是,如果你想指定要创建的目的地类型的话,那么你可以将之前创建的队列或主题的目的地bean装配进来:

现在,调用JmsTemplate的send()方法时,我们可以去除第一个参数了:

    这种形式的send()方法只需要传入一个MessageCreator。因为希望消息发送给默认目的地,所以我们没有必要再指定特定的目的地。在调用send()方法时,我们不必再显式指定目的地能够让任务得以简化。但是如果我们使用消息转换器的话,发送消息会更加简单。

在发送时,对消息进行转换

    除了send()方法,JmsTemplate还提供了convertAndSend()方法。与send()方法不同,convertAndSend()方法并不需要MessageCreator作为参数。这是因convertAndSend()会使用内置的消息转换器(message converter)为我们创建消息。当我们使用convertAndSend()时,sendSpittleAlert()可以减少到方法体中只包含一行代码:

Spittle会在发送之前转换为Message。JmsTemplate内部会进行一些处理。它使用一个MessageConverter的实现类将对象转换为Message。MessageConverter是Spring定义的接口,只有两个需要实现的方法:

默认实现有:

默认情况下,JmsTemplate在convertAndSend()方法中会使用SimpleMessage Converter。但是通过将消息转换器声明为bean并将其注入到JmsTemplate的messageConverter属性中。如果你想使用JSON消息的话,那么可以声明一个MappingJacksonMessageConverterbean:

然后进行注入

接收消息

    当调用JmsTemplate的receive()方法时,JmsTemplate会尝试从消息代理中获取一个消息。如果没有可用的消息,receive()方法会一直等待,直到获得消息为止。

因为Spittle消息是作为一个对象消息来发送的,所以它可以在到达后转型为ObjectMessage。然后,我们调用getObject()方法把ObjectMessage转换为Spittle对象并返回此对象。

消息转换器还可以用在接收端,也就是使用JmsTemplate的receiveAndConvert():

    现在,没有必要将Message转换为ObjectMessage,也没有必要通过调用getObject()来获取Spittle,更无需担心检查型的JMSException异常。这个新的retrieve SpittleAlert()简洁了许多。但是,依然还有一个问题。使用JmsTemplate接收消息的最大缺点在于receive()和receiveAndConvert()方法都是同步的。这意味着接收者必须耐心等待消息的到来,因此这些方法会一直被阻塞,直到有可用消息(或者直到超时)。

3.创建消息驱动的POJO

EJB2规范的一个重要内容是引入了消息驱动bean(message-drivenbean,MDB)。MDB是可以异步处理消息的EJB。

创建消息监听器

如果使用EJB的消息驱动模型来创建Spittle的提醒处理器,我们需要使用@MessageDriven注解进行标注。

Spring提供了以POJO的方式处理消息的能力,这些消息来自于JMS的队列或主题中。例如:

配置消息监听器

    为POJO赋予消息接收能力的方法是在Spring中把它配置为消息监听器。Spring的jms命名空间为我们提供了所需要的一切。首先,让我们先把处理器声明为bean:

    然后,为了把SpittleAlertHandler转变为消息驱动的POJO,需要把这个bean声明为消息监听器:

    这里在消息监听器容器中包含了一个消息监听器。消息监听器容器(message listener container)是一个特殊的bean,它可以监控JMS目的地并等待消息到达。一旦有消息到达,它取出消息,然后把消息传给任意一个对此消息感兴趣的消息监听器。

    为了在Spring中配置消息监听器容器和消息监听器,我们使用了Spring jms命名空间中的两个元素。<jms:listener-container>中包含了<jms:listener>元素。这里的connection-factory属性配置了对connectionFactory的引用,容器中的每个<jms:listener>都使用这个连接工厂进行消息监听。

    对于<jms:listener>元素,它用于标识一个bean和一个可以处理消息的方法。为了处理Spittle提醒消息,ref元素引用了spittleHandlerbean。当消息到达spitter.alert.queue队列(通过destination属性配置)时,spittleHandler bean的handleSpittleAlert()方法(通过method属性指定的)会被触发。

3.使用AMQP实现消息功能

    AMQP为消息定义了线路层(wire-level protocol)的协议,而JMS所定义的是API规范。JMS的API协议能够确保所有的实现都能通过通用的API来使用,但是并不能保证某个JMS实现所发送的消息能够被另外不同的JMS实现所使用。而AMQP的线路层协议规范了消息的格式,消息在生产者和消费者间传送的时候会遵循这个格式。这样AMQP在互相协作方面就要优于JMS——它不仅能跨不同的AMQP实现,还能跨语言和平台。

    相比JMS,AMQP另外一个明显的优势在于它具有更加灵活和透明的消息模型。使用JMS的话,只有两种消息模型可供选择:点对点和发布-订阅。这两种模型在AMQP可以实现,而且AMQP还能够以其他的多种方式来发送消息,这是通过将消息的生产者与存放消息的队列解耦实现的。

1.AMQP简介

    在JMS中,有三个主要的参与者:消息的生产者、消息的消费者以及在生产者和消费者之间传递消息的通道(队列或主题)。

    在JMS中,通道有助于解耦消息的生产者和消费者,但是这两者依然会与通道相耦合。生产者会将消息发布到一个特定的队列或主题上,消费者从特定的队列或主题上接收这些消息。通道具有双重责任,也就是传递数据以及确定这些消息该发送到什么地方,队列的话会使用点对点算法发送,主题的话就使用发布-订阅的方式。

    AMQP的生产者并不会直接将消息发布到队列中。AMQP在消息的生产者以及传递信息的队列之间引入了一种间接的机制:Exchange。

    消息的生产者将信息发布到一个Exchange。Exchange会绑定到一个或多个队列上,它负责将信息路由到队列上。信息的消费者会从队列中提取数据并进行处理。

    AMQP定义了四种不同类型的Exchange,每一种都有不同的路由算法,这些算法决定了是否要将信息放到队列中。根据Exchange的算法不同,它可能会使用消息的routing key和/或参数,并将其与Exchange和队列之间binding的routing key和参数进行对比。(routing key可以大致理解为Email的收件人地址,指定了预期的接收者。)如果对比结果满足相应的算法,那么消息将会路由到队列上。否则的话,将不会路由到队列上。

四种标准的AMQP Exchange如下所示:
1.Direct:如果消息的routing key与binding的routing key直接匹配的话,消息将会路由到该队列上;
2.Topic:如果消息的routing key与binding的routing key符合通配符匹配的话,消息将会路由到该队列上;
3.Headers:如果消息参数表中的头信息和值都与bingding参数表中相匹配,消息将会路由到该队列上;
4.Fanout:不管消息的routing key和参数表的头信息/值是什么,消息将会路由到所有队列上。

2.配置Spring支持AMQP消息

    使用SpringAMQP需要配置一个连接工厂,具体来讲是配置一个RabbitMQ连接工厂。

    RabbitMQ是一个流行的开源消息代理,它实现了AMQP。SpringAMQP为RabbitMQ提供了支持,包括RabbitMQ连接工厂、模板以及Spring配置命名空间。

    配置RabbitMQ连接工厂最简单的方式就是使用Spring AMQP所提供的rabbit配置命名空间。为了使用这项功能,需要确保在Spring配置文件中已经声明了该模式:

     默认情况下,连接工厂会假设RabbitMQ服务器监听localhost的5672端口,并且用户名和密码均为guest。对于开发来讲,这是合理的默认值,但是对于生产环境,会修改这些默认值。如下<connection-factory>的设置重写了默认的做法:

声明队列、Exchange以及binding

声明队列、Exchange和binding的一种方式是使用RabbitMQ Channel接口的各种方法。除此之外还有以下组件:

    这些配置元素要与<admin>元素一起使用。<admin>元素会创建一个RabbitMQ管理组件(administrative component),它会自动创建(如果它们在RabbitMQ代理中尚未存在的话)上述这些元素所声明的队列、Exchange以及binding。

   例如,如果你希望声明名为spittle.alert.queue的队列,只需要在Spring配置中添加如下的两个元素即可:

    对于简单的消息来说,我们只需做这些就足够了。这是因为默认会有一个没有名称的direct Exchange,所有的队列都会绑定到这个Exchange上,并且routing key与队列的名称相同。在这个简单的配置中,我们可以将消息发送到这个没有名称的Exchange上,并将routing key设定为spittle.alert.queue,这样消息就会路由到这个队列中。实际上,我们重新创建了JMS的点对点模型。

    假如需要我们声明一个或更多的Exchange,并将其绑定到队列上。例如,如果要将消息路由到多个队列中,而不管routing key是什么,我们可以按照如下的方式配置一个fanout以及多个队列:

3.使用RabbitTemplate发送消息

配置RabbitTemplate的最简单方式是使用rabbit命名空间的<template>元素,如下所示:

    现在,要发送消息的话,我们只需要将模板bean注入到AlertServiceImpl中,并使用它来发送Spittle。如下的程序清单展现了一个新版本的AlertServiceImpl,它使用RabbitTemplate代替JmsTemplate来发送Spittle提醒。

    sendSpittleAlert()调用RabbitTemplate的convertAndSend()方法,其中RabbitTemplate是被注入进来的。它传入了三个参数:Exchange的名称、routing key以及要发送的对象。注意,这里并没有指定消息该路由到何处、要发送给哪个队列以及期望哪个消费者来获取消息。convertAndSend方法有多个重载方法这个可以去从源码中了解。

    如果在参数列表中省略Exchange名称,或者同时省略Exchange名称和routing key的话,RabbitTemplat将会使用默认的Exchange名称和routing key。按照我们之前的配置,默认的Exchange名称为空(或者说是默认没有名称的那一个Exchange),默认的routing key也为空。但是,我们可以在<template>元素上借助exchange和routing-key属性配置不同的默认值:

RabbitTemplate还有其他的方法来发送消息,如:

   鉴于这种情况,我们有了convertAndSend()方法,它会自动将对象转换为Message。它需要一个消息转换器的帮助来完成该任务,默认的消息转换器是SimpleMessageConverter,它适用于String、Serializable实例以及字节数组。Spring AMQP还提供了其他几个有用的消息转换器,其中包括使用JSON和XML数据的消息转换器。

4.接收AMQP消息

使用RabbitTemplate来接收消息

RabbitTemplate提供了多个接收信息的方法。最简单就是receive()方法,它位于消息的消费者端,对应于RabbitTemplate的send()方法。借助receive()方法,我们可以从队列中获取一个Message对象:

还可以配置获取消息的默认队列,这是通过在配置模板的时候,设置queue属性实现的:

这样的话,我们在调用receive()方法的时候,不需要设置任何参数就能从默认队列中获取消息了:

    在获取到Message对象之后,我们可能需要将它body属性中的字节数组转换为想要的对象。就像在发送的时候将领域对象转换为Message一样,将接收到的Message转换为领域对象同样非常繁琐。因此,我们可以考虑使用RabbitTemplate的receiveAndConvert()方法作为替代方案:

或者调用默认模板:

    receiveAndConvert()方法会使用与sendAndConvert()方法相同的消息转换器,将Message对象转换为原始的类型。调用receive()和receiveAndConvert()方法都会立即返回,如果队列中没有等待的消息时,将会得到null。这就需要我们来管理轮询(polling)以及必要的线程,实现队列的监控。

定义消息驱动的AMQP POJO

如果你想在消息驱动POJO中异步地消费使用Spittle对象,首先要解决的问题就是这个POJO本身。如:

并且要将其声明为一个bean

最后,我们需要声明一个监听器容器和监听器,当消息到达的时候,能够调用SpittleAlertHandler。

    <listener-container>与<listener>都与JMS对应的元素非常类似。但是,这些元素来自rabbit命名空间,而不是JMS命名空间。这里我们不再通过destination属性(JMS中的做法)来监听队列或主题,我们会通过queue-names属性来指定要监听的队列。但是,除此之外,基于AMQP的MDP与基于JMS的MDP都非常类似。

    另外一种指定要监听队列的方法是引用<queue>元素所声明的队列bean。我们可以通过queues属性来进行设置:

这里可以接受逗号分割的queue ID列表。这需要我们在声明队列的时候,为其指定ID。例如,如下是重新定义的提醒队列,这次指定了ID:

这里的id属性用来在Spring应用上下文中设置队列的bean ID,而name属性指定了RabbitMQ代理中队列的名称。

 

 

参考《Spring实战》

推荐书籍《Rabbit实战》

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值