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 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。目前官方仅支持RabbitMQ、Kafka
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);
}

浙公网安备 33010602011771号