看一张小图:
比如,这是我们一个完整的项目(脑补完整!!)。此时中间件用的是RabbitMQ,如果现在项目需求换成ActiveMQ(对,就是一个很傻x的需求),你咋办?
思考60分钟3秒。......................
决定改代码!!!
那你就太low了,我们srpingCloud为我们提供了一个组件去解决这个问题。
一、Spring Cloud Stream
我们通过一个绑定层来解决这个问题,消息都经过绑定层,如果切换MQ,直接可以找到对应的MQ,相当于动态的配置不同的MQ。
1、核心概念
发布/订阅模型:
2、案例
生产者:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
</dependencies>
server:
port: 7001 #服务端口
spring:
application:
name: rabbitmq-consumer #指定服务名
rabbitmq:
addresses: 127.0.0.1
username: user
password: password
cloud:
stream:
bindings:
output:
destination: stream-default # 指定消息发送的目的地,rabbitMQ中发送到指定的exchange上
binders: # 绑定器配置
defaultRabbit:
type: rabbit
package com.springcloud.stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
/**
* @ClassName ProviderApplication
* @Description 生产者
* @Author
* @Date 2020/5/31 13:29
* @Version 1.0
**/
@SpringBootApplication
@EnableBinding(Source.class)
public class ProviderApplication implements CommandLineRunner {
@Autowired
private MessageChannel output;
@Override
public void run(String... args) throws Exception {
output.send(MessageBuilder.withPayload("hello world").build());
}
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class);
}
}
启动,就会看到对应的交换机注入了进来:
消费者:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
</dependencies>
server:
port: 7002 #服务端口
spring:
application:
name: rabbitmq-consumer #指定服务名
rabbitmq:
addresses: 127.0.0.1
username: user
password: password
cloud:
stream:
bindings:
input:
destination: stream-default
binders:
defaultRabbit:
type: rabbit
package com.springcloud.stream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import javax.xml.transform.Source;
/**
* @ClassName ConsumerApplication
* @Description 消费者
* @Author
* @Date 2020/5/31 13:48
* @Version 1.0
**/
@SpringBootApplication
@EnableBinding(Sink.class)
public class ConsumerApplication {
/**
* 获取监听的消息
*/
@StreamListener(Sink.INPUT)
public void print(String str){
System.out.println("收到RabbitMQ : " + str);
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
}
package com.springcloud.stream.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
/**
* @ClassName provider
* @Description 向中间件发送消息
* @Author
* @Date 2020/5/31 14:02
* @Version 1.0
**/
@Component
@EnableBinding(Source.class)
public class MessageProviderUtil {
@Autowired
private MessageChannel output;
public void send(Object obj){
output.send(MessageBuilder.withPayload(obj).build());
}
}
单元测试
package com.springcloud.stream;
import com.springcloud.stream.util.MessageProviderUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @ClassName com.springcloud.stream.ProviderTest
* @Description
* @Author 戴书博
* @Date 2020/5/31 14:15
* @Version 1.0
**/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ProviderTest {
@Autowired
private MessageProviderUtil messageProviderUtil;
@Test
public void testSend(){
messageProviderUtil.send("hello world");
}
}
消费者
package com.springcloud.stream.util;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.stereotype.Component;
/**
* @ClassName MessageComsumerUtil
* @Description 发送消息
* @Author
* @Date 2020/5/31 14:13
* @Version 1.0
**/
@Component
@EnableBinding(Sink.class)
public class MessageComsumerUtil {
/**
* 获取监听的消息
*/
@StreamListener(Sink.INPUT)
public void print(String str){
System.out.println("收到RabbitMQ : " + str);
}
}
自定义通道
package com.springcloud.stream.interf;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
/**
* @ClassName MyProvider
* @Description 生产者消息通道
* @Author
* @Date 2020/5/31 14:31
* @Version 1.0
**/
public interface MyProvider {
String MY_OUTPUT = "myoutput";
@Output("myoutput")
MessageChannel myoutput();
}
package com.springcloud.stream.interf;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
/**
* @ClassName MyConsumer
* @Description 发送消息
* @Author
* @Date 2020/5/31 14:37
* @Version 1.0
**/
public interface MyConsumer {
String MY_INPUT = "myinput";
@Input(MY_INPUT)
SubscribableChannel myinput();
}
修改:
消息分组
现在我们有两个消费者,如果生产者发送消息,两个消费者都可以接收的。
消息分区
生产者
从上面的配置中,我们可以看到增加了这三个参数:
-
spring.cloud.stream.bindings.input.consumer.partitioned :通过该参数开启消费者分区功能;
-
-
spring.cloud.stream.instanceIndex :该参数设置当前实例的索引号,从0开始,最大值为spring.cloud.stream.instanceCount 参数 - 1。我们试验的时候需要启动多个实例,可以通过运行参数来为不同实例设置不同的索引值。
消费者
-
pring.cloud.stream.bindings.output.producer.partitionKeyExpression :通过该参数指定了分区键的表达式规则,我们可以根据实际的输出消息规则来配置SpEL来生成合适的分区键;
-
spring.cloud.stream.bindings.output.producer.partitionCount :该参数指定了消息分区的数量。