Spring Cloud Stream如何消费自己生产的消息
在上一篇《Spring Cloud Stream如何处理消息重复消费》中,我们通过消费组的配置解决了多实例部署情况下消息重复消费这一入门时的常见问题。本文将继续说说在另外一个被经常问到的问题:如果微服务生产的消息自己也想要消费一份,应该如何实现呢?
- 常见错误
在放出标准答案前,先放出一个常见的错误姿势和告警信息(以便您可以通过搜索引擎找到这里_)。以下错误基于Spring Boot 2.0.5、Spring Cloud Finchley SR1。
首先,根据入门示例,为了生产和消费消息,需要定义两个通道:一个输入、一个输出。比如下面这样:
public interface TestTopic {
String OUTPUT = "example-topic";
String INPUT = "example-topic";
@Output(OUTPUT)
MessageChannel output();
@Input(INPUT)
SubscribableChannel input();
}
通过
INPUT和OUTPUT使用相同的名称,让生产消息和消费消息指向相同的Topic,从而实现消费自己发出的消息。
接下来,创建一个HTTP接口,并通过上面定义的输出通道触来生产消息,比如:
@Slf4j
@RestController
public class TestController {
@Autowired
private TestTopic testTopic;
@GetMapping("/sendMessage")
public String messageWithMQ(@RequestParam String message) {
testTopic.output().send(MessageBuilder.withPayload(message).build());
return "ok";
}
}
已经有生产消息的实现,下面来创建对输入通道的监听,以实现消息的消费逻辑。
@Slf4j
@Component
public class TestListener {
@StreamListener(TestTopic.INPUT)
public void receive(String payload) {
log.info("Received: " + payload);
throw new RuntimeException("BOOM!");
}
}
最后,在应用主类中,使用@EnableBinding注解来开启它,比如:
@EnableBinding(TestTopic.class)
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
看似天衣无缝的操作,然而在启动的瞬间,你可能收到了下面这样的错误:
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'example-topic' defined in com.didispace.stream.TestTopic: bean definition with this name already exists - Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.didispace.stream.TestTopic; factoryMethodName=input; initMethodName=null; destroyMethodName=null
at org.springframework.cloud.stream.binding.BindingBeanDefinitionRegistryUtils.registerBindingTargetBeanDefinition(BindingBeanDefinitionRegistryUtils.java:64) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
at org.springframework.cloud.stream.binding.BindingBeanDefinitionRegistryUtils.registerOutputBindingTargetBeanDefinition(BindingBeanDefinitionRegistryUtils.java:54) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
at org.springframework.cloud.stream.binding.BindingBeanDefinitionRegistryUtils.lambda$registerBindingTargetBeanDefinitions$0(BindingBeanDefinitionRegistryUtils.java:86) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:562) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:541) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.cloud.stream.binding.BindingBeanDefinitionRegistryUtils.registerBindingTargetBeanDefinitions(BindingBeanDefinitionRegistryUtils.java:76) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
at org.springframework.cloud.stream.config.BindingBeansRegistrar.registerBeanDefinitions(BindingBeansRegistrar.java:45) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:358) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_151]
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:357) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:145) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:328) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:61) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1277) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1265) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
at com.didispace.stream.TestApplication.main(TestApplication.java:13) [classes/:na]
- 正确姿势
根据错误提示:
Invalid bean definition with name 'example-topic' defined in com.didispace.stream.TestTopic: bean definition with this name already exists,没有启动成功的原因是已经存在了一个名为example-topic的Bean,那么为什么会重复创建这个Bean呢?
实际上,在F版的Spring Cloud Stream中,当我们使用
@Output和@Input注解来定义消息通道时,都会根据传入的通道名称来创建一个Bean。而在上面的例子中,我们定义的@Output和@Input名称是相同的,因为我们系统输入和输出是同一个Topic,这样才能实现对自己生产消息的消费。
既然这样,我们定义相同的通道名是行不通了,那么我们只能通过定义不同的通道名,并为这两个通道配置相同的目标Topic来将这一对输入输出指向同一个实际的Topic。对于上面的错误程序,只需要做如下两处改动:
第一步:修改通道名,使用不同的名字
public interface TestTopic {
String OUTPUT = "example-topic-output";
String INPUT = "example-topic-input";
@Output(OUTPUT)
MessageChannel output();
@Input(INPUT)
SubscribableChannel input();
}
第二步:在配置文件中,为这两个通道设置相同的Topic名称,比如:
spring.cloud.stream.bindings.example-topic-input.destination=aaa-topic
spring.cloud.stream.bindings.example-topic-output.destination=aaa-topic
这样,这两个输入输出通道就会都指向名为aaa-topic的Topic了。
最后,再启动该程序,没有报错。然后访问接口:localhost:8080/sendMessage?message=hello-didi,可以在控制台中看到如下信息:
2018-11-17 23:24:10.425 INFO 32039 --- [ctor-http-nio-2] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2018-11-17 23:24:10.453 INFO 32039 --- [ctor-http-nio-2] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory.publisher#266753da:0/SimpleConnection@627fba83 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 60752]
2018-11-17 23:24:10.458 INFO 32039 --- [ctor-http-nio-2] o.s.amqp.rabbit.core.RabbitAdmin : Auto-declaring a non-durable, auto-delete, or exclusive Queue (aaa-topic.anonymous.fNUxZ8C0QIafxrhkFBFI1A) durable:false, auto-delete:true, exclusive:true. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
2018-11-17 23:24:10.483 INFO 32039 --- [IafxrhkFBFI1A-1] com.didispace.stream.TestListener
消费自己生产的消息成功了!读者也还可以访问一下应用的
/actuator/beans端点,看看当前Spring上下文中有哪些Bean,应该可以看到有下面Bean,也就是上面分析的两个通道的Bean对象
"example-topic-output": {
"aliases": [],
"scope": "singleton",
"type": "org.springframework.integration.channel.DirectChannel",
"resource": null,
"dependencies": []
},
"example-topic-input": {
"aliases": [],
"scope": "singleton",
"type": "org.springframework.integration.channel.DirectChannel",
"resource": null,
"dependencies": []
},
- 后记
其实大部分开发者在使用Spring Cloud Stream时候碰到的问题都源于对Spring Cloud Stream的核心概念还是不够理解。所以,还是推荐读一下下面的文章和示例:
本文示例读者可以通过查看下面仓库的中的stream-consumer-self项目:
本文由 程序猿DD-翟永超 创作,采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
Spring Cloud Stream如何消费自己生产的消息的更多相关文章
- Spring Cloud Stream如何消费自己生产的消息?
在上一篇<Spring Cloud Stream如何处理消息重复消费>中,我们通过消费组的配置解决了多实例部署情况下消息重复消费这一入门时的常见问题.本文将继续说说在另外一个被经常问到的问 ...
- 整合Spring Cloud Stream Binder与GCP Pubsub进行消息发送与接收
我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 前言 之前的文章<整合Spring Cloud Stream Binder与RabbitMQ进行消息发送与接收& ...
- Spring Cloud Stream学习(五)入门
前言: 在了解完RabbitMQ后,再来学习SpringCloudStream就轻松很多了,SpringCloudStream现在主要支持两种消息中间件,一个是RabbitMQ,还有一个是KafK ...
- 使用 Spring Cloud Stream 构建消息驱动微服务
相关源码: spring cloud demo 微服务的目的: 松耦合 事件驱动的优势:高度解耦 Spring Cloud Stream 的几个概念 Spring Cloud Stream is a ...
- Spring Cloud Stream 知识点
发布-订阅模式 在Spring Cloud Stream中的消息通信方式遵循了发布-订阅模式,当一条消息被投递到消息中间件之后,它会通过共享的Topic主题进行广播,消息消费者在订阅的主题中收到它并触 ...
- Spring Cloud Alibaba学习笔记(14) - Spring Cloud Stream + RocketMQ实现分布式事务
发送消息 在Spring消息编程模型下,使用RocketMQ收发消息 一文中,发送消息使用的是RocketMQTemplate类. 在集成了Spring Cloud Stream之后,我们可以使用So ...
- Spring Cloud Stream教程(二)主要概念
Spring Cloud Stream提供了一些简化了消息驱动的微服务应用程序编写的抽象和原语.本节概述了以下内容: Spring Cloud Stream的应用模型 Binder抽象 持续的发布 - ...
- 整合Spring Cloud Stream Binder与RabbitMQ进行消息发送与接收
我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 前言 Spring Cloud Stream专门用于事件驱动的微服务系统,使用消息中间件来收发信息.使用Spring ...
- Spring Cloud Stream同一通道根据消息内容分发不同的消费逻辑
应用场景 有的时候,我们对于同一通道中的消息处理,会通过判断头信息或者消息内容来做一些差异化处理,比如:可能在消息头信息中带入消息版本号,然后通过if判断来执行不同的处理逻辑,其代码结构可能是这样的: ...
随机推荐
- video.js学习笔记
video.js学习笔记获取用户观看时长
- 03-树2 List Leaves (25 分)
Given a tree, you are supposed to list all the leaves in the order of top down, and left to right. I ...
- C#程序发送POST请求数据中+号丢失解决方案
C#程序把RSA加密后的密码发送到后台总是校验失败,用wireshark抓包检查发现POST发出的密码中的+号都变成了空格.为了正确的发送数据,要用URL转码协议进行转码. 有两个方法进行URL转码 ...
- mysql 生成随机数rand()
mysql> select rand(); +--------------------+ | rand() | +--------------------+ | 0.99134733527092 ...
- 微信小程序 按钮固定在页面底部遮住页面显示内容问题
我们分为以下部分来解决这个问题: 第一部分:问题的表现是怎么样的? 第二部分:问题的是如何实现的? 第三部分:如何解决问题? 第一部分:问题的表现是怎么样的? 我设置了页面有0-99共100个数,但是 ...
- mysql下sql语句令某字段值等于原值加上一个字符串
MYSQL在一个字段值后面加字符串,如下: member 表名 card 字段名 update member SET card = '00' || card; (postgreSQL 用 || 来连贯 ...
- POP IM 产品分析报告
一. 体验环境 产品名称:POP IM 软件版本:v2.4.0 手机系统:一加5T Android 9 体验时间:2019.10.22-2019.10.31 二. 产品简介 1. 产品定位 ...
- SSL 安全协议 以及 如何认证
目录 ssl安全协议 以及 认证 什么是协议 http劫持 ssl是什么 ssl 证书 概念 3种类型ssl 证书 ssl认证:阿里云免费认证 配置 Nginx的ssl认证 nginx的ssl证书(一 ...
- PostgreSQL中的pg_relation_filepath()函数
pg_relation_filepath()类似于pg_relation_filenode(),但它返回关系的整个文件路径名(相对于数据库集群的数据目录PGDATA). postgres=# sele ...
- Electron 入门第一篇
官网:http://electronjs.org/docs/tutorial/application-architecture 转载:https://blog.csdn.net/qq_33323731 ...