Spring Cloud Stream 的 binder 负责与消息中间件交互(和消息中间件解耦,不需要关注具体的消息中间件)应用通过Spring Cloud Stream插入的input(相当于消费者consumer,它是从队列中接收消息的)和output(相当于生产者producer,它是从队列中发送消息的。)通道与外界交流。当需要升级消息中间件,或者是更换其他消息中间件产品时,我们需要做的就是更换对应的Binder绑定器而不需要修改任何应用逻辑。
Stream内置了三个接口,分别是Source,Sink,Processor
Source是输出通道,通道名是output
public interface Source {
String OUTPUT = "output";
@Output("output")
MessageChannel output();
}
Sink是输入通道,通道名是input
public interface Sink {
String INPUT = "input";
@Input("input")
SubscribableChannel input();
}
Processor即是输入通道,也是输出通道,通道名是output,input
public interface Processor extends Source, Sink {
}
接下来我们创建三个模块stream_client_1(端口8091),stream_client_2(8092)stream_client_3(8093)
三个模块都添加依赖,这里我们为了方便就没有到服务中心注册,我们测试的话,只启动这三个模块就行了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
stream_client_1模块作为消息生产者
application.yml文件
server:
port: 8091
spring:
application:
name: producer
cloud:
stream:
kafka:
binder:
#绑定kafka
brokers: localhost:9092
#如果设置为false,就不会自动创建Topic 有可能你Topic还没创建就直接调用了。
auto-create-topics: true
bindings:
#使用Source 提供的output,输出通道
output:
#消息发往的目的地
destination: stream-demo
#消息发送的格式,使用文本的格式发送
content-type: text/plain
我们创建一个user类,这个user就是我们发送的消息
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
@EnableBinding是用来绑定消息通道
@EnableBinding(Source.class)
public class SendUser {
@Autowired
private Source source;
public void sendUser(User user){
//发送消息
source.output().send(MessageBuilder.withPayload(user).build());
}
}
定义一个controller调用sendUser方法
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private SendUser sendUser;
@RequestMapping("/sendUser")
public void sendUser(){
User user = new User();
user.setId(1);
user.setName("小红");
sendUser.sendUser(user);
}
}
接着我们创建stream_client_2和stream_client_3模块,两个模块一样
server:
port: 8092
spring:
application:
name: consumer
cloud:
stream:
kafka:
binder:
auto-create-topics: true
brokers: localhost:9092
bindings:
#使用Sink的输入通道
input:
#上面我们是向stream-demo通道中发送消息
destination: stream-demo
接收方法
//绑定Sink通道
@EnableBinding(Sink.class)
public class Consumer1 {
//使用StreamListener监听input通道
@StreamListener(Sink.INPUT)
public void getUser(Object obj){
System.out.println(obj);
}
}
当我们访问发送消息的方法时,接收端会收到消息:
两个接收者都接收到了消息,但是我们有时候只要一个接收者接收消息怎么办?
我们可以将两个接收者放到同一个组中,这样他们就会互相竞争,最终只有一个接收者接收到消息。不同的组不会产生竞争关系。
我们只需要在接收端加上分组就可以了。
bindings:
input:
destination: stream-demo
#两个接收端分到同一个组中会相互竞争
group: student
这样我们在发送消息时,只会有一个接收端接收到消息。
接下来我们做另一个demo,client1发送消息到client2,client2可以拿到消息进行修改,然后转发给cllient3,这样我们就要用到stream提供的第三个通道Processor
接下来我们修改client1发送端,将格式改为json格式
output:
destination: stream-demo
content-type: application/json
下面是client2转发端
bindings:
input:
#还是接收client1发送的数据
destination: stream-demo
output:
#2向这个通道发送数据,让3接收这个通道的数据
destination: transfer
content-type: application/json
创建一个中转站类
@EnableBinding(Processor.class)
public class Transfer {
//输入通道是input,输出通道是output
@ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT)
public Object transfer(Object obj){
System.out.println(obj);
return "加工:"+obj;
}
}
这里注意要将前面绑定的@EnableBinding(Sink.class)给注释掉,要不然会报重复绑定。
接着是client3修改接收通道
bindings:
input:
#接收2发送的消息
destination: transfer
我们可以看到消息成功转发,而且是json格式。
这里有一个问题没有弄明白,就是client1发送text/plain格式,而client2发送application/json格式,client3收到的却是text的格式?
在实际生产中,我们不可能仅仅使用stream提供的三个通道,这个远远不够的,因此我们要自定义消息通道,其实很简单,就是比这葫芦画瓢。
首先我们来改client1,这是发送端,我们比这模拟一个myOutput通道
public interface MyOutput {
String MYOUTPUT = "myOutput";
@Output("myOutput")
//方法名是什么无所谓
MessageChannel output();
}
发送方法
//这里我们绑定的是自定义的消息输出通道
@EnableBinding(MyOutput.class)
public class SendUser {
@Autowired
private MyOutput myOutput;
public void sendUser(User user){
myOutput.output().send(MessageBuilder.withPayload(user).build());
}
}
修改配置文件
bindings:
#不要忘了,这个通道要换成我们自定义的
myOutput:
destination: stream-demo
#发送端发送的是text格式
content-type: text/plain
接着我们修改client3,这是消息接收端,我们来模拟一个myInput通道
public interface MyInput {
String MYINPUT = "myInput";
@Input("myInput")
MessageChannel input();
}
接收方法
@EnableBinding(MyInput.class)
public class Consumer2 {
@StreamListener(MyInput.MYINPUT)
public void getUser(Object obj){
System.out.println(obj);
}
}
修改配置
bindings:
myInput:
destination: transfer
然后我们修改client2,这是中转站,我们模拟transfer通道
public interface MyTransfer {
String TRANSFERTO = "transferTo";
String TRANSFERIN = "transferIn";
@Output("transferTo")
MessageChannel transferTo();
@Input("transferIn")
MessageChannel transferIn();
}
换成我们自己的通道
@EnableBinding(MyTransfer.class)
public class Transfer {
@ServiceActivator(inputChannel = MyTransfer.TRANSFERIN, outputChannel = MyTransfer.TRANSFERTO)
public Object transfer(Object obj){
System.out.println(obj);
return "加工:"+obj;
}
}
修改配置
bindings:
transferIn:
destination: stream-demo
transferTo:
destination: transfer
#发送的是json格式
content-type: application/json
最后启动程序:
client1发送的是text格式,client2转发的是json格式,但是最后client3收到的却是text格式?这个问题目前还没有搞懂。