ActiveMQ
安装
linux下运行 bin 文件夹中的 activemq
windows系统下可以直接下载解压后运行 bin 文件夹中 activemq.bat 文件运行
docker
61616是容器端口,8161是管理页面端口,默认用户名密码
admin
创建项目
配置文件
# activemq连接信息
spring.activemq.broker-url=tcp://192.168.189.101:61616
# 信任所有包
spring.activemq.packages.trust-all=true
# 用户名
spring.activemq.user=admin
# 密码
spring.activemq.password=admin
启动类中先定义一个消息队列
import javax.jms.Queue;
@SpringBootApplication
public class SpringbootJmsActivemqApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootJmsActivemqApplication.class, args);
}
// 定义一个消息多列,这里可以看出Queue是一个jms定义的规范,这里只是用 ActiveMQQueue 实现
// 了这个规范,可以对比JDBC去理解
@Bean
Queue queue() {
return new ActiveMQQueue("southyin-queue");
}
}
项目结构
public class Message implements Serializable {
private String content;
private Date date;
// getter,setter,toString
}
import javax.jms.Queue;
@Component
public class JmsComponent {
@Autowired
JmsMessagingTemplate template;
@Autowired
Queue queue;
public void send(Message message) {
// 第一个参数是目的地(即队列),第二个参数是消息内容
template.convertAndSend(queue,message);
}
/**
* @JmsListener 表示这是一个接收消息的方法
* destination = "southyin-queue" 表示要接收 southyin-queue 队列的消息
* @param message
*/
@JmsListener(destination = "southyin-queue")
public void receive(Message message) {
System.out.println("message = " + message);
}
}
实际项目中,发送方法和接收方法应该分别在两个项目中,这里为了方便写在了一起
测试
@SpringBootTest
class SpringbootJmsActivemqApplicationTests {
@Autowired
JmsComponent jmsComponent;
@Test
void contextLoads() {
Message msg = new Message();
msg.setContent("southyin activemq");
msg.setDate(new Date());
jmsComponent.send(msg);
}
}
OK,小伙伴发现是不是非常的easy,但是activemq功能比较单一,都是点对点,点对面的形式,后续我们介绍形式更加丰富的RabbitMQ
RabbitMQ
安装
rabbitmq安装的坑非常多!需要 erlang 环境,并且版本不清晰,很容易出现版本不兼容的问题,学习阶段非常不建议把时间浪费这些方面,所以我们采取docker
可以到 Docker hub 上查找 容器
docker 安装命令
docker run -d --hostname my-rabbit --name myrabbit -p 5672:5672 -p 15672:15672 rabbitmq:3-management
启动成功后浏览器访问 ip:15672 ,用户名密码默认 guest 即可登录管理页面
创建项目
配置文件
# rabbitmq连接参数
spring.rabbitmq.host=192.168.189.101
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
在rabbitmq中有一个交换机的概念,所有生产的消息都交给它,由交换机根据不同策略分发到不同的队列当中,在rabbitmq中一共有4种策略
Direct模式
项目结构
package org.southyin.springbootamqprabbitmq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectConfig {
@Bean
Queue queue() {
return new Queue("southyin-queue");
}
// 注意!如果使用的是direct模式,以下2个bean是可以省略的,与activemq很类似
@Bean
DirectExchange directExchange() {
// 第一个参数是 交换机名字 自定义
// 第二个参数是 重启后是否依然有效
// 第三个参数是 长期未使用是是否删除
return new DirectExchange("southyin-direct",true,false);
}
// 粘合器:将上面的 队列 和 交换机 绑定到一起,with里的参数是routingKey,在topic模式中有用
@Bean
Binding binding() {
return BindingBuilder.bind(queue()).to(directExchange()).with("direct");
}
}
package org.southyin.springbootamqprabbitmq.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DirectRecevier {
@RabbitListener(queues = "southyin-queue")
public void handler(String msg) {
System.out.println("msg = " + msg);
}
}
启动项目后,用测试用例测试
@SpringBootTest
class SpringbootAmqpRabbitmqApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend("southyin-queue","hello rabbit");
}
}
Fanout模式
项目结构
package org.southyin.springbootamqprabbitmq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutConfig {
@Bean
Queue queueOne() {
return new Queue("queueOne");
}
@Bean
Queue queueTwo() {
return new Queue("queueTwo");
}
// routingKey没有用,topic模式中有用,所以这里没指定
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("southyin-fanout",true,false);
}
// 绑定 queueOne 到 FanoutExchange
@Bean
Binding bindingOne() {
return BindingBuilder.bind(queueOne()).to(fanoutExchange());
}
// 绑定 queueTwo 到 FanoutExchange
@Bean
Binding bindingTwo() {
return BindingBuilder.bind(queueTwo()).to(fanoutExchange());
}
}
package org.southyin.springbootamqprabbitmq.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class FanoutRecevier {
@RabbitListener(queues = "queueOne")
public void handler1(String msg) {
System.out.println("handler1 = " + msg);
}
@RabbitListener(queues = "queueTwo")
public void handler2(String msg) {
System.out.println("handler2 = " + msg);
}
}
启动项目后,测试用例测试
package org.southyin.springbootamqprabbitmq;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootAmqpRabbitmqApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
// 第一个参数是 交换机 的名字,交换机上现在维护着 2个 队列,所以两个 Listener 都会收到消息
// 第二个参数routingkey没有,在topic模式中有用
@Test
void contextLoads2() {
rabbitTemplate.convertAndSend("southyin-fanout",null,"hello rabbit fanout");
}
}
topic模式
这个交换机稍微复杂,但更灵活
项目结构
package org.southyin.springbootamqprabbitmq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TopicConfig {
@Bean
Queue xiaomi() {
return new Queue("xiaomi");
}
@Bean
Queue huawei() {
return new Queue("huawei");
}
@Bean
Queue phone() {
return new Queue("phone");
}
@Bean
TopicExchange topicExchange() {
return new TopicExchange("southyin-topic",true,false);
}
// routingKey作用类似标签,可以感兴趣的标签进行消息的订阅
// BindingBuilder.bind(xiaomi()).to(topicExchange()).with("xiaomi.#")
// 表示 xiaomi 队列和 topicExchange 进行绑定,对应以 xiaomi. 开头的消息
@Bean
Binding bindingXiaomi() {
return BindingBuilder.bind(xiaomi()).to(topicExchange()).with("xiaomi.#");
}
@Bean
Binding bindingHuawei() {
return BindingBuilder.bind(huawei()).to(topicExchange()).with("huawei.#");
}
@Bean
Binding bindingPhone() {
return BindingBuilder.bind(phone()).to(topicExchange()).with("#.phone.#");
}
}
package org.southyin.springbootamqprabbitmq.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class TopicRecevier {
@RabbitListener(queues = "phone")
public void phRecevier(String msg) {
System.out.println("phone msg = " + msg);
}
@RabbitListener(queues = "xiaomi")
public void xmRecevier(String msg) {
System.out.println("xiaomi msg = " + msg);
}
@RabbitListener(queues = "huawei")
public void hwRecevier(String msg) {
System.out.println("huawei msg = " + msg);
}
}
分别测试
@Test
void contextLoads3() {
rabbitTemplate.convertAndSend("southyin-topic","xiaomi.news","hello xiaomi.news");
rabbitTemplate.convertAndSend("southyin-topic","huawei.news","hello huawei.news");
rabbitTemplate.convertAndSend("southyin-topic","xiaomi.phone","hello xiaomi.phone");
rabbitTemplate.convertAndSend("southyin-topic","huawei.phone","hello huawei.phone");
}
header模式
项目结构
使用较少,根据 header 将消息路由到队列
package org.southyin.springbootamqprabbitmq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.HeadersExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class HeaderConfig {
@Bean
Queue queueAge() {
return new Queue("queue-age");
}
@Bean
Queue queueName() {
return new Queue("queue-name");
}
@Bean
HeadersExchange headersExchange() {
return new HeadersExchange("southyin-header",true,false);
}
@Bean
Binding bindingAge() {
Map<String, Object> map = new HashMap<>();
map.put("age", 99);
// whereAny中如果是map,对消息有要求,就是header中必须要有age,并且值必须为99,才会路由到队列中
return BindingBuilder.bind(queueAge()).to(headersExchange()).whereAny(map).match();
}
@Bean
Binding bindingName() {
// header有name就行
return BindingBuilder.bind(queueName()).to(headersExchange()).where("name").exists();
}
}
package org.southyin.springbootamqprabbitmq.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class HeaderRecevier {
@RabbitListener(queues = "queue-age")
public void handler1(String msg) {
System.out.println("age msg = " + msg);
}
@RabbitListener(queues = "queue-name")
public void handler2(String msg) {
System.out.println("name msg = " + msg);
}
}
@Test
void contextLoads4() {
// 第一个参数是交换机名字
// 第2个参数是routingKey
// 第3个参数是消息对象
Message nameMsg = MessageBuilder.withBody("name msg!!!".getBytes()).setHeader("name","southyin").build();
rabbitTemplate.send("southyin-header",null,nameMsg);
Message nameMsg = MessageBuilder.withBody("age msg!!!".getBytes()).setHeader("age",991).build();
rabbitTemplate.send("southyin-header",null,nameMsg);
}