Spring Cloud Stream is used in the Spring Cloud system to build highly scalable event-driven microservices, for the purpose of simplifying the development of messages in a Spring Cloud application. Spring Cloud Stream (SCS) has a lot of content, as well as many external dependencies. To be familiar with SCS, you must first learn about Spring Messaging and Spring Integration. This article mainly covers the following topics:
Spring Messaging is a Spring Framework module, which is a programming model of unified messaging.
Message
has a Payload (message body) and a Header: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
of the message channel. This subinterface is 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;
}
1. Message receiving argument handler and return value handler: The message receiving argument handler HandlerMethodArgumentResolver
is used in conjunction with annotations such as @Header
and @Payload
. The return value handler HandlerMethodReturnValueHandler
is used in conjunction with the @SendTo
annotation after a message is received.
2. Message body content converter: MessageConverter
.
3. Unified and abstract message sending template: AbstractMessageSendingTemplate
.
4. Message channel interceptor: ChannelInterceptor
.
Spring Integration Extends the Spring programming model to support the well-known 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 the MessageChannel
(such as DirectChannel
, ExecutorChannel
, PublishSubscribeChannel
) and MessageHandler
(such as MessageFilter
, ServiceActivatingHandler
, and MethodInvokingSplitter
).
First, this article describes several message processing methods:
Then, let us try Spring Integration with a simple example:
SubscribableChannel messageChannel = new DirectChannel(); // 1
messageChannel.subscribe(msg -> { // 2
System.out.println("receive: " + msg.getPayload());
});
messageChannel.send(MessageBuilder.withPayload("msg from alibaba").build()); // 3
1. Build a subscribable message channel messageChannel
.
2. Use MessageHandler
to consume messages of this message channel.
3. Send a message to this message channel. This message will be eventually consumed by the MessageHandler
of the message channel. At last, the controller prints: receive: msg from alibaba
.
DirectChannel
has an internal UnicastingDispatcher
, which dispatches messages to the corresponding MessageChannel
. UnicastingDispatcher
literally means a unicasting dispatcher which can choose only one message channel. How does it choose the message channel? An internal LoadBalancingStrategy
is provided. By default, UnicastingDispatcher polls for the channel. Scaling up is supported.
Let us modify the preceding code 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
, which is a unicasting dispatcher polls for the channel. Therefore, two consumption activities correspond to two MessageHandlers
. The controller prints:
receive1: msg from alibaba
receive2: msg from alibaba
After introducing the UnicastingDispatcher
, let us 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());
Send two messages, and both are consumed by all MessageHandlers
. The controller prints:
receive1: msg from alibaba
receive2: msg from alibaba
receive1: msg from alibaba
receive2: msg from alibaba
Relationships between SCS and other modules are:
Binder
, Binding
, @EnableBinding
, and @StreamListener
.Binder
is the component that binds an internal message middleware to an external one. ECS provides two Binding
methods: bindConsumer
and bindProducer
, which are used to respectively bind the consumer and the producer. The currently available implementations are Rabbit Binder and Kafka Binder, and Spring Cloud Alibaba has already implemented RocketMQ Binder.
You can see from the following picture that Binding is the bridge that connects the application and the message middleware, and is used to produce and consume messages. Let us take at a simple example that uses the RocketMQ Binder, and then analyze its underlying processing logic:
@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
}
}
}
}
@Service
public class StreamListenerReceiveService {
@StreamListener(Sink.INPUT) // 4
public void receiveByStreamListener1(String receiveMsg) {
System.out.println("receiveByStreamListener: " + receiveMsg);
}
}
This snippet is very straight forward, 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 from RabbitMQ to Kafka, change the configuration file. You do not have to modify the code.
Let us analyze how this snippet works:
1. SCS provides two interface properties for the @EnableBinding
annotation: Source and Sink. SCS internally builds the BindableProxyFactory
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 AbstractMessageChannelBinder#SendingHandler
. This handler then sends the message to a MessageHandler created by the AbstractMessageChannelBinder#createProducerMessageHandler
method (implemented by a message middleware) for processing.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 that 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 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 section covers the Binder implementation of a message middleware and the basic subscription and publication features of a message queue (MQ).
Now, let us take a look at an SCS snippet for message processing:
@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 Spring MVC Controller to receive requests. That is because they use similar architectures. Spring MVC classes used to process arguments and return values of the controller are respectively org.springframework.web.method.support.HandlerMethodArgumentResolver
and org.springframework.web.method.support.HandlerMethodReturnValueHandler
.
Spring Messaging classes used to process arguments and return values are respectively org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver
and org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler
.
Their class names and even the internal method names are the same.
The preceding figure summarizes classes of the SCS system. For more demos about SCS and the RocketMQ Binder, see RocketMQ Binder Demos. These demos cover the message aggregator, splitter, and filter; the handling of abnormal messages; message tags and SQL filtering; and synchronous and asynchronous consumption.
The next article will analyze the role of the Spring Cloud Bus in the Spring Cloud system, and show you how RocketMQ Binder of Spring Cloud Alibaba implements the Spring Cloud Stream standard.
Knowledge sharing - Introduction to the Spring Cloud Bus message bus
2,599 posts | 762 followers
FollowAlibaba Cloud Native Community - November 10, 2021
Alibaba Clouder - May 17, 2019
Alibaba Developer - October 22, 2021
Alibaba Cloud Native Community - December 1, 2021
Alibaba Developer - August 19, 2021
Alibaba Cloud Native Community - April 24, 2023
2,599 posts | 762 followers
FollowA PaaS platform for a variety of application deployment options and microservices solutions to help you monitor, diagnose, operate and maintain your applications
Learn MoreAlibaba Cloud Container Service for Kubernetes is a fully managed cloud container management service that supports native Kubernetes and integrates with other Alibaba Cloud products.
Learn MoreA secure image hosting platform providing containerized image lifecycle management
Learn MoreMore Posts by Alibaba Clouder