Spring Cloud Stream 实战
本文结合实际项目经验,重新对Spring Cloud Stream做一个总结。
一: 目的:
Spring cloud集成Kafka,RabitMQ, 可以在不改变业务代码的情况下,自由切换消息中间件,仅需改变配置即可,类似于Hibernate,可以自由在各种数据库之间进行切换。
二: 架构

三:基础示例一:
1. jar 包依赖以及版本:
本示例所采用的 spring-boot-starter-parent的版本为2.3.6.RELEASE。spring-cloud-starter-stream-rabbit或者spring-cloud-stream-binder-rabbit(二选一即可)的版本为3.0.9.RELEASE。
1 <!-- <dependency>--> 2 <!-- <groupId>org.springframework.cloud</groupId>--> 3 <!-- <artifactId>spring-cloud-starter-stream-rabbit</artifactId>--> 4 <!-- <version>3.0.9.RELEASE</version>--> 5 <!-- </dependency>--> 6 <dependency> 7 <groupId>org.springframework.cloud</groupId> 8 <artifactId>spring-cloud-stream-binder-rabbit</artifactId> 9 <version>3.0.9.RELEASE</version> 10 </dependency>
2. 系统相关类之间的依赖关系:
理解各个接口之间的关系,可以为理解配置的写法以及后面的实战重构代码打下基础。

3. 生产者的简单实现:
生产者通过Source接口的output()向MessageChannel发送消息:
1 @Component 2 @EnableBinding(Source.class) 3 public class MessageProducer { 4 private static final Logger LOGGER = LoggerFactory.getLogger(MessageProducer.class); 5 6 @Autowired 7 private Source source; 8 9 public void sendMessage(){ 10 LOGGER.info("sendMessage"); 11 source.output().send(MessageBuilder.withPayload("Hello World from Provider").build()); 12 } 13 }
4. 消费者的简单实现:
消息的消费端通过StreamListener来监听Sink接口接收消息:
1 @EnableBinding(Sink.class) 2 @Component 3 public class MessageConsumer { 4 private static final Logger LOGGER = LoggerFactory.getLogger(MessageConsumer.class); 5 6 @StreamListener(Sink.INPUT) 7 public void receive(String message){ 8 LOGGER.info("received message: {} from MQ", message); 9 } 10 }
5. 配置文件:
注:Output和input分别为Output和Input接口中的值。
1 spring: 2 cloud: 3 stream: 4 bindings: 5 #message send channel, Output is the same name with org.springframework.cloud.stream.messaging.Source @Output("Output") value 6 Output: 7 destination: test.exchange 8 binder: test_binder 9 Input: 10 destination: test.exchange 11 binders: 12 test_binder: 13 type: rabbit 14 environment: 15 spring: 16 rabbitmq: 17 host: 127.0.0.1 18 port: 5672 19 username: guest 20 password: guest 21 virtual-host: /
6. 测试:
通过log模拟消息的发送和接受的过程:
2022-07-04 11:55:28.520 INFO 8236 --- [nio-8080-exec-4] c.a.s.stream.provider.MessageProducer : sendMessage
2022-07-04 11:55:28.523 INFO 8236 --- [Z22pN_dOiB4zg-1] c.a.s.stream.consumer.MessageConsumer : received message: Hello World from Provider from MQ
四. 自定义Channel(单个)
在实际项目中,我们回有多种不同类型的消息的生产者和消费者,就回用到不同的消息队列对消息进行处理,故需要子定义channel来处理。
1. 自定义channel的依赖

2. 自定义Channel的接口
如上图所示,我们需要重写Source和Sink的接口。
3. 使用自定义Channel的生产者
1 @Component 2 @EnableBinding(MySource.class) 3 public class MessageProducer { 4 private static final Logger LOGGER = LoggerFactory.getLogger(MessageProducer.class); 5 6 @Autowired 7 private MySource source; 8 9 public void sendMessage(){ 10 LOGGER.info("sendMessage"); 11 source.output().send(MessageBuilder.withPayload("Hello World from Provider").build()); 12 } 13 }
4. 使用自定义Channel的消费者
1 @EnableBinding(MySink.class) 2 @Component 3 public class MessageConsumer { 4 private static final Logger LOGGER = LoggerFactory.getLogger(MessageConsumer.class); 5 6 @StreamListener(MySink.MY_INPUT) 7 public void receive(String message){ 8 LOGGER.info("received message: {} from MQ", message); 9 } 10 }
5. 使用自定义Channel的配置修改
1 spring: 2 cloud: 3 stream: 4 bindings: 5 #message send channel, Output is the same name with org.springframework.cloud.stream.messaging.Source @Output("Output") value 6 my_output: 7 destination: test.exchange 8 binder: test_binder 9 my_input: 10 destination: test.exchange 11 binders: 12 test_binder: 13 type: rabbit 14 environment: 15 spring: 16 rabbitmq: 17 host: 127.0.0.1 18 port: 5672 19 username: guest 20 password: guest 21 virtual-host: /
五. 多个Channel综合模拟实例
1. 功能流程

2. 关键代码实现.
MessageChannelConstants定义各种要用到的消息交换渠道:
1 public class MessageChannelConstants { 2 public static final String NORMAL_MESSAGE_PROCUDER = "normalMessageProducer"; 3 public static final String SMS_MESSAGE_PROCUDER = "smsMessageProducer"; 4 public static final String EMAIL_MESSAGE_PROCUDER = "emailMessageProducer"; 5 6 public static final String NORMAL_MESSAGE_CONSUMER = "normalMessageConsumer"; 7 public static final String SMS_MESSAGE_CONSUMER = "smsMessageConsumer"; 8 public static final String EMAIL_MESSAGE_CONSUMER = "emailMessageConsumer"; 9 }
ChannelFactory,使用工厂方法获取MessageChannel:
@Component("rabbitChannelFactory")
public class ChannelFactory {
private MessageChannel normalMessageChannel;
private MessageChannel smsMessageChannel;
private MessageChannel emailMessageChannel;
public ChannelFactory(@Qualifier(MessageChannelConstants.NORMAL_MESSAGE_PROCUDER) MessageChannel normalMessageChannel,
@Qualifier(MessageChannelConstants.SMS_MESSAGE_PROCUDER)MessageChannel smsMessageChannel,
@Qualifier(MessageChannelConstants.EMAIL_MESSAGE_PROCUDER)MessageChannel emailMessageChannel) {
this.normalMessageChannel = normalMessageChannel;
this.smsMessageChannel = smsMessageChannel;
this.emailMessageChannel = emailMessageChannel;
}
public MessageChannel getChannelByMessageType(MessageType messageType){
switch (messageType){
case NORAL:
return this.normalMessageChannel;
case EMAIL:
return this.emailMessageChannel;
case SMS:
return this.smsMessageChannel;
default:
return null;
}
}
}
自定义@Input和@Output的接口(注:要配置Configuration@EnableBinding({ApplicationProcessorOutput.class, ApplicationProcessorInput.class})):
public interface ApplicationProcessorInput { @Input(MessageChannelConstants.NORMAL_MESSAGE_CONSUMER) SubscribableChannel normalMessageInput(); @Input(MessageChannelConstants.SMS_MESSAGE_CONSUMER) SubscribableChannel smsMessageInput(); @Input(MessageChannelConstants.EMAIL_MESSAGE_CONSUMER) SubscribableChannel emailMessageInput(); } public interface ApplicationProcessorOutput { @Output(MessageChannelConstants.NORMAL_MESSAGE_PROCUDER) MessageChannel normalMessageOutput(); @Output(MessageChannelConstants.SMS_MESSAGE_PROCUDER) MessageChannel smsMessageOutput(); @Output(MessageChannelConstants.EMAIL_MESSAGE_PROCUDER) MessageChannel emailMessageOutput(); }
消息发送端的公共类ApplicationEventPublisher,具体发消息时候,只用传入对应的消息类型既可:
@Component public class ApplicationEventPublisher implements Publisher{ private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationEventPublisher.class); private ChannelFactory channelFactory; public ApplicationEventPublisher(@Qualifier("rabbitChannelFactory") ChannelFactory channelFactory) { this.channelFactory = channelFactory; } @Override public <T> void publish(Map<String, Object> headers, T material, MessageType messageType) { MessageChannel channel = channelFactory.getChannelByMessageType(messageType); if (!ObjectUtils.isEmpty(channel)){ LOGGER.info("publish() {}", material.toString()); channel.send(MessageBuilder.createMessage(material, new MessageHeaders(headers))); } } }
配置文件:
spring: cloud: stream: bindings: #message send channel, Output is the same name with org.springframework.cloud.stream.messaging.Source @Output("Output") value normalMessageProducer: destination: noral.message.exchange binder: message_binder group: group_normal_message smsMessageConsumer: destination: sms.message.exchange binder: message_binder group: group_sms_message emailMessageConsumer: destination: email.message.exchange binder: message_binder group: group_email_message
3. 消息分组和分区
在微服务的架构中,同一个服务回被部署成多份,就会监听同一个消息队列。如果不进行分组,就会导致重复消费。
4. 测试
localhost:8080/sendMessage
log:
2022-10-01 12:21:55.250 INFO 48204 --- [nio-8080-exec-7] c.aaron.rabbitmq.service.MessageService : normal message is: qbz@163.com, Jingle Bells 2022-10-01 12:21:55.251 INFO 48204 --- [nio-8080-exec-7] c.a.r.m.p.ApplicationMessagePublisher : publish qbz@163.com, Jingle Bells to channel NORMAL 2022-10-01 12:21:55.254 INFO 48204 --- [LS44twisLVGNg-1] c.a.r.m.receiver.NormalMessagerReceiver : received the normal message qbz@163.com, Jingle Bells 2022-10-01 12:21:55.255 INFO 48204 --- [LS44twisLVGNg-1] c.a.r.m.p.ApplicationMessagePublisher : publish qbz@163.com to channel EMAIL 2022-10-01 12:21:55.256 INFO 48204 --- [LS44twisLVGNg-1] c.a.r.m.p.ApplicationMessagePublisher : publish Jingle Bells to channel TEXT 2022-10-01 12:21:55.260 INFO 48204 --- [j-HoU_ypdqwJw-1] c.a.r.mq.receiver.EmailMessagerReceiver : received the email message qbz@163.com 2022-10-01 12:21:55.260 INFO 48204 --- [Q2Mw58fe_Td7A-1] c.a.r.mq.receiver.TextMessagerReceiver : received the text message Jingle Bells
5. 项目结构:


浙公网安备 33010602011771号