SpringCloudStream 体验

SpringCloudStream

1、阅读须知

本教程 只是教大家如何快速用起来,原理类的东西不做解释【博主不懂】

本教程 采用 SpringCloudStream 2.x版本进行Demo

本教程 会用RabbbitMQ和Kafka 进行演示,相关工具安装不做说明

本教程 会用自定义ActiveMQ binders 的实现以及自定义 OUTPUT以及INPUT来进行一些拓展

1、概念

英文官网:Spring Cloud Stream

​ 官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。
  应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互,通过我们配置来 binding ,而 Spring Cloud Stream 的 binder 负责与消息中间件交互。所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
  通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。目前官方仅支持RabbitMQKafka

​ Stream解决了开发人员无感知的使用消息中间件的问题,因为Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程

2、版本规定

SpringCloud 版本:Greenwich.SR3
SpringBoot 版本: 2.1.1.RELEAS
Maven 版本:3.6.1
Java 版本: 1.8   

3、Pom文件

由于我采用父子结构搭建工程
父pom 进行版本管理
     <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    子pom:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
          <!--rabbit-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>
          <!--kafka-->
          <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-kafka</artifactId>
        </dependency>

4、工程搭建

4.1、代码

启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.messaging.Source;

//@EnableBinding 注解用来指定一个或多个定义了 @Input 或 @Output 注解的接口
// Source.class 定义了OUTPUT Sink定义的INPUT
@SpringBootApplication
@EnableBinding({Source.class, Sink.class})
public class SpringCloudStreamKafkaApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudStreamKafkaApplication.class,args);
    }
}

发送Service

// @EnableBinding
// RiestRetData 不用管,是我定义的返回对象

@Service
@EnableBinding(Source.class)
public class ImplSendMessageService implements SendMessageServiceKafka {


    @Autowired
    private Source source;

    @Override
    public RiestRetData sendMessage(String msg) {
        // 处理消息
        MessageBuilder<String> messageBuilder = MessageBuilder.withPayload(msg);
        // 发送消息
        source.output().send(messageBuilder.build());

        return RiestRetData.Success();
    }
}

发送Controller

@RestController
public class SendMesage {
    @Autowired
    private SendMessageServiceKafka sendMessageService;

    @GetMapping(value = "/send")
    public RiestRetData send(String message) {
        System.out.println("发送消息:" + message);
        return sendMessageService.sendMessage(message);
    }
}

监听Server

// @StreamListener 监听注解
@Component
public class MeaasgeRece {
    @StreamListener(Sink.INPUT)
    public void getMessage(Object message) {
        System.out.println("收到kafka消息:" + message);
    }
}

4.2、配置文件

#rabbit的配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#kafka的配置
spring.cloud.stream.kafka.binder.brokers=localhost:9092
spring.cloud.stream.kafka.binder.zk-nodes=localhost:2181 
spring.cloud.stream.kafka.binder.auto-create-topics=true
# rabbit/kafka
spring.cloud.stream.binders.default.type=rabbit
#【output】
spring.cloud.stream.bindings.output.destination=test-kafka
spring.cloud.stream.bindings.output.content-type=application/json
#【input】:正常该配置会在 消费端配置
spring.cloud.stream.bindings.input.destination=test-kafka

5、测试

启动工程 启动RabittMQ和Kafka

进行不同消息队列测试时只需切换spring.cloud.stream.binders.default.type = xxx 即可

http://localhost:8080/send?message=测试发送

正常情况下都是可以发送和收到消息的

6、源代码

后续上传github

联系QQ:2575101192 免费提供

7、拓展

7.1、自定义ActiveMQ Binder 实现

我们公司之前用的是ActiveMQ,但是SpringCloudStream并未提供支持,所以自己照猫画虎写一个Binder

pom

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-jms</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-pool</artifactId>
        </dependency>
        <dependency>
            <groupId>org.messaginghub</groupId>
            <artifactId>pooled-jms</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.qpid</groupId>
            <artifactId>qpid-jms-client</artifactId>
            <version>0.53.0</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
            <scope>compile</scope>
        </dependency>

conf

package com.springbinder.stream.config;

import org.apache.activemq.command.ActiveMQBytesMessage;
import org.apache.activemq.command.ActiveMQTextMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.binder.Binder;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.cloud.stream.messaging.DirectWithAttributesChannel;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.Assert;

import javax.jms.*;

public class ActiveMQMessageChannelBinder implements Binder<MessageChannel, ConsumerProperties, ProducerProperties> {

    private static final Logger logger = LoggerFactory.getLogger(ActiveMQMessageChannelBinder.class);

    @Autowired
    private JmsTemplate jmsTemplate;

    /**
     * 接受 ActiveMQ 消息
     *
     * @param name
     * @param group
     * @param inputChannel
     * @param consumerProperties
     * @return
     */
    @Override
    public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel inputChannel, ConsumerProperties consumerProperties) {
        ConnectionFactory connectionFactory = jmsTemplate.getConnectionFactory();
        try {
            // 创造 JMS 链接
            Connection connection = connectionFactory.createConnection();
            // 启动连接
            connection.start();
            // 创建会话 Session
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            // 创建消息目的
            Destination destination = session.createQueue(name);
            // 创建消息消费者
            MessageConsumer messageConsumer = session.createConsumer(destination);

            messageConsumer.setMessageListener(message -> {

                logger.info("activemq message class - " + message.getClass());
                logger.info("activemq message - " + message);
                logger.info("activemq - inputChannel " + inputChannel);
                // message 来自于 ActiveMQ
                Object object = null;
                if (message instanceof ActiveMQBytesMessage) {
                    ActiveMQBytesMessage objectMessage = (ActiveMQBytesMessage) message;
                    try {
                        byte[] bytes = new byte[(int) objectMessage.getBodyLength()];
                        objectMessage.readBytes(bytes);
                        object = new String(bytes);
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
                if (message instanceof ActiveMQTextMessage) {
                    ActiveMQTextMessage objectMessage = (ActiveMQTextMessage) message;
                    try {
                        object = objectMessage.getText();
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
                if (message instanceof ObjectMessage) {
                    ObjectMessage objectMessage = (ObjectMessage) message;
                    try {
                        object = objectMessage.getObject();
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
                inputChannel.send(new GenericMessage(object));
            });
        } catch (JMSException e) {
            e.printStackTrace();
        }

        return () -> {
        };
    }

    /**
     * 负责发送消息到 ActiveMQ
     *
     * @param name
     * @param outputChannel
     * @param producerProperties
     * @return
     */
    @Override
    public Binding<MessageChannel> bindProducer(String name, MessageChannel outputChannel, ProducerProperties producerProperties) {
        Assert.isInstanceOf(SubscribableChannel.class, outputChannel, "Binding is supported only for SubscribableChannel instances");
        SubscribableChannel subscribableChannel = (SubscribableChannel) outputChannel;
        DirectWithAttributesChannel aaa = (DirectWithAttributesChannel) outputChannel;

        aaa.addInterceptor(new ChannelInterceptor() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                System.out.println("sssssssssssssssssss   preSend");
                System.out.println("sssssssssssssssssss   preSend" + channel);
                System.out.println("sssssssssssssssssss   preSend" + message);
                System.out.println("sssssssssssssssssss   preSend" + channel.getClass());
                System.out.println("sssssssssssssssssss   preSend" + message.getClass());
                return ChannelInterceptor.super.preSend(message, channel);
            }

            @Override
            public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
                System.out.println("sssssssssssssssssss   afterSendCompletion");

                ChannelInterceptor.super.afterSendCompletion(message, channel, sent, ex);
            }
        });

        subscribableChannel.subscribe(message -> {
            // 接受内部管道消息,来自于 MessageChannel#send(Message)
            // 实际并没有发送消息,而是此消息将要发送到 ActiveMQ Broker
            logger.info("Sender Binger JMS ---" + name);



            System.out.println(name);
            System.out.println(outputChannel);
            System.out.println(outputChannel.getClass());
            System.out.println(producerProperties);
//            System.out.println(ReflectionToStringBuilder.toString(producerProperties));
//            System.out.println(ReflectionToStringBuilder.toString(message));

            System.out.println("message.getHeaders().get(\"timeout\")" + message.getHeaders().get("timeout"));
//            Long timeout = Optional.ofNullable((Long) message.getHeaders().get("timeout")).orElse(3000L);

            if (message.getHeaders().containsKey("timeout")) {
                jmsTemplate.setTimeToLive((Long) message.getHeaders().get("timeout"));
            }

            Object strMessage = message.getPayload();
            // TODO
            // 消息有效时间外边传进来了,但是现在没有办法获取
            // 跟踪源码查看MessageChanel的timeout到最后是没有使用的 - 到rabbitmq好像是没有使用
            jmsTemplate.convertAndSend(name, strMessage);


//            String strMessage = new String((byte[]) message.getPayload());
//            Greetings greetings = null;
//            try {
//                greetings = objectMapper.readValue(strMessage, Greetings.class);
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//            String id = greetings.getId();
//            MessageCreator sendcreate = (session) -> {
//                TextMessage msg = session.createTextMessage();
//                msg.setText(strMessage);
//                msg.setJMSCorrelationID(id);
//                return msg;
//            };
//            jmsTemplate.send(name, sendcreate);

        });

        return () -> {
            logger.info("Unbinding");
        };
    }
}

package com.springbinder.stream.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.stream.binder.Binder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * ActiveMQ Stream Binder 自动装配
 */
@Configuration
@ConditionalOnMissingBean(Binder.class)
//@Component
public class ActiveMQStreamBinderAutoConfiguration {
    /**
     * 暴露自定义的 ActiveMQMessageChannelBinder Bean
     */
    @Bean
    public ActiveMQMessageChannelBinder activeMQMessageChannelBinder() {
        return new ActiveMQMessageChannelBinder();
    }
}

**resource 目录下 新建 META-INF文件夹,新建 spring.binders **

activemq:\
com.springbinder.stream.config.ActiveMQStreamBinderAutoConfiguration

至此 已经完成,用法和前面的代码一样,引入到你的工程中,配置相关MQ的参数即可

7.2、自定义 OUTPUT 和 INPUT

这个很简单,基本上也是照猫画虎

InputTest

public interface InputTest {
    /**
     * Input channel name.
     */
    String INPUT = "rece";

    /**
     * @return input channel.
     */
    @Input(InputTest.INPUT)
    SubscribableChannel input();
}

OutputTest


public interface OutPutTest {
    String OUTPUT = "send";

    @Output(OutPutTest.OUTPUT)
    MessageChannel output();
}

配置文件

#第一个 发送队列【新加的】 send 和 OutPutTest 中的OutPutTest  中的定义保持一致
spring.cloud.stream.bindings.send.content-type=application/json
spring.cloud.stream.bindings.send.destination=test-rabbitmq-01
#第二个 发送队列
spring.cloud.stream.bindings.output.content-type=application/json
spring.cloud.stream.bindings.output.destination=test-rabbitmq-02
#【input】
# 第一个监听【新加的】 rece 和InputTest 中的定义保持一致
spring.cloud.stream.bindings.rece.destination=test-rabbitmq-01
# 第二个监听
spring.cloud.stream.bindings.input.destination=test-rabbitmq-02

启动类修改

@SpringBootApplication
@EnableBinding({OutPutTest.class, InputTest.class, Source.class, Sink.class})
public class SpringCloudStreamRabbitMQApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudStreamRabbitMQApplication.class, args);
    }
}

监听类新增

    @StreamListener(InputTest.INPUT)
    public void getMessage(Object message) {
        System.out.println("收到消息自定义消息:" + message);
    }
posted @ 2022-06-27 15:15  一个努力的人QAQ  阅读(246)  评论(0)    收藏  举报