上一篇我们介绍了如何使用Spring AMQP和RabbitMQ结合,开发消费者应用程序,使用的是Xml配置的Spring框架。
本篇我们仍然使用Spring AMQP开发生产者应用,不过我们使用零 XML配置的Spring Boot环境进行开发,使用的库是spring-boot-starter-amqp库。
使用Spring-boot-starter-amqp搭建框架
我们使用Spring Boot 1.4.3 RELEASE版本,pom.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>SpringMQProducer</artifactId>
<version>0.1.0</version>
<packaging>jar</packaging>
<name>SpringMQProducer</name>
<description>The MQ producer in spring boot environment</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
如果我们想定制自己的ConnectionFactory,RabbitTemplate等对象,建议在Configuration中将其禁用:@Configuration @ConditionalOnClass({ RabbitTemplate.class, Channel.class }) @EnableConfigurationProperties(RabbitProperties.class) @Import(RabbitAnnotationDrivenConfiguration.class) public class RabbitAutoConfiguration { @Configuration @ConditionalOnMissingBean(ConnectionFactory.class) protected static class RabbitConnectionFactoryCreator { @Bean public CachingConnectionFactory rabbitConnectionFactory(RabbitProperties config) { ........ @Configuration @Import(RabbitConnectionFactoryCreator.class) protected static class RabbitTemplateConfiguration { @Bean @ConditionalOnSingleCandidate(ConnectionFactory.class) @ConditionalOnMissingBean(RabbitTemplate.class) public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { ........ @Bean @ConditionalOnSingleCandidate(ConnectionFactory.class) @ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true) @ConditionalOnMissingBean(AmqpAdmin.class) public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) { return new RabbitAdmin(connectionFactory); } ....... @Configuration @ConditionalOnClass(RabbitMessagingTemplate.class) @ConditionalOnMissingBean(RabbitMessagingTemplate.class) @Import(RabbitTemplateConfiguration.class) protected static class MessagingTemplateConfiguration { @Bean @ConditionalOnSingleCandidate(RabbitTemplate.class) public RabbitMessagingTemplate rabbitMessagingTemplate( RabbitTemplate rabbitTemplate) { return new RabbitMessagingTemplate(rabbitTemplate); }
@Configuration
@ComponentScan("com.qf.rabbitmq")
@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
@SpringBootApplication
public class SpringMqProducerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMqProducerApplication.class, args);
}
}
在src/main/resources目录下创建application.properties文件,添加rabbitmq相关设置,以spring.rabbitmq为前缀,这样它会自动映射到RabbitProperties配置类
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=rabbitmq_producer
spring.rabbitmq.password=123456
spring.rabbitmq.virtualHost=test_vhosts
添加RabbitConfig类,用于创建自定义ConnectionFactory,RabbitAdmin,RabbitTemplate等。 这个类头部如下所示
@Configuration
@EnableConfigurationProperties(RabbitProperties.class)
public class RabbitConfig
{
@Autowired
private RabbitProperties rabbitProperties;
这里用到了org.springframework.boot.autoconfigure.amqp.RabbitProperties类,记录application.properties中的RabbitMQ连接设置。
再创建ConnectionFactory:
@Bean("connectionFactory")
public ConnectionFactory getConnectionFactory() {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory =
new com.rabbitmq.client.ConnectionFactory();
rabbitConnectionFactory.setHost(rabbitProperties.getHost());
rabbitConnectionFactory.setPort(rabbitProperties.getPort());
rabbitConnectionFactory.setUsername(rabbitProperties.getUsername());
rabbitConnectionFactory.setPassword(rabbitProperties.getPassword());
rabbitConnectionFactory.setVirtualHost(rabbitProperties.getVirtualHost());
ConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitConnectionFactory);
return connectionFactory;
}
这里可以像消费者应用里提到的那样,设置CacheMode和最大缓存数,我们这里暂不设置。 再创建RabbitAdmin对象
@Bean(name="rabbitAdmin")
public RabbitAdmin getRabbitAdmin()
{
RabbitAdmin rabbitAdmin = new RabbitAdmin(getConnectionFactory());
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
定义MessageConverter和MessagePropertiesConverter对象
@Bean(name="serializerMessageConverter")
public MessageConverter getMessageConverter(){
return new SimpleMessageConverter();
}
@Bean(name="messagePropertiesConverter")
public MessagePropertiesConverter getMessagePropertiesConverter()
{
return new DefaultMessagePropertiesConverter();
}
定义发送消息所用的RabbitTemplate对象,由于我们的场景要求发送之后立即从消费者处获得返回消息,因此我们在RabbitTemplate对象中设置了ReplyAddress,而且在下面的MessageListenerContainer中将这个对象作为Listener设置到Container中。
@Bean(name="rabbitTemplate")
public RabbitTemplate getRabbitTemplate()
{
RabbitTemplate rabbitTemplate = new RabbitTemplate(getConnectionFactory());
rabbitTemplate.setUseTemporaryReplyQueues(false);
rabbitTemplate.setMessageConverter(getMessageConverter());
rabbitTemplate.setMessagePropertiesConverter(getMessagePropertiesConverter());
rabbitTemplate.setReplyAddress(AppConstants.REPLY_QUEUE_NAME);
rabbitTemplate.setReceiveTimeout(60000);
return rabbitTemplate;
}
使用RabbitAdmin对象定义发送消息Exchange/Queue/Binding,返回消息Exchange/Queue/Binding对象,如果它们在RabbitMQ中已经存在,下面的定义代码可以省略.这里的Exchange/Queue名称和前面的消费者应用使用的相同。
@Bean(name="springMessageQueue")
public Queue createQueue(@Qualifier("rabbitAdmin")RabbitAdmin rabbitAdmin)
{
Queue sendQueue = new Queue(AppConstants.SEND_QUEUE_NAME,true,false,false);
rabbitAdmin.declareQueue(sendQueue);
return sendQueue;
}
@Bean(name="springMessageExchange")
public Exchange createExchange(@Qualifier("rabbitAdmin")RabbitAdmin rabbitAdmin)
{
DirectExchange sendExchange = new DirectExchange(AppConstants.SEND_EXCHANGE_NAME,true,false);
rabbitAdmin.declareExchange(sendExchange);
return sendExchange;
}
@Bean(name="springMessageBinding")
public Binding createMessageBinding(@Qualifier("rabbitAdmin")RabbitAdmin rabbitAdmin)
{
Map<String,Object> arguments = new HashMap<String,Object>();
Binding sendMessageBinding =
new Binding(AppConstants.SEND_QUEUE_NAME, Binding.DestinationType.QUEUE,
AppConstants.SEND_EXCHANGE_NAME, AppConstants.SEND_MESSAGE_KEY, arguments);
rabbitAdmin.declareBinding(sendMessageBinding);
return sendMessageBinding;
}
@Bean(name="springReplyMessageQueue")
public Queue createReplyQueue(@Qualifier("rabbitAdmin")RabbitAdmin rabbitAdmin)
{
Queue replyQueue = new Queue(AppConstants.REPLY_QUEUE_NAME,true,false,false);
rabbitAdmin.declareQueue(replyQueue);
return replyQueue;
}
@Bean(name="springReplyMessageExchange")
public Exchange createReplyExchange(@Qualifier("rabbitAdmin")RabbitAdmin rabbitAdmin)
{
DirectExchange replyExchange = new DirectExchange(AppConstants.REPLY_EXCHANGE_NAME,true,false);
rabbitAdmin.declareExchange(replyExchange);
return replyExchange;
}
@Bean(name="springReplyMessageBinding")
public Binding createReplyMessageBinding(@Qualifier("rabbitAdmin")RabbitAdmin rabbitAdmin)
{
Map<String,Object> arguments = new HashMap<String,Object>();
Binding replyMessageBinding =
new Binding(AppConstants.REPLY_QUEUE_NAME, Binding.DestinationType.QUEUE,
AppConstants.REPLY_EXCHANGE_NAME, AppConstants.REPLY_MESSAGE_KEY, arguments);
rabbitAdmin.declareBinding(replyMessageBinding);
return replyMessageBinding;
}
最后定义接收返回消息的Message Listener Container,这里的Listener属性设置的是上面创建的RabbitTemplate对象。
@Bean(name="replyMessageListenerContainer")
public SimpleMessageListenerContainer createReplyListenerContainer() {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
listenerContainer.setConnectionFactory(getConnectionFactory());
listenerContainer.setQueueNames(AppConstants.REPLY_QUEUE_NAME);
listenerContainer.setMessageConverter(getMessageConverter());
listenerContainer.setMessageListener(getRabbitTemplate());
listenerContainer.setRabbitAdmin(getRabbitAdmin());
listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
return listenerContainer;
}
我们还定义了一个ThreadPoolExecutor对象,用于RabbitTemplate异步执行,发送和接收消息使用。
@Bean(name="threadExecutor")
public ThreadPoolTaskExecutor createThreadPoolTaskExecutor()
{
ThreadPoolTaskExecutor threadPoolTaskExecutor =
new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(5);
threadPoolTaskExecutor.setMaxPoolSize(10);
threadPoolTaskExecutor.setQueueCapacity(200);
threadPoolTaskExecutor.setKeepAliveSeconds(20000);
return threadPoolTaskExecutor;
}
我们的生产者-消费者场景是系列1中提到的RPC方式计算阶乘,使用Rest接口调用生产者应用,生产者应用发送消息给消费者应用,计算出阶乘结果返回给生产者。
为了实现这个场景,我们先在生产者程序中定义用于发送请求消息和接收返回消息的服务接口SendMessageService和它的实现类SendMessageServiceImpl类。
public interface SendMessageService
{
String sendMessage(String message);
}
@Service("sendMessageService")
public class SendMessageServiceImpl implements SendMessageService
{
@Autowired
private ThreadPoolTaskExecutor executor;
public String sendMessage(String message)
{
CompletableFuture<String> resultCompletableFuture =
CompletableFuture.supplyAsync(new MQSenderSupplier(message), executor);
try
{
String result = resultCompletableFuture.get();
return result;
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (ExecutionException e)
{
e.printStackTrace();
}
return null;
}
}
在实现类中,我们将发送请求消息和接收返回消息交给一个Supplier对象(JDK 1.8引入)在后台线程池中异步执行,我们自定义了MQSenderSupplier类,实现了发送请求和接收返回消息的操作。
public class MQSenderSupplier implements Supplier<String> {
private String message;
public MQSenderSupplier(String message)
{
this.message = message;
}
public String get()
{
Date sendTime = new Date();
String correlationId = UUID.randomUUID().toString();
MessagePropertiesConverter messagePropertiesConverter =
(MessagePropertiesConverter) ApplicationContextUtil.getBean("messagePropertiesConverter");
RabbitTemplate rabbitTemplate =
(RabbitTemplate)ApplicationContextUtil.getBean("rabbitTemplate");
AMQP.Basic