配置消息代理服务器-AmqpAdmin
AMQP规范描述了在消息代理中如何利用协议来配置队列,交换和绑定。这些操作可以使用org.springframework.amqp.core package中的AmqpAdmin接口来完成,这些操作从0.8版本规范到现在为止是通用的。
AmqpAdmin接口是基于Spring AMQP高度抽象的:
public interfaceAmqpAdmin {
// Exchange Operations
void declareExchange(Exchange exchange);
void deleteExchange(String exchangeName);
// Queue Operations
Queue declareQueue();
String declareQueue(Queue queue);
void deleteQueue(String queueName);
void deleteQueue(String queueName, booleanunused, booleanempty);
void purgeQueue(String queueName, booleannoWait);
// Binding Operations
void declareBinding(Binding binding);
void removeBinding(Binding binding);
Properties getQueueProperties(String queueName);
}
无参方法declareQueue()在代理商定义队列,这个队列的名称是自动生成的。自动生成的队列的附件属性还有exclusive=true,autoDelete=true,durable=false。
declareQueue(Queue queue)接收Queue对象作为参数,并且返回queue的名称。如果你想让代理生成名称,这很有帮助。这和AnonymousQueue行车对比,它将生成UUID名称,并且设置exclusive和autoDelete属性为true。如果提供的队列的名称是空的,那么代理在声明队列的时候会生成名称,并且将名称返回给调用者。Queue自身并没有变化。这个方法只能通过RabbitAdmin编码直接调用。它不支持在应用程序上下文中自动生成。使用<rabbit:queue/>name属性设置为空或者不设置会创建AnonymousQueue。这是因为如果连接失败,重新声明的名称将会不一样。所以声明的名称应该固定,因为它们会在上下文的任何地方使用,例如,监听器:
<rabbit:listener-container>
<rabbit:listener ref="listener" queue-names="#{someQueue.name}"/>
</rabbit:listener-container>
RabbitMQ对AmqpAdmin的实现是RabbitAdmin,使用XML配置如下:
<rabbit:connection-factory id="connectionFactory"/>
<rabbit:admin id="amqpAdmin" connection-factory="connectionFactory"/>
当CachingConnectionFactory的缓存模式是CHANNEL,RabbitAdmin的实现将自动迟声明在一个应用上下文的Queues,Exchanges,Bindings。这些组件将在打开与消息代理的连接时声明。有一些方便的命名空间,使得这些变得很方便:
<rabbit:queue id="tradeQueue"/>
<rabbit:queue id="marketDataQueue"/>
<fanout-exchange name="broadcast.responses"
xmlns="http://www.springframework.org/schema/rabbit">
<bindings>
<binding queue="tradeQueue"/>
</bindings>
</fanout-exchange>
<topic-exchange name="app.stock.marketdata"
xmlns="http://www.springframework.org/schema/rabbit">
<bindings>
<binding queue="marketDataQueue" pattern="${stocks.quote.pattern}"/>
</bindings>
</topic-exchange>
在上面的这个例子中,我们使用了匿名队列(实质上是框架帮我们生成队列的名称,而不是代理),通过引用来引用它们。当然我们可以在声明队列的时候指定队列的名称,当然这个名称同样作为引用这些bean的标识。例如:
<rabbit:queue name="stocks.trade.queue"/>
提示:
你可以同时提供id和name属性,这样你可以通过id引用这个队列。它同样允许一些标准的Spring特色,例如属性占位符或者SpEL作为Queue的name;这些属性在使用name作为标识符时候不可用。
队列还可以配置一些额外的参数,例如x-message-ttl,x-ha-policy。使用命名空间的支持它们可以通过使用<rabbit:queue-arguments>元素以键值对的方式提供:
<rabbit:queue name="withArguments">
<rabbit:queue-arguments>
<entry key="x-ha-policy" value="all"/>
</rabbit:queue-arguments>
</rabbit:queue>
在默认的情况下,参数类型默认为字符串。对于其他的类型,我们需要提供类型信息。
<rabbit:queue name="withArguments">
<rabbit:queue-arguments value-type="java.lang.Long">
<entry key="x-message-ttl" value="100"/>
</rabbit:queue-arguments>
</rabbit:queue>
对于混合类型,我们需要为每一个entry提供:
<rabbit:queue name="withArguments">
<rabbit:queue-arguments>
<entry key="x-message-ttl">
<value type="java.lang.Long">100</value>
</entry>
<entry key="x-ha-policy" value="all"/>
</rabbit:queue-arguments>
</rabbit:queue>
在Spring Framework3.2之后,它可以声明的更为简洁:
<rabbit:queue name="withArguments">
<rabbit:queue-arguments>
<entry key="x-message-ttl" value="100" value-type="java.lang.Long"/>
<entry key="x-ha-policy" value="all"/>
</rabbit:queue-arguments>
</rabbit:queue>
重点:
RabbitMQ代理不允许声明参数不一致的队列。例如,如果一个队列已经存在,没有time to live 参数,紧接着你又试图去使用下列参数去声明一个队列 key="x-message-ttl" value="100",将会有异常抛出。
默认的情况下,RabbitAdmin会立马停止处理所有的声明,如果有异常出现。这样将引起连锁反应,例如监听容器因为队列没有声明而导致初始化失败。
这个问题可以通过设置RabbitAdmin的ignore-declaration-failures为true来完成。这一设置将会导致RabbitAdmin记录异常,并且继续声明其他的元素。
自从1.3版本开始,可以配置HeaderExchange...(没明白它的作用)
<rabbit:headers-exchange name="headers-test">
<rabbit:bindings>
<rabbit:binding queue="bucket">
<rabbit:binding-arguments>
<entry key="foo" value="bar"/>
<entry key="baz" value="qux"/>
<entry key="x-match" value="all"/>
</rabbit:binding-arguments>
</rabbit:binding>
</rabbit:bindings>
</rabbit:headers-exchange>
如何通过Java来配置AMQP框架,看看下面的例子,使用@Configuration类AbstractStockRabbitConfiguration,它有RabbitClientConfiguration和RabbitServerConfiguration两个子类。
AbstractStockRabbitConfiguration的代码如下:
@Configuration
public abstract class AbstractStockAppRabbitConfiguration {
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory =
newCachingConnectionFactory("localhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
returnconnectionFactory;
}
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = newRabbitTemplate(connectionFactory());
template.setMessageConverter(jsonMessageConverter());
configureRabbitTemplate(template);
returntemplate;
}
@Bean
public MessageConverter jsonMessageConverter() {
return newJsonMessageConverter();
}
@Bean
public TopicExchange marketDataExchange() {
return newTopicExchange("app.stock.marketdata");
}
// additional code omitted for brevity
}
在这个例子中,Server的配置如下:
@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration {
@Bean
public Queue stockRequestQueue() {
return new Queue("app.stock.request");
}
}
这是@Configuration类继承链的最末端。最末端结果是当应用启动的时候TopicExchange和Queue将被声明。在Server的配置中没有绑定TopicExchange和Queue,这些将在客户端应用中做。这里队列被绑定到默认的Exchange,这种定义是规范中定义的。
客户端的配置稍微复杂一些:
@Configuration
public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration {
@Value("${stocks.quote.pattern}")
private String marketDataRoutingKey;
@Bean
public Queue marketDataQueue() {
return amqpAdmin().declareQueue();
}
/**
* Binds to the market data exchange. Interested in any stock quotes
* that match its routing key.
*/
@Bean
public Binding marketDataBinding() {
return BindingBuilder.bind(
marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
}
// additional code omitted for brevity
}
条件声明
默认情况下,queues,exchanges和bindings通过RabbitAdmin实例进行声明,RabbitAdmin实例随着应用上下文的启动创建。
注意:
自从1.2版本发以来,条件声明这些元素成为可能。当一个应用连接到多个代理的时候,这会特别有用,你需要指明你要声明元素的代理。
实现Declarable接口的类,代表着这些元素,它提供了两个方法:shouldDeclare()和getDeclaringAdmins()。RabbitAdmin使用这些方法来决定一个实例是否应该在当前连接上处理声明。
这些属性可以在命名空间中进行配置。
<rabbit:admin id="admin1" connection-factory="CF1"/>
<rabbit:admin id="admin2" connection-factory="CF2"/>
<rabbit:queue id="declaredByBothAdminsImplicitly"/>
<rabbit:queue id="declaredByBothAdmins" declared-by="admin1, admin2"/>
<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1"/>
<rabbit:queue id="notDeclaredByAny" auto-declare="false"/>
<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
<rabbit:bindings>
<rabbit:binding key="foo" queue="bar"/>
</rabbit:bindings>
</rabbit:direct-exchange>
注意,auto-declare属性默认情况下为true,如果declare-by属性没有提供,那么所有的RabbitAdmin都将声明这个实体。
同样,可以基于@Configuration进行配置。
@Bean
public RabbitAdmin admin() {
RabbitAdmin rabbitAdmin = newRabbitAdmin(cf1());
rabbitAdmin.afterPropertiesSet();
returnrabbitAdmin;
}
@Bean
public RabbitAdmin admin2() {
RabbitAdmin rabbitAdmin = newRabbitAdmin(cf2());
rabbitAdmin.afterPropertiesSet();
returnrabbitAdmin;
}
@Bean
public Queue queue() {
Queue queue = newQueue("foo");
queue.setAdminsThatShouldDeclare(admin());
returnqueue;
}
@Bean
public Exchange exchange() {
DirectExchange exchange = newDirectExchange("bar");
exchange.setAdminsThatShouldDeclare(admin());
returnexchange;
}
@Bean
public Binding binding() {
Binding binding = newBinding("foo", DestinationType.QUEUE, exchange().getName(), "foo",
null);
binding.setAdminsThatShouldDeclare(admin());
returnbinding;
}