当想使用 Spring Cloud Stream 让微服务自己生产的消息自己消费时,如果使用的@Input("xxx")
与@Output("xxx")
注解中的"xxx"名称相同,启动程序将会报错:
Invalid bean definition with name 'xxx' defined in xxx: bean definition with this name already exists
。
完整错误记录及解决方法:
首先,为了生产和消费消息,需要定义一个输入通道、一个输出通道:
public interface StreamClient {
String INPUT = "myMessage";
String OUTPUT = "myMessage";
@Input(StreamClient.INPUT)
SubscribableChannel input();
@Output(StreamClient.OUTPUT)
MessageChannel output();
}
通过 INPUT 和 OUTPUT 使用相同的名称,让生产消息和消费消息指向相同的Topic,从而实现消费自己发出的消息。
接下来,创建一个HTTP接口,并通过上面定义的输出通道触来生产消息:
@RestController
public class SendMessageController {
@Autowired
private StreamClient streamClient;
@GetMapping("/sendMessage")
public void process() {
String message = "now " + new Date();
streamClient.output().send(MessageBuilder.withPayload(message).build());
}
}
已经有生产消息的实现,下面来创建对输入通道的监听,以实现消息的消费逻辑,需在该类名上标记 @EnableBinding(xxx.class)
注解:
@Component
@EnableBinding(StreamClient.class)
@Slf4j
public class StreamReceiver {
@StreamListener(value = StreamClient.INPUT)
public void process(String message) {
log.info("StreamReceiver: {}", message);
}
}
这看似天衣无缝的操作,如果你在这时启动,将会看到如下报错信息:
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'myMessage' defined in com.imooc.order.server.message.StreamClient: 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.imooc.order.server.message.StreamClient; 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]
以下省略...
解决方法:
根据错误提示:Invalid bean definition with name 'myMessage' defined in com.imooc.order.server.message.StreamClient: bean definition with this name already exists
,没有启动成功的原因是:已经存在了一个名为myMessage的Bean,那么为什么会重复创建这个Bean呢?
当我们使用@Output
和@Input
注解来定义消息通道时,都会根据传入的通道名称来创建一个Bean,而在上面的例子中,因为我们需要实现对自己生产的消息进行消费,所以我们需要定义的 @Output 和 @Input 名称相同的Topic,这样一来,便会生成相同Bean,以至报错bean definition with this name already exists
。
既然这样,我们定义相同的通道名是行不通了,那么我们只能通过定义不同的通道名,并为这两个通道配置相同的目标Topic来将这一对输入输出指向同一个实际的Topic。
对于上面的错误程序,只需要做如下两处改动:
第一步:修改通道名,使用不同的名字
public interface StreamClient {
String INPUT = "myMessageTopicInput";
String OUTPUT = "myMessageTopicOutput";
@Input(StreamClient.INPUT)
SubscribableChannel input();
@Output(StreamClient.OUTPUT)
MessageChannel output();
}
第二步:在配置文件中,为这两个通道设置相同的Topic名称
spring:
cloud:
stream:
bindings:
myMessageTopicInput:
group: order
destination: same-topic
content-type: application/json
myMessageTopicOutput:
group: order
destination: same-topic
content-type: application/json
这样,这两个输入输出通道就会都指向名为same-topic
的Topic了。
最后,再启动该程序,没有报错。然后访问controller:localhost:8080/sendMessage
,可以在控制台中看到如下输出信息:
2020-03-17 22:09:56.490 INFO 84 --- [e-topic.order-1] c.i.order.server.message.StreamReceiver : StreamReceiver: now Tue Mar 17 22:09:56 CST 2020
至此,已解决错误,希望能帮助到更多的伙伴们!