SpringBoot整合消息中间件

451 阅读5分钟

ActiveMQ

安装

官方下载地址

linux下运行 bin 文件夹中的 activemq

windows系统下可以直接下载解压后运行 bin 文件夹中 activemq.bat 文件运行

docker

1621060490714.png 61616是容器端口,8161是管理页面端口,默认用户名密码 admin

1621060545726.png

创建项目

1621060545726.png 配置文件

# 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");
    }
}

项目结构

1621060545726.png

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);
    }
}

1621062026696.png

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 即可登录管理页面

创建项目

1621065062336.png

配置文件

# rabbitmq连接参数
spring.rabbitmq.host=192.168.189.101
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

在rabbitmq中有一个交换机的概念,所有生产的消息都交给它,由交换机根据不同策略分发到不同的队列当中,在rabbitmq中一共有4种策略

Direct模式

项目结构

1621067614930.png

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");
    }

}

1621068510758.png

Fanout模式

项目结构

1621068510758.png

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");
    }
}

1621077006006.png

topic模式

这个交换机稍微复杂,但更灵活

项目结构

1621078609079.png

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模式

项目结构

1621080085904.png

使用较少,根据 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);
}