看一張小圖:
比如,這是我們一個完整的項目(腦補完整!!)。此時中間件用的是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 :該參數指定了消息分區的數量。