消息请求应答与RPC
AmqpTemplate同样提供了大量的sendAndReceive方法,这些方法和上面讲到的方法接收到同样的参数来完成一次性的发送工作。这些方法在请求应答场景中很有用,因为它们在发送之前配置了reply-to属性,因此它可以监听响应消息(内部穿件了一个专属队列来完成)。
类似,同样存在这convertSendAndReceive方法,在请求和响应的时候使用了MessageConverter。
默认的情况下,每个应答都使用一个新的临时的队列。然而,可以在模板上配置应答队列,这样更高效,同时你还可以为这个队列设置参数,在这种情况下,你必须提供<reply-listener/>子元素。这个子元素提供了应答队列的监听容器,template是监听者。在消息监听容器配置中配置的东西都可以在这里配置,除了connection-factory,message-converter,这些东西将继承template的配置。
<rabbit:template id="amqpTemplate"
connection-factory="connectionFactory" reply-queue="replies">
<rabbit:reply-listener/>
</rabbit:template>
因为容器和模板共享一个连接,但是它们不共享同一个channel,因此请求和应答不在一个事物中。
应答队列中的消息关联
当你使用固定的应答队列,提供关联数据很有必要,这样请求和响应才能关联上。在默认的情况下,标准的correlationId属性会被用来持有关联数据。然而,如果你想使用定制的属性来持有关联数据,你可以设置<rabbit-template/>的correlation-key属性。这种情况下,设置correlationId和忽略这个属性一样。当然,客户端和服务器端必须使用同样的头来关联数据。
应答监听容器
当你使用固定的应答队列时,使用SimpleListnerContainer来接收应答,使用RabbitTemplate来进行消息监听。当使用<rabbit:template/>时,像上面那样,解析器会定义容器,并且将模板定义为监听器。
注意:
当你不使用固定的replyQueue时,监听容器没有必要。
如果你通过<bean/>或者使用@Configuration来定义RabbitTemplate,你需要自己定义监听容器。如果你不这样做,你将永远都接收不到应答,最终导致超时,返回空给sendAndReceive方法。
注意:
当你自己装配应答监听和模板的时候,确保模板的replyQueue和容器的queues属性参照同一个队列很重要。模板将响应队列插入到发送消息的replyTo属性中。
下面是手动专配这些beans:
<bean id="amqpTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<constructor-arg ref="connectionFactory"/>
<property name="exchange" value="foo.exchange"/>
<property name="routingKey" value="foo"/>
<property name="replyQueue" ref="replyQ"/>
<property name="replyTimeout" value="600000"/>
</bean>
<bean class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
<constructor-arg ref="connectionFactory"/>
<property name="queues" ref="replyQ"/>
<property name="messageListener" ref="amqpTemplate"/>
</bean>
<rabbit:queue id="replyQ" name="my.reply.queue"/>
@Bean
publicRabbitTemplate amqpTemplate() {
RabbitTemplate rabbitTemplate = newRabbitTemplate(connectionFactory());
rabbitTemplate.setMessageConverter(msgConv());
rabbitTemplate.setReplyQueue(replyQueue());
rabbitTemplate.setReplyTimeout(60000);
returnrabbitTemplate;
}
@Bean
publicSimpleMessageListenerContainer replyListenerContainer() {
SimpleMessageListenerContainer container = newSimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setQueues(replyQueue());
container.setMessageListener(amqpTemplate());
returncontainer;
}
@Bean
publicQueue replyQueue() {
return newQueue("my.reply.queue");
}
使用AMQP进行Spring Remoting
Spring Framework具有通用的远程调用的能力,使用不同的传输协议。Spring-AMQP支持类似机制,在客户端使用AmqpProxyFactoryBean,在服务器端使用AmqpInvokerServiceExporter。这里通过AMQP提供RPC。在客户端RabbitTemplate像上面描述的那样使用。在服务器端,调用者接收消息,调用配置服务,使用接收到的replyTo信息返回应答。
客户端工厂可以注入到任何一个bean;这样客户端可以通过代理触发方法,最终通过AMQP获得远程调用的结果。
注意:
使用默认转换器MessageConverter,方法参数和返回结果必须是Serializable的实例。
在服务器端,AmqpInvokerServiceExporter拥有AmqpTemplate和MessageConverter属性。目前,模板的MessageConverter不会使用。如果你想使用定制的消息转换器,你必须使用messgeConverter属性进行设置。在客户端,定制的转换器可以通过AmqpTemplate的进行添加,AmqpTemplate通过AmqpProxyFactoryBean的amqpTemplate属性进行设置。
客户端与服务器端的最简配置如下:
<bean id="client"
class="org.springframework.amqp.remoting.client.AmqpProxyFactoryBean">
<property name="amqpTemplate" ref="template"/>
<property name="serviceInterface" value="foo.ServiceInterface"/>
</bean>
<rabbit:connection-factory id="connectionFactory"/>
<rabbit:template id="template" connection-factory="connectionFactory" reply-timeout="2000"
routing-key="remoting.binding" exchange="remoting.exchange"/>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue name="remoting.queue"/>
<rabbit:direct-exchange name="remoting.exchange">
<rabbit:bindings>
<rabbit:binding queue="remoting.queue" key="remoting.binding"/>
</rabbit:bindings>
</rabbit:direct-exchange>
<bean id="listener"
class="org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter">
<property name="serviceInterface" value="foo.ServiceInterface"/>
<property name="service" ref="service"/>
<property name="amqpTemplate" ref="template"/>
</bean>
<bean id="service" class="foo.ServiceImpl"/>
<rabbit:connection-factory id="connectionFactory"/>
<rabbit:template id="template" connection-factory="connectionFactory"/>
<rabbit:queue name="remoting.queue"/>
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener ref="listener" queue-names="remoting.queue"/>
</rabbit:listener-container>
注意:
AmqpInvokerServiceExporter只能处理一定格式的消息,例如AmqpProxyFactoryBean发来的消息。如果它发来的消息没有办法解析,一个RuntimeException将会被抛出。如果这个消息没有replyToAddress这个属性,这个消息将被拒绝,如果没有配置Dead
Letter Exchange它将丢失。