根据spring官网小栗子实现一个简单的spring-cloud-stream自定义binder

关于如何实现自定义binder,spring官网提供了一套方式,具体如下:

  • Add the required dependencies
  • Provide a ProvisioningProvider implementation
  • Provide a MessageProducer implementation
  • Provide a MessageHandler implementation
  • Provide a Binder implementation
  • Create a Binder Configuration
  • Define your binder in META-INF/spring.binders

下面我们就根据这个步骤简单实现一个demo。

Add the required dependencies

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-stream</artifactId>
</dependency>

当然为了测试,我们还会加上spring-boot-starter-web

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Provide a ProvisioningProvider implementation

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.");
        }

    }

}

Provide a MessageProducer implementation

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);
        }
    }

}

Provide a MessageHandler implementation

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);
        }
    }


}

Provide a Binder implementation

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);
    }

}

Create a Binder Configuration

@Configuration
public class FileMessageBinderConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public FileMessageBinderProvisioner fileMessageBinderProvisioner() {
        return new FileMessageBinderProvisioner();
    }

    @Bean
    @ConditionalOnMissingBean
    public FileMessageBinder fileMessageBinder(FileMessageBinderProvisioner fileMessageBinderProvisioner) {
        return new FileMessageBinder(null, fileMessageBinderProvisioner);
    }

}

Define your binder in META-INF/spring.binders

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。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值