关于如何实现自定义binder,spring官网提供了一套方式,具体如下:
下面我们就根据这个步骤简单实现一个demo。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-streamartifactId>
dependency>
当然为了测试,我们还会加上spring-boot-starter-web
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
public class FileMessageBinderProvisioner implements ProvisioningProvider<ConsumerProperties, ProducerProperties> {
@Override
public ProducerDestination provisionProducerDestination(
final String name,
final ProducerProperties properties) {
return new FileMessageDestination(name);
}
@Override
public ConsumerDestination provisionConsumerDestination(
final String name,
final String group,
final ConsumerProperties properties) {
return new FileMessageDestination(name);
}
private class FileMessageDestination implements ProducerDestination, ConsumerDestination {
private final String destination;
private FileMessageDestination(final String destination) {
this.destination = destination;
}
@Override
public String getName() {
return destination.trim();
}
@Override
public String getNameForPartition(int partition) {
throw new UnsupportedOperationException("Partitioning is not implemented for file messaging.");
}
}
}
public class FileMessageProducer extends MessageProducerSupport {
public static final String ARCHIVE = "archive.txt";
private final ConsumerDestination destination;
private String previousPayload;
public FileMessageProducer(ConsumerDestination destination) {
this.destination = destination;
}
@Override
public void doStart() {
receive();
}
private void receive() {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleWithFixedDelay(() -> {
String payload = getPayload();
if(payload != null) {
Message<String> receivedMessage = MessageBuilder.withPayload(payload).build();
archiveMessage(payload);
sendMessage(receivedMessage);
}
}, 0, 50, MILLISECONDS);
}
private String getPayload() {
try {
List<String> allLines = Files.readAllLines(Paths.get(destination.getName()));
String currentPayload = allLines.get(allLines.size() - 1);
if(!currentPayload.equals(previousPayload)) {
previousPayload = currentPayload;
return currentPayload;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
private void archiveMessage(String payload) {
try {
Files.write(Paths.get(ARCHIVE), (payload + "\n").getBytes(), CREATE, APPEND);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class FileMessageHandler implements MessageHandler {
private final ProducerDestination destination;
public FileMessageHandler(ProducerDestination destination) {
this.destination = destination;
}
@Override
public void handleMessage(Message<?> message) throws MessagingException {
//write message to file
String fileName = destination.getName();
String payload = new String((byte[])message.getPayload()) + "\n";
try {
Files.write(Paths.get(fileName), payload.getBytes(), CREATE, APPEND);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class FileMessageBinder extends AbstractMessageChannelBinder<ConsumerProperties, ProducerProperties, FileMessageBinderProvisioner> {
public FileMessageBinder(
String[] headersToEmbed,
FileMessageBinderProvisioner provisioningProvider) {
super(headersToEmbed, provisioningProvider);
}
@Override
protected MessageHandler createProducerMessageHandler(
final ProducerDestination destination,
final ProducerProperties producerProperties,
final MessageChannel errorChannel) throws Exception {
return new FileMessageHandler(destination);
}
@Override
protected MessageProducer createConsumerEndpoint(
final ConsumerDestination destination,
final String group,
final ConsumerProperties properties) throws Exception {
return new FileMessageProducer(destination);
}
}
@Configuration
public class FileMessageBinderConfiguration {
@Bean
@ConditionalOnMissingBean
public FileMessageBinderProvisioner fileMessageBinderProvisioner() {
return new FileMessageBinderProvisioner();
}
@Bean
@ConditionalOnMissingBean
public FileMessageBinder fileMessageBinder(FileMessageBinderProvisioner fileMessageBinderProvisioner) {
return new FileMessageBinder(null, fileMessageBinderProvisioner);
}
}
file:\
com.gx.sc.springcloudstreamredisbinder.FileMessageBinderConfiguration
完成以上七步基本上就创建好了一个简单的binder,当然还有一个事情也需要做,就是配置文件:
#此处的file就是我们在META-INF/spring.binders中定义的file
#channel name is output,对应StreamProcessor中的OUTPUT
spring.cloud.stream.bindings.output.binder=file
#default content-type is application/json, optional
spring.cloud.stream.bindings.output.content-type=application/json
#topic name is topic1
spring.cloud.stream.bindings.output.destination=topic1
#topic group is group1,group可以在多节点时避免消息的重复消费
spring.cloud.stream.bindings.output.group=group1
#此处的file就是我们在META-INF/spring.binders中定义的file
#channel name is input,对应StreamProcessor中的INPUT
spring.cloud.stream.bindings.input.binder=file
#default content-type is application/json, optional
spring.cloud.stream.bindings.input.content-type=application/json
#topic name is topic1
spring.cloud.stream.bindings.input.destination=topic1
#topic group is group1,group可以在多节点时避免消息的重复消费
spring.cloud.stream.bindings.input.group=group1
下面我们来测试一下,该demo是用spring-boot搭建的,当然会有下面的启动类:
@SpringBootApplication
public class SpringCloudStreamRedisBinderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudStreamRedisBinderApplication.class, args);
}
}
再创建一个TestController
@RequestMapping("/test")
@RestController
@EnableBinding(StreamProcessor.class)
public class TestController {
@Autowired
private StreamProcessor streamProcessor;
@GetMapping("/send")
public void send(@RequestParam String message) {
streamProcessor.output().send(MessageBuilder.withPayload(message).build());
}
}
其中的StreamProcessor是指定需要绑定的目标,具体代码如下:
public interface StreamProcessor {
String INPUT = "input";
String OUTPUT = "output";
@Input(INPUT)
SubscribableChannel input();
@Output(OUTPUT)
MessageChannel output();
}
可以简单看一下@Input源码,大概意思就是指定binding(可以看成channel,方便理解)目标名称,然后将该名称作为一个bean的名称,所以该名称要保证唯一,否则会冲突,还有就是如果没有指定destination(可以理解成topic)名称,则以该名称作为默认的destination名称
@Qualifier
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE,
ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Input {
/**
* Specify the binding target name; used as a bean name for binding target and as a
* destination name by default.
* @return the binding target name
*/
String value() default "";
}
@Output源码大概也是这么个意思,和@Input的区别就是@Output是代表发送方,@Input代表接收方,默认可以参考Source(发送方)和Sink(接收方)接口,另外还有一个接口Processor,继承了Source和Sink,所以它具有两者的意义。
@Qualifier
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE,
ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Output {
/**
* Specify the binding target name; used as a bean name for binding target and as a
* destination name by default.
* @return the binding target name
*/
String value() default "";
}
到这里代码部分已经全部完成,下面就启动应用,打开浏览器,输入如下地址:
http://localhost:8080/test/send?message=hello
此时项目根路径下面会多出两个文件,一个是topic1(发送内容),一个是archive.txt(接收内容),里面都有一行hello;
可以继续修改message的值,每次修改然后请求之后,两个文件就会多出来一行。
以上就是根据官网提供的步骤简单实现的一个binder的demo。