Spring Cloud Stream是一个建立在Spring Boot和Spring Integration之上的框架,有助于创建事件驱动或消息驱动的微服务。在本文中,我们将通过一些简单的例子来介绍Spring Cloud Stream的概念和构造。
1 Maven依赖
在开始之前,我们需要添加Spring Cloud Stream与RabbitMQ消息中间件的依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId></dependency>
同时为支持Junit单元测试,在pom.xml文件中添加
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-test-support</artifactId> <scope>test</scope></dependency>
2 主要概念
微服务架构遵循“智能端点和哑管道”的原则。端点之间的通信由消息中间件(如RabbitMQ或Apache Kafka)驱动。服务通过这些端点或信道发布事件来进行通信。
让我们通过下面这个构建消息驱动服务的基本范例,来看看Spring Cloud Stream框架的一些主要概念。
2.1 服务类
通过Spring Cloud Stream建立一个简单的应用,从Input通道监听消息然后返回应答到Output通道。
@SpringBootApplication@EnableBinding(Processor.class)public class MyLoggerServiceApplication { public static void main(String[] args) { SpringApplication.run(MyLoggerServiceApplication.class, args); } @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public LogMessage enrichLogMessage(LogMessage log) { return new LogMessage(String.format("[1]: %s", log.getMessage())); } }
注解@EnableBinding声明了这个应用程序绑定了2个通道:INPUT和OUTPUT。这2个通道是在接口Processor中定义的(Spring Cloud Stream默认设置)。所有通道都是配置在一个具体的消息中间件或绑定器中。
让我们来看下这些概念的定义:
Bindings — 声明输入和输出通道的接口集合。
Binder — 消息中间件的实现,如Kafka或RabbitMQ
Channel — 表示消息中间件和应用程序之间的通信管道
StreamListeners — bean中的消息处理方法,在中间件的MessageConverter特定事件中进行对象序列化/反序列化之后,将在信道上的消息上自动调用消息处理方法。
Message Schemas — 用于消息的序列化和反序列化,这些模式可以静态读取或者动态加载,支持对象类型的演变。
将消息发布到指定目的地是由发布订阅消息模式传递。发布者将消息分类为主题,每个主题由名称标识。订阅方对一个或多个主题表示兴趣。中间件过滤消息,将感兴趣的主题传递给订阅服务器。订阅方可以分组,消费者组是由组ID标识的一组订户或消费者,其中从主题或主题的分区中的消息以负载均衡的方式递送。
2.2 测试类
测试类是一个绑定器的实现,允许与通道交互和检查消息。让我们向上面的enrichLogMessage 服务发送一条消息,并检查响应中是否包含文本“[ 1 ]:”:
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = MyLoggerServiceApplication.class)@DirtiesContextpublic class MyLoggerApplicationIntegrationTest { @Autowired private Processor pipe; @Autowired private MessageCollector messageCollector; @Test public void whenSendMessage_thenResponseShouldUpdateText() { pipe.input().send(MessageBuilder.withPayload(new LogMessage("This is my message")).build()); Object payload = messageCollector.forChannel(pipe.output()).poll().getPayload(); assertEquals("[1]: This is my message", payload.toString()); } }
2.3 RabbitMQ配置
我们需要在工程src/main/resources目录下的application.yml文件里增加RabbitMQ绑定器的配置。
spring: cloud: stream: bindings: input: destination: queue.log.messages binder: local_rabbit group: logMessageConsumers output: destination: queue.pretty.log.messages binder: local_rabbit binders: local_rabbit: type: rabbit environment: spring: rabbitmq: host: localhost port: 5672 username: guest password: guest virtual-host: /
input绑定使用名为queue.log.messages的消息交换机,output绑定使用名为queue.pretty.log.messages的消息交换机。所有的绑定都使用名为local_rabbit的绑定器。
请注意,我们不需要预先创建RabbitmQ交换机或队列。运行应用程序时,两个交换机都会自动创建。
3 自定义通道
在上面的例子里,我们使用Spring Cloud提供的Processor接口,这个接口有一个input通道和一个output通道。
如果我们想创建一些不同,比如说一个input通道和两个output通道,可以新建一个自定义处理器。
public interface MyProcessor { String INPUT = "myInput"; @Input SubscribableChannel myInput(); @Output("myOutput") MessageChannel anOutput(); @Output MessageChannel anotherOutput(); }
3.1 服务类
Spring将为我们提供这个接口的实现。通道的名称可以通过使用注解来设定,比如@Output(“myOutput”)。如果没有设置的话,Spring将使用方法名来作为通道名称。因此这里有三个通道:myInput, myOutput, anotherOutput。
现在我们可以增加一些路由规则,如果接收到的值小于10则走一个output通道;如果接收到的值大于等于10则走另一个output通道。
@Autowiredprivate MyProcessor processor;@StreamListener(MyProcessor.INPUT)public void routeValues(Integer val) { if (val < 10) { processor.anOutput().send(message(val)); } else { processor.anotherOutput().send(message(val)); } }private static final <T> Message<T> message(T val) { return MessageBuilder.withPayload(val).build(); }
3.2 测试类
发送不同的消息,判断返回值是否是通过不同的通道获得。
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = MultipleOutputsServiceApplication.class)@DirtiesContextpublic class MultipleOutputsServiceApplicationIntegrationTest { @Autowired private MyProcessor pipe; @Autowired private MessageCollector messageCollector; @Test public void whenSendMessage_thenResponseIsInAOutput() { whenSendMessage(1); thenPayloadInChannelIs(pipe.anOutput(), 1); } @Test public void whenSendMessage_thenResponseIsInAnotherOutput() { whenSendMessage(11); thenPayloadInChannelIs(pipe.anotherOutput(), 11); } private void whenSendMessage(Integer val) { pipe.myInput().send(MessageBuilder.withPayload(val).build()); } private void thenPayloadInChannelIs(MessageChannel channel, Integer expectedValue) { Object payload = messageCollector.forChannel(channel).poll().getPayload(); assertEquals(expectedValue, payload); } }
4 根据条件分派
使用@StreamListener 注释,我们还可以使用自定义的SpEL表达式来过滤用户期望的消息。下面这个例子,我们使用条件调度将消息路由到不同的输出。
@Autowiredprivate MyProcessor processor;@StreamListener( target = MyProcessor.INPUT, condition = "payload < 10")public void routeValuesToAnOutput(Integer val) { processor.anOutput().send(message(val)); }@StreamListener( target = MyProcessor.INPUT, condition = "payload >= 10")public void routeValuesToAnotherOutput(Integer val) { processor.anotherOutput().send(message(val)); }
5 总结
在本教程中,我们介绍了Spring Cloud Stream的主要概念,并展示了如何通过RabbitMQ上的一些简单示例来使用它。工程代码可参看https://download.csdn.net/download/peterwanghao/10412121