rabbitmq 与 spring 整合,熟悉 RabbitAdmin、 RabbitTemplate、SimpleMessageListenerContainer、MessageListenerAdapter 和 MessageConverter 几个类(接口)的用法。
代码示例会使用 springboot 项目进行演示
pom 依赖如下:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- json 转换 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
RabbitAdmin
RabbitAdmin 可以用来创建交换机、队列、绑定关系等。当然,这些交换机、队列、绑定关系也可以通过配置文件使用容器进行创建。
首先创建一个主配置类,把 RabbitAdmin 注入容器中,然后就可以直接使用 RabbitAdmin 中的方法了
@Configuration
@ComponentScan({"com.xiao.*"})
public class RabbitmqConfig {
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
cachingConnectionFactory.setAddresses("192.168.0.125:5672");
cachingConnectionFactory.setUsername("guest");
cachingConnectionFactory.setPassword("guest");
cachingConnectionFactory.setVirtualHost("/");
return cachingConnectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
// 该方法的参数必须与上一个bean的方法名相同,否则在容器中无法找到该对象
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
// 必须设置为 true,否则容器不会加载 RabbitAdmin 类
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
}
在测试类中使用 RabbitAdmin 的功能
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqTest {
@Autowired
RabbitAdmin rabbitAdmin;
@Test
public void testRabbitAdmin() {
// 第一种绑定方式
rabbitAdmin.declareExchange(new TopicExchange("admin.topic.exchange", false, false));
rabbitAdmin.declareQueue(new Queue("admin.queue", false, false, false));
//rabbitAdmin.declareBinding(new Binding(destination, destinationType, exchange, routingKey, arguments));
rabbitAdmin.declareBinding(new Binding("admin.queue", Binding.DestinationType.QUEUE, "admin.topic.exchange", "test.#", null));
/*
// 第二种绑定方式
rabbitAdmin.declareBinding(BindingBuilder
.bind(new Queue("admin.queue",false,false,false))
.to(new TopicExchange("admin.topic.exchange", false, false))
.with("builder.#"));
*/
// 清空队列中的消息数据
//rabbitAdmin.purgeQueue(queueName, noWait);
//rabbitAdmin.purgeQueue("admin.queue", false);
}
}
我运行了几次,发现第一种方法可以直接创建交换机和队列,并建立绑定关系,但是第二种方法只能使用已经存在的交换机和队列,如果写不存在的交换机和队列名会报错。
在配置类中通过容器创建交换机、队列和绑定关系,只要在配置类中使用 @Bean 注解即可
@Bean
public TopicExchange exchange1() {
return new TopicExchange("topicExchange001", false, false);
}
@Bean
public Queue queue1() {
return new Queue("queue001", false);
}
@Bean
public Binding binding1() {
return BindingBuilder.bind(queue1()).to(exchange1()).with("bean.#");
}
执行上面的测试方法即可看到 topicExchange001 和 queue001 及绑定关系已经创建了
RabbitTemplate
- RabbitTemplate 与 springAMQP 整合是发送消息的关键类,可以设置消息的属性 MessageProperties
- 该类提供了丰富的发送消息方法,需注入到 spring 容器进行使用
首先需要在配置类中注入 RabbitTemplate 实例,同 RabbitAdmin
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
return rabbitTemplate;
}
测试用例
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void testRabbitTemplage() {
MessageProperties messageProperties = new MessageProperties();
messageProperties.getHeaders().put("hid", "head id");
messageProperties.getHeaders().put("hfun", "head 功能说明");
Message message = new Message("rabbitTemplage.convertAndSend 发送消息。。。 ".getBytes(), messageProperties);
//rabbitTemplate.convertAndSend(exchange, routingKey, message, messagePostProcessor);
rabbitTemplate.convertAndSend("topicExchange001", "bean.123", message, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
System.err.println("------------- 消息后置处理 -------------");
message.getMessageProperties().getHeaders().put("hfun", "head 后置处理后的功能说明 ");
message.getMessageProperties().getHeaders().put("htype", "head 后置处理新增的类型 ");
return message;
}
});
}
@Test
public void testRabbitTemplage2() {
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType("text/plain");
Message message = new Message("rabbitTemplage.send 发送 1 条消息。。。 ".getBytes(), messageProperties);
//rabbitTemplate.send(exchange, routingKey, message);
rabbitTemplate.send("topicExchange001", "bean.aaa", message);
//rabbitTemplate.convertAndSend(exchange, routingKey, object);
rabbitTemplate.convertAndSend("admin.topic.exchange", "test.111", "rabbitTemplate.convertAndSend 发送消息对象 ");
}
运行 testRabbitTemplage 测试方法后,去控制台中查看消息
testRabbitTemplage2 测试方法就是看看发送消息的其他方式
SimpleMessageListenerContainer
- 可以动态设置消费者配置项,如最大消费者、ack模式、消费者标签策略等(个人理解,类比成连接池)
- 可以监听(多个)队列、接收消息
@Bean
public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory);
//listenerContainer.setQueues(queue1()); // 设置要监听的队列
listenerContainer.setQueueNames("admin.queue","queue001"); // 会覆盖上面一行设置的监听队列
listenerContainer.setConcurrentConsumers(1); // 限制当前消费者数量
listenerContainer.setMaxConcurrentConsumers(3); // 限制最大消费者数量
listenerContainer.setDefaultRequeueRejected(false); // 不要重回队列
listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO); // 自动签收
listenerContainer.setConsumerTagStrategy(new ConsumerTagStrategy() { // 自定义消费者标签策略
@Override
public String createConsumerTag(String queue) {
return queue + "_"+ UUID.randomUUID().toString();
}
});
listenerContainer.setMessageListener(new ChannelAwareMessageListener() { // 消息监听
@Override
public void onMessage(Message message, Channel channel) throws Exception {
String msg = new String(message.getBody());
System.err.println("---------MessageListener 接收消息: " + msg);
}
});
return listenerContainer;
}
由于执行了上面两个发送消息的测试用例,现在"admin.queue","queue001"这两个队列是一共有 3 条消息,运行主入口类,可以看到 3 条消息都已被监听,且自动签收。
MessageListenerAdapter
- 不采用其提供的消息监听,可以自定义适配器,适配自己编写的接收消息处理类。默认方法 handleMessage(byte[] messagebody)
- 可以指定为其他方法,
- 可以通过转换器改变参数类型
- 可以给队列绑定方法
MessageConverter
- 该类提供多种转换器
- 可自定义常用转换器,一般都会实现这个接口
由于适配器和转换器都是添加到监听容器的,代码示例就直接放一起了
首先创建一个 适配器 类
public class MessageAdapter {
public void handleMessage(byte[] msgBody) {
System.out.println("默认方法:消息内容——>" + new String(msgBody));
}
public void modifiedMessageMethod(byte[] msgBody) {
System.out.println("字节数组方法:消息内容——>" + new String(msgBody));
}
public void modifiedMessageMethod(String msgBody) {
System.out.println("字符串方法:消息内容——>" + msgBody);
}
public void queueMethod(String msgBody) {
System.out.println("队列名匹配方法:消息内容——>" + msgBody);
}
public void queueMethod2(String msgBody) {
System.out.println("队列名匹配方法2:消息内容——>" + msgBody);
}
public void modifiedMessageMethod(Map msgBody) {
System.out.println("map转换方法:消息内容——>" + msgBody);
}
public void modifiedMessageMethod(Student stu) {
System.out.println("Student 对象:消息内容——> sid==" + stu.getSid() + ", sname==" + stu.getSname());
}
public void modifiedMessageMethod(Address addr) {
System.out.println("Address 对象:消息内容——> province==" + addr.getProvince() + ", city==" + addr.getCity());
}
public void modifiedMessageMethod(File file) {
System.out.println("文件对象:消息内容——>" + file.getName());
}
}
另外用到了两个实体类,代码就不贴了。(注意:实体类须实现序列化,并且要有无参构造方法)
下面是若干自定义转换器类
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
public class TextMessageConverter implements MessageConverter {
@Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
return new Message(object.toString().getBytes(), messageProperties);
}
@Override
public Object fromMessage(Message message) throws MessageConversionException {
String contentType = message.getMessageProperties().getContentType();
if(contentType != null && contentType.contains("text")) {
return new String(message.getBody());
}
return message.getBody();
}
}
public class ImageMessageConverter implements MessageConverter {
@Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
throw new MessageConversionException("converter error !");
}
@Override
public Object fromMessage(Message message) throws MessageConversionException {
System.err.println("--------------Image Converter--------------");
Object extName = message.getMessageProperties().getHeaders().get("extName");
String imgExtName = extName == null? "jpg": extName.toString();
byte[] msgBody = message.getBody();
String fileName = UUID.randomUUID().toString();
String path = "e:/testFile/" + fileName + "." + imgExtName;
File file = new File(path);
try {
Files.copy(new ByteArrayInputStream(msgBody), file.toPath());
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
}
public class PDFMessageConverter implements MessageConverter {
@Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
throw new MessageConversionException("converter error !");
}
@Override
public Object fromMessage(Message message) throws MessageConversionException {
System.err.println("--------------PDF Converter--------------");
byte[] msgBody = message.getBody();
String fileName = UUID.randomUUID().toString();
String path = "e:/testFile/" + fileName + ".pdf";
File file = new File(path);
try {
Files.copy(new ByteArrayInputStream(msgBody), file.toPath());
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
}
主配置类中把适配器和转换器添加到监听器中
@Bean
public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory);
//listenerContainer.setQueues(queue1()); // 设置要监听的队列
listenerContainer.setQueueNames("admin.queue","queue001"); // 会覆盖上面一行设置的监听队列
listenerContainer.setConcurrentConsumers(1); // 限制当前消费者数量
listenerContainer.setMaxConcurrentConsumers(3); // 限制最大消费者数量
listenerContainer.setDefaultRequeueRejected(false); // 不要重回队列
listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO); // 自动签收
listenerContainer.setConsumerTagStrategy(new ConsumerTagStrategy() { // 自定义消费者标签策略
@Override
public String createConsumerTag(String queue) {
return queue + "_"+ UUID.randomUUID().toString();
}
});
/*
listenerContainer.setMessageListener(new ChannelAwareMessageListener() { // 消息监听
@Override
public void onMessage(Message message, Channel channel) throws Exception {
String msg = new String(message.getBody());
System.err.println("---------MessageListener 接收消息: " + msg);
}
});
*/
/**
* 1. 适配方式:有字节默认的方法名 (handleMessage)
* 可指定为自定义的方法名 (modifiedMessageMethod)
* 可添加转换器,将字节数组转为字符串
*/
/* MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
//使用默认的方法则注释下面两行即可
messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
messageAdapter.setMessageConverter(new TextMessageConverter());
listenerContainer.setMessageListener(messageAdapter);
*/
/**
* 2. 适配方式:将队列名称与方法名匹配
* 多个队列可以匹配同一个方法名
*/
/* MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
Map<String, String> queueOrTagToMethodName = new HashMap<String, String>();
queueOrTagToMethodName.put("queue001", "queueMethod");
queueOrTagToMethodName.put("admin.queue", "queueMethod");
messageAdapter.setQueueOrTagToMethodName(queueOrTagToMethodName);
messageAdapter.setMessageConverter(new TextMessageConverter());
listenerContainer.setMessageListener(messageAdapter);
*/
// json 格式转换
/* MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
messageAdapter.setMessageConverter(jsonMessageConverter);
listenerContainer.setMessageListener(messageAdapter);
*/
// java 对象转换
/* MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
// 在SpringBoot升级后,之前的系统内使用实体传输受到了限制,如果使用SpringBoot默认的序列化方式不会出现信任package的问题,
// 之所以出现这个问题是因为项目使用fastjson方式进行类的序列化已经反序列化,在之前SpringBoot 1.5.10版本的时候 RabbitMQ依赖内
// 的DefaultClassMapper类在构造函数内配置*,表示信任项目内的所有package,在SpringBoot 2.0.0版本时,
// DefaultClassMapper类源码构造函数进行了修改,不再信任全部package而是仅仅信任java.util、java.lang。
//SpringBoot2.0新特性 - RabbitMQ信任package设置
javaTypeMapper.addTrustedPackages("com.xiao.entity");
jsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
messageAdapter.setMessageConverter(jsonMessageConverter);
listenerContainer.setMessageListener(messageAdapter);
*/
// java 对象多映射转换
MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
Map<String, Class<?>> javaObjMap = new HashMap<String, Class<?>>();
javaObjMap.put("student", Student.class);
javaObjMap.put("address", Address.class);
javaTypeMapper.setIdClassMapping(javaObjMap);
javaTypeMapper.addTrustedPackages("com.xiao.entity");
jsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
messageAdapter.setMessageConverter(jsonMessageConverter);
listenerContainer.setMessageListener(messageAdapter);
/*
// 全局转换器
MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
ContentTypeDelegatingMessageConverter converter = new ContentTypeDelegatingMessageConverter();
TextMessageConverter messageConverter = new TextMessageConverter();
converter.addDelegate("text", messageConverter);
converter.addDelegate("html/text", messageConverter);
converter.addDelegate("xml/text", messageConverter);
converter.addDelegate("text/plain", messageConverter);
Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
converter.addDelegate("json", jsonMessageConverter);
converter.addDelegate("application/json", jsonMessageConverter);
ImageMessageConverter imageMessageConverter = new ImageMessageConverter();
converter.addDelegate("image", imageMessageConverter);
converter.addDelegate("image/jpg", imageMessageConverter);
PDFMessageConverter pdfMessageConverter = new PDFMessageConverter();
converter.addDelegate("application/pdf", pdfMessageConverter);
messageAdapter.setMessageConverter(converter);
listenerContainer.setMessageListener(messageAdapter);
*/
return listenerContainer;
}
测试用例:
@Test
public void testMessage4Text() {
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType("text/plain");
Message message = new Message("一条 text 消息。。。 ".getBytes(), messageProperties);
rabbitTemplate.send("topicExchange001", "bean.aaa", message);
}
@Test
public void testSendJsonMessage() throws Exception {
Student stu = new Student("001", "张三");
ObjectMapper objectMapper = new ObjectMapper();
String stuString = objectMapper.writeValueAsString(stu);
System.err.println("stu 2 json :" + stuString);
MessageProperties messageProperties = new MessageProperties();
// 务必设置 ContentType 为 json 类型
messageProperties.setContentType("application/json");
Message message = new Message(stuString.getBytes(), messageProperties);
rabbitTemplate.send("topicExchange001", "bean.aaa", message);
}
@Test
public void testSendJavaMessage() throws Exception {
Student stu = new Student("002", "李四");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(stu);
System.err.println("stu 2 json :" + json);
MessageProperties messageProperties = new MessageProperties();
// 务必设置 ContentType 为 json 类型
messageProperties.setContentType("application/json");
// 键为固定值,两边分别是两个下划线;值是 java 对象的全路径
messageProperties.getHeaders().put("__TypeId__", "com.xiao.entity.Student");
Message message = new Message(json.getBytes(), messageProperties);
rabbitTemplate.send("topicExchange001", "bean.aaa", message);
}
@Test
public void testSendMappingJavaMessage() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
Student stu = new Student("003", "王五");
String stuJson = objectMapper.writeValueAsString(stu);
System.err.println("stu 2 json :" + stuJson);
MessageProperties messageProperties = new MessageProperties();
// 务必设置 ContentType 为 json 类型
messageProperties.setContentType("application/json");
messageProperties.getHeaders().put("__TypeId__", "student");
Message message = new Message(stuJson.getBytes(), messageProperties);
rabbitTemplate.send("topicExchange001", "bean.aaa", message);
Address addr = new Address("北京", "丰台");
String addrJson = objectMapper.writeValueAsString(addr);
System.err.println("addr 2 json :" + addrJson);
MessageProperties messageProperties2 = new MessageProperties();
// 务必设置 ContentType 为 json 类型
messageProperties2.setContentType("application/json");
messageProperties2.getHeaders().put("__TypeId__", "address");
Message message2 = new Message(addrJson.getBytes(), messageProperties2);
rabbitTemplate.send("topicExchange001", "bean.aaa", message2);
}
@Test
public void testSendExtMessage() throws Exception {
byte[] msgBody = Files.readAllBytes(Paths.get("e:/smallicon/money.jpg"));
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType("image/jpg");
messageProperties.getHeaders().put("extName", "jpg");
Message message = new Message(msgBody, messageProperties);
rabbitTemplate.send("topicExchange001", "bean.aaa", message);
/*
byte[] msgBody = Files.readAllBytes(Paths.get("e:/test.pdf"));
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType("application/pdf");
Message message = new Message(msgBody, messageProperties);
rabbitTemplate.send("topicExchange001", "bean.aaa", message);
*/
}
下面是一些效果截图