By Luoye
Spring Cloud Stream is used to build highly scalable event-driven microservices within the Spring Cloud system to simplify the development of messages in Spring Cloud applications.
Spring Cloud Stream (SCS) has a lot of content. It also has many external dependencies. To get familiar with SCS, you must learn about Spring Messaging and Spring Integration first. This article will focus on three topics:
Spring Messaging is a module in Spring Framework, which is a programming model of unified messaging.
package org.springframework.messaging;
public interface Message<T> {
T getPayload();
MessageHeaders getHeaders();
}
MessageChannel
receives messages. You can call the send method to send messages to this message channel:@FunctionalInterface
public interface MessageChannel {
long INDEFINITE_TIMEOUT = -1;
default boolean send(Message<?> message) {
return send(message, INDEFINITE_TIMEOUT);
}
boolean send(Message<?> message, long timeout);
}
SubscribableChannel
, which can be subscribed to by subinterface
of a message channel. The messages are subscribed to by MessageHandler
:public interface SubscribableChannel extends MessageChannel {
boolean subscribe(MessageHandler handler);
boolean unsubscribe(MessageHandler handler);
}
MessageHandler
:@FunctionalInterface
public interface MessageHandler {
void handleMessage(Message<?> message) throws MessagingException;
}
HandlerMethodArgumentResolver
, a message receiving argument handler, is used in conjunction with annotations, such as @Header and @Payload. HandlerMethodReturnValueHandler
, the return value handler, is used in conjunction with the annotation @SendTo after a message is received.MessageConverter
AbstractMessageSendingTemplate
ChannelInterceptor
Spring Integration extends the Spring programming model to support the Enterprise Integration Patterns. Spring Integration is an extension of Spring Messaging.
It involves many new concepts, including MessageRoute
, MessageDispatcher
, Filter, Transformer, Aggregator, and Splitter. It also provides the implementation of MessageChannel
and MessageHandler
, including DirectChannel
, ExecutorChannel
, PublishSubscribeChannel
, MessageFilter
, ServiceActivatingHandler
, and MethodInvokingSplitter
.
This code snippet and its explanation are listed below:
SubscribableChannel messageChannel =new DirectChannel(); // 1
messageChannel.subscribe(msg-> { // 2
System.out.println("receive: " +msg.getPayload());
});
messageChannel.send(MessageBuilder.withPayload("msgfrom alibaba").build()); // 3
messageChannel
MessageHandler
to consume messages in this message channelMessageHandler
in the message channel.receive: msg from alibaba
DirectChannel
has an internal UnicastingDispatcher
, which dispatches messages to the corresponding MessageChannel
. UnicastingDispatcher
is a unicasting dispatcher, as you can tell from its name. Only one message channel can be selected. How does it choose the message channel? An internal LoadBalancingStrategy
is provided. UnicastingDispatcher
polls for the channel by default. Scaling up is supported.
Let’s modify the code above to use multiple MessageHandlers
to process messages:
SubscribableChannel messageChannel = new DirectChannel();
messageChannel.subscribe(msg -> {
System.out.println("receive1: " + msg.getPayload());
});
messageChannel.subscribe(msg -> {
System.out.println("receive2: " + msg.getPayload());
});
messageChannel.send(MessageBuilder.withPayload("msg from alibaba").build());
messageChannel.send(MessageBuilder.withPayload("msg from alibaba").build());
The internal message dispatcher of DirectChannel
is UnicastingDispatcher
with a polling load balancing strategy. Therefore, two consumption activities here correspond to the two MessageHandlers
. The console prints:
receive1: msg from alibaba
receive2: msg from alibaba
After introducing the UnicastingDispatcher
, let’s take a look at the BroadcastingDispatcher
, which is used by the PublishSubscribeChannel
message channel. BroadcastingDispatcher
dispatches messages to all MessageHandlers
:
SubscribableChannel messageChannel = new PublishSubscribeChannel();
messageChannel.subscribe(msg -> {
System.out.println("receive1: " + msg.getPayload());
});
messageChannel.subscribe(msg -> {
System.out.println("receive2: " + msg.getPayload());
});
messageChannel.send(MessageBuilder.withPayload("msg from alibaba").build());
messageChannel.send(MessageBuilder.withPayload("msg from alibaba").build());
/bindings
and /channelsendpoint
.BindingProperties
and BinderProperties
.Binder is the component that binds an internal message middleware to an external one. It provides two Binding methods, bindConsumer
and bindProducer
, which are used to bind the consumer and the producer, respectively. The currently available implementations are Rabbit Binder and Kafka Binder, and Spring Cloud Alibaba has already implemented RocketMQ Binder.
From this figure, we can tell that Binding is the bridge that connects the application and the message middleware and is used to produce and consume messages. Let’s take a look at a simple example about using the RocketMQ Binder to analyze its underlying processing logic:
Enable the classes and send a message:
@SpringBootApplication
@EnableBinding({ Source.class, Sink.class }) // 1
public class SendAndReceiveApplication {
public static void main(String[] args) {
SpringApplication.run(SendAndReceiveApplication.class, args);
}
@Bean // 2
public CustomRunner customRunner() {
return new CustomRunner();
}
public static class CustomRunner implements CommandLineRunner {
@Autowired
private Source source;
@Override
public void run(String... args) throws Exception {
int count = 5;
for (int index = 1; index <= count; index++) {
source.output().send(MessageBuilder.withPayload("msg-" + index).build()); // 3
}
}
}
}
Receive the message:
@Service
public class StreamListenerReceiveService {
@StreamListener(Sink.INPUT) // 4
public void receiveByStreamListener1(String receiveMsg) {
System.out.println("receiveByStreamListener: " + receiveMsg);
}
}
This snippet is very straightforward, and it does not involve any code that relates to RocketMQ. The message is sent and received based on the SCS system. If you want to switch to RabbitMQ or Kafka, you only need to modify the configuration file without any modification to the code.
Let’s analyze how this snippet works:
1. SCS provides two interface attributes for the @EnableBinding annotation: Source and Sink. SCS builds the BindableProxyFactory
internally based on the Source and Sink attributes. The MessageChannel
returned by the corresponding output and input methods is DirectChannel
. The values of the corresponding annotations modified by the output and input methods are the names of bindings in the configuration file.
public interface Source {
String OUTPUT = "output";
@Output(Source.OUTPUT)
MessageChannel output();
}
public interface Sink {
String INPUT = "input";
@Input(Sink.INPUT)
SubscribableChannel input();
}
The names of bindings in the configuration file are the values of annotations of the Source and Sink interfaces corresponding to the output and input methods:
spring.cloud.stream.bindings.output.destination=test-topic
spring.cloud.stream.bindings.output.content-type=text/plain
spring.cloud.stream.rocketmq.bindings.output.producer.group=demo-group
spring.cloud.stream.bindings.input.destination=test-topic
spring.cloud.stream.bindings.input.content-type=text/plain
spring.cloud.stream.bindings.input.group=test-group1
2. Build the CommandLineRunner
. The run method of CustomRunner
will be executed upon startup of the application.
3. Call the output method of the Source interface to obtain the DirectChannel
and send a message to this message channel. The snippet is identical to the one in the Spring Integration section.
DirectChannel
message channel, the message is processed by the MessageHandler
called AbstractMessageChannelBinder#SendingHandler
. Then, it delegates the message to the MessageHandler
created by AbstractMessageChannelBinder#createProducerMessageHandler
for processing. (This method is implemented by a different message middleware.)MessageHandler
returned by the AbstractMessageChannelBinder#createProducerMessageHandler
method of the message middleware converts the Spring Message to the corresponding Message model and sends the model to the broker of the middleware.4. Use the @StreamListener annotation to subscribe to the message. Note: The corresponding value of Sink.input
in the annotation is "input." The configuration is subject to the value whose corresponding binding name is input:
AbstractMessageChannelBinder#createConsumerEndpoint
method implemented by a message middleware uses the Consumer to subscribe to the message and then converts the Message model of the message middleware to a Spring Message.StreamListenerMessageHandler
corresponding to the @StreamListener annotation subscribes to the message channel whose name is input and consumes the message.The text description is a little verbose. The following diagram summarizes the entire process. (The yellow-highlighted part covers the Binder implementation of the message middleware and the basic subscription and publication features of MQ.)
@StreamListener(value = Sink.INPUT, condition = "headers['index']=='1'")
public void receiveByHeader(Message msg) {
System.out.println("receive by headers['index']=='1': " + msg);
}
@StreamListener(value = Sink.INPUT, condition = "headers['index']=='9999'")
public void receivePerson(@Payload Person person) {
System.out.println("receive Person: " + person);
}
@StreamListener(value = Sink.INPUT)
public void receiveAllMsg(String msg) {
System.out.println("receive allMsg by StreamListener. content: " + msg);
}
@StreamListener(value = Sink.INPUT)
public void receiveHeaderAndMsg(@Header("index") String index, Message msg) {
System.out.println("receive by HeaderAndMsg by StreamListener. content: " + msg);
}
You may find this snippet similar to the one that is used in the Spring MVC Controller to receive requests. Their architectures are similar. In Spring MVC, handler classes for arguments and return values in the Controller are org.springframework.web.method.support.HandlerMethodArgumentResolver
and org.springframework.web.method.support.HandlerMethodReturnValueHandler
.
The handler classes for arguments and return values in Spring Messaging were also mentioned above, which are org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver
and org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler
.
Their class names and the internal method names are also the same.
This figure summarizes classes of the SCS system. For more examples about SCS and the RocketMQ Binder, please see the RocketMQ Binder Demos document. It describes message aggregation, splitting, filtering, message exception handling, message tags, SQL filtering, and synchronous and asynchronous consumption.
How Does Alibaba RocketMQ Endure Zero Failures During the 2020 Double 11 Peak?
KubeVela Releases 1.1: Reaching New Peaks in Cloud-Native Continuous Delivery
507 posts | 48 followers
FollowAlibaba Clouder - May 17, 2019
Alibaba Clouder - May 17, 2019
Alibaba Developer - October 22, 2021
Alibaba Cloud Native Community - December 1, 2021
Alibaba Cloud Native Community - September 13, 2023
Alibaba Developer - August 19, 2021
507 posts | 48 followers
FollowApsaraMQ for RocketMQ is a distributed message queue service that supports reliable message-based asynchronous communication among microservices, distributed systems, and serverless applications.
Learn MoreAlibaba Cloud Function Compute is a fully-managed event-driven compute service. It allows you to focus on writing and uploading code without the need to manage infrastructure such as servers.
Learn MoreHigh Performance Computing (HPC) and AI technology helps scientific research institutions to perform viral gene sequencing, conduct new drug research and development, and shorten the research and development cycle.
Learn MoreDeploy custom Alibaba Cloud solutions for business-critical scenarios with Quick Start templates.
Learn MoreMore Posts by Alibaba Cloud Native Community