前一篇中我們介紹了使用RabbitMQ Java Client訪問RabbitMQ的方法。但是使用這種方式訪問RabbitMQ,開發人員在程式中需要自己管理Connection,Channel對象,Consumer對象的建立,銷毀,這樣會非常不方便。我們下面介紹使用Spring AMQP串連RabbitMQ,進行訊息的接收和發送。
Spring AMQP是一個Spring子項目,它提供了訪問基於AMQP協議的Message Service器的解決方案。它包含兩部分,spring-ampq是基於AMQP協議的訊息發送和接收的高層實現,spring-rabbit是基於RabbitMQ的具體實現。這兩部分我們下面都會使用到。
Spring-AMQP中的基礎類/介面 spring-amqp中定義了幾個基礎類/介面,Message,Exchange,Queue,Binding
Message
public class Message implements Serializable { private final MessageProperties messageProperties; private final byte[] body;
spring-amqp中的Message類類似於javax的Message類,封裝了訊息的Properties和訊息體。
Exchange spring-amqp定義了Exchange介面
public interface Exchange extends Declarable { //Exchange名稱String getName(); //Exchange的類型String getType(); //Exchange是否持久化boolean isDurable(); //Exchange不再被使用時(沒有任何綁定的情況下),是否由RabbitMQ自動刪除boolean isAutoDelete(); //Exchange相關的參數Map<String, Object> getArguments();
這個介面和RabbitMQ Client中的Exchange類相似。 spring-amqp中的Exchange繼承關係如下圖所示
AbstractExchange類是所有Exchange類的父類,實現Exchange介面的具體方法。 CustomExchange針對使用者自訂的Exchange對象。其他四個Exchange類,分別對應四種Exchange。 我們在Spring設定檔中配置Exchange對象時,使用的就是這幾種Exchange類。
Queue spring-amqp定義了Queue類,和RabbitMQ Client中的Queue相似,對應RabbitMQ中的訊息佇列。
public class Queue extends AbstractDeclarable { private final String name; private final boolean durable; private final boolean exclusive; private final boolean autoDelete; private final java.util.Map<java.lang.String, java.lang.Object> arguments; public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete) {this(name, durable, exclusive, autoDelete, null);}
Binding Binding類是對RabbitMQ中Exchange-Exchange以及Exchange-Queue綁定關係的抽象。
public class Binding extends AbstractDeclarable { public enum DestinationType {QUEUE, EXCHANGE;} private final String destination; private final String exchange; private final String routingKey; private final Map<String, Object> arguments; private final DestinationType destinationType; public Binding(String destination, DestinationType destinationType, String exchange, String routingKey,Map<String, Object> arguments) {this.destination = destination;this.destinationType = destinationType;this.exchange = exchange;this.routingKey = routingKey;this.arguments = arguments;}
對照RabbitMQ Java Client中Channel介面的queueBind和ExchangeBind方法
Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map<String, Object> arguments) Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map<String, Object> arguments)
我們可以看出Binding類實際是對底層建立的Exchange-Queue和Exchange-Exchange綁定關係的高層抽象記錄類,它使用枚舉類型DestinationType區分Exchange-Queue和Exchange-Exchange兩種綁定。
Spring AMQP搭建消費者應用 消費者應用程式架構搭建 我們接下來使用spring-amqp搭建一個RabbitMQ的消費者Web應用,我們先建立一個maven webapp應用程式,再添加一個dependency。
<dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>1.6.5.RELEASE</version> </dependency>
spring-rabbit庫的引入是為了使用它裡面的RabbitAdmin類,建立Exchange,Queue和Binding對象,在匯入這個庫的時候同時引入了 spring-ampq和rabbitmq-client的庫,不需要另行匯入。
在src/main/resources目錄下建立application.properties檔案,用於記錄RabbitMQ的配置資訊。
mq.ip=localhostmq.port=5672mq.userName=rabbitmq_consumermq.password=123456mq.virutalHost=test_vhosts
在src/main/resource目錄下建立applicationContext.xml檔案:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd" > <context:annotation-config/> <context:property-placeholder ignore-unresolvable="true" location="classpath*:/application.properties" /> <!--從RabbitMQ Java Client建立RabbitMQ串連工廠對象--> <bean id="rabbitMQConnectionFactory" class="com.rabbitmq.client.ConnectionFactory"> <property name="username" value="${mq.userName}" /> <property name="password" value="${mq.password}" /> <property name="host" value="${mq.ip}" /> <property name="port" value="${mq.port}" /> <property name="virtualHost" value="${mq.virutalHost}" /> </bean> <!--基於RabbitMQ串連工廠對象構建spring-rabbit的串連工廠對象Wrapper--> <bean id="connectionFactory" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory"> <constructor-arg name="rabbitConnectionFactory" ref="rabbitMQConnectionFactory" /> </bean> <!--構建RabbitAmdin對象,它負責建立Queue/Exchange/Bind對象--> <bean id="rabbitAdmin" class="org.springframework.amqp.rabbit.core.RabbitAdmin"> <constructor-arg name="connectionFactory" ref="connectionFactory" /> <property name="autoStartup" value="true"></property> </bean> <!--構建Rabbit Template對象,用於發送RabbitMQ訊息,本程式使用它發送返回訊息--> <bean id="rabbitTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate"> <constructor-arg name="connectionFactory" ref="connectionFactory" /> </bean> <!--RabbitMQ訊息轉化器,用於將RabbitMQ訊息轉換為AMQP訊息,我們這裡使用基本的Message Converter --> <bean id="serializerMessageConverter" class="org.springframework.amqp.support.converter.SimpleMessageConverter" /> <!--Message Properties轉換器,用於在spring-amqp Message對象中的Message Properties和RabbitMQ的 Message Properties對象之間互相轉換 --> <bean id="messagePropertiesConverter" class="org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter" /> <!--定義AMQP Queue--> <bean id="springMessageQueue" class="org.springframework.amqp.core.Queue"> <constructor-arg name="name" value="springMessageQueue" /> <constructor-arg name="autoDelete" value="false" /> <constructor-arg name="durable" value="true" /> <constructor-arg name="exclusive" value="false" /> <!--定義AMQP Queue建立所需的RabbitAdmin對象--> <property name="adminsThatShouldDeclare" ref="rabbitAdmin" /> <!--判斷是否需要在串連RabbitMQ後建立Queue--> <property name="shouldDeclare" value="true" /> </bean> <!--定義AMQP Exchange--> <bean id="springMessageExchange" class="org.springframework.amqp.core.DirectExchange"> <constructor-arg name="name" value="springMessageExchange" /> <constructor-arg name="durable" value="true" /> <constructor-arg name="autoDelete" value="false" /> <!--定義AMQP Queue建立所需的RabbitAdmin對象--> <property name="adminsThatShouldDeclare" ref="rabbitAdmin" /> <!--判斷是否需要在串連RabbitMQ後建立Exchange--> <property name="shouldDeclare" value="true" /> </bean> <util:map id="emptyMap" map-class="java.util.HashMap" /> <!--建立Exchange和Queue之間的Bind--> <bean id="springMessageBind" class="org.springframework.amqp.core.Binding"> <constructor-arg name="destination" value="springMessageQueue" /> <constructor-arg name="destinationType" value="QUEUE" /> <constructor-arg name="exchange" value="springMessageExchange" /> <constructor-arg name="routingKey" value="springMessage" /> <constructor-arg name="arguments" ref="emptyMap" /> </bean> <!--偵聽springMessageQueue隊列訊息的Message Listener--> <bean id="consumerListener" class="com.qf.rabbitmq.listener.RabbitMQConsumer" /> <!--建立偵聽springMessageQueue隊列的Message Listener Container--> <bean id="messageListenerContainer" class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer"> <property name="messageConverter" ref="serializerMessageConverter" /> <property name="connectionFactory" ref="connectionFactory" /> <property name="messageListener" ref="consumerListener" /> <property name="queues" ref="springMessageQueue" /> <!--設定訊息確認方式為自動確認--> <property name="acknowledgeMode" value="AUTO" /> </bean></beans>
我們定義了偵聽訊息佇列的Message Listener類RabbitMQConsumer
public class RabbitMQConsumer implements MessageListener{ @Autowired private MessagePropertiesConverter messagePropertiesConverter; @Override public void onMessage(Message message) { try { //spring-amqp Message對象中的Message Properties屬性 MessageProperties messageProperties = message.getMessageProperties(); //使用Message Converter將spring-amqp Message對象中的Message Properties屬性 //轉換為RabbitMQ 的Message Properties對象 AMQP.BasicProperties rabbitMQProperties = messagePropertiesConverter.fromMessageProperties(messageProperties, "UTF-8"); System.out.println("The message's correlationId is:" + rabbitMQProperties.getCorrelationId()); String messageContent = null; messageContent = new String(message.getBody(),"UTF-8"); System.out.println("The message content is:" + messageContent); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }}
上面的Listener類是實現了MessageListener介面的類,當容器接收到訊息後,會自動觸發onMessage方法。 如果我們想使用普通的POJO類作為Message Listener,需要引入org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter類
public class MessageListenerAdapter extends AbstractAdaptableMessageListener { public MessageListenerAdapter(Object delegate) {doSetDelegate(delegate);}}
這裡的delegate對象就是我們的POJO對象。 假設我們定義一個Delegate類ConsumerDelegate
public class ConsumerDelegate{ public void processMessage(Object message) { //這裡接收的訊息對象僅是訊息體,不包含MessageProperties //如果想擷取帶MessageProperties的訊息對象,需要在Adpater中 //定義MessageConverter屬性。 String messageContent = message.toString(); System.out.println(messageContent); }}
在applicationContext.xml中定義Adapter對象,引用我們的Delegate對象。
<bean id="consumerDelegate" class="com.qf.rabbitmq.listener.ConsumerDelegate" /> <bean id="consumerListenerAdapter" class="org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter"> <property name="delegate" ref="consumerDelegate" /> <!--指定delegate處理訊息的預設方法 --> <property name="defaultListenerMethod" value="processMessage" /> </bean>
最後將Message Listener Container中的Message Listener指向Adapter對象。
<bean id="messageListenerContainer" class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer"> <property name="messageConverter" ref="serializerMessageConverter" /> <property name="connectionFactory" ref="connectionFactory" /> <!--設定Message Listener為Adapter對象 --> <property name="messageListener" ref="consumerListenerAdapter"/> <property name="queues" ref="springMessageQueue" /> <property name="acknowledgeMode" value="AUTO" /> </bean>
啟動Web應用後,我們從開機記錄資訊可以看出應用串連上了RabbitMQ伺服器
從RabbitMQ的管理介面(用rabbitmq_consumer使用者登入)可以看到springMessageExchange和springMessageQueue已經建立,綁定關係也已經建立。
Consumer Tag自訂 串連springMessageQueue的消費者Tag是RabbitMQ隨機產生的Tag名
如果我們想設定消費者Tag為指定Tag,我們可以在Message Listener Container中 設定自訂consumer tag strategy。首先我們需要定義一個Consumer Tag Strategy類,它實現了ConsumerTagStrategy介面。
public class CustomConsumerTagStrategy implements ConsumerTagStrategy{ @Override public String createConsumerTag(String queue) { String consumerName = "Consumer1"; return consumerName + "_" + queue; }}
在applicationContext.xml中設定自訂ConsumerTagStrategy
<bean id="consumerTagStrategy" class="com.qf.rabbitmq.strategy.CustomConsumerTagStrategy" /> <!--建立偵聽springMessageQueue隊列的Message Listener Container--> <bean id="messageListenerContainer" class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer"> <property name="messageConverter" ref="serializerMessageConverter" /> <property name="connectionFactory" ref="connectionFactory" /> <property name="messageListener" ref="consumerListener" /> <property name="queues" ref="springMessageQueue" /> <property name="acknowledgeMode" value="AUTO" /> <property name="consumerTagStrategy" ref="consumerTagStrategy" /> </bean>
再次啟動Web應用,查看RabbitMQ管理介面,我們可以看到Consumer Tag已經變成“Consumer1_springMessageQueue”,正如我們在CustomConsumerTagStrategy中設定的那樣。
消費者應用接收訊息驗證 我們編寫了一個生產者程式,向springMessageExchange發送訊息。 生產者的主要代碼如下,由於Exchange,Queue,Bind已經由消費者Web應用建立,因此生產者程式不再建立。
ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");factory.setPort(5672);factory.setUsername("rabbitmq_producer");factory.setPassword("123456");factory.setVirtualHost("test_vhosts"); //建立與RabbitMQ伺服器的TCP串連connection = factory.newConnection();channel = connection.createChannel(); String message = "First Web RabbitMQ Message"; String correlationId = UUID.randomUUID().toString();AMQP.BasicProperties props = new AMQP.BasicProperties .Builder() .correlationId(correlationId) .build(); channel.basicPublish("springMessageExchange","springMessage", props, message.getBytes());
啟動消費者Web應用,從控制台輸出資訊可以看到消費者接收到了生產者發送的訊息。
設定訊息手動確認模式
到目前為止,消費者端的Web應用對訊息的確認是自動確認模式,如果我們想改為手動確認方式,需要做以下兩點改動:
1)修改applicationContext.xml檔案中Message Listener Container的acknowledgeMode屬性的值為MANUAL。
<bean id="messageListenerContainer" class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer"> ...... <property name="acknowledgeMode" value="MANUAL" /> </bean>
2)將自訂的Message Listener類從實現org.springframework.amqp.core.MessageListener介面,改為實現 org.springframework.amqp.rabbit.core.ChannelAwareMessageListener介面,實現它的 onMessage(Message,Channel)方法。
public class RabbitMQConsumer implements ChannelAwareMessageListener{ ........... @Override public void onMessage(Message message, Channel channel) { try { //spring-amqp Message對象中的Message Properties屬性 MessageProperties messageProperties = message.getMessageProperties(); //使用Message Converter將spring-amqp Message對象中的Message Properties屬性 //轉換為RabbitMQ 的Message Properties對象 AMQP.BasicProperties rabbitMQProperties = messagePropertiesConverter.fromMessageProperties(messageProperties, "UTF-8"); System.out.println("The message's correlationId is:" + rabbitMQProperties.getCorrelationId()); String messageContent = null; messageContent = new String(message.getBody(),"UTF-8"); System.out.print