一般集成一个中间件,比如redis,elasticjob等,分为以下几个步骤
1.导入包含springboot对应的start
2.写配置文件,相应的配置信息
3.定义配置文件对应的属性类,加载配置信息(可不用,用注解读取配置文件属性)
4.写配置类,初始化相应的bean
比如elasticjob需要zk,所以要配置类配置zk信息,今天说的roketmq需要nameserver服务作为类似注册中心,所以需要配置nameserver信息。但是既然是springboot start最大的好处就是简化配置,理想情况下,只需要前两步,引入依赖,添加配置信息就行了。至于加载配置信息,创建bean应该是springboot自己做好了。
来,
1.
org.apache.rocketmq
rocketmq-spring-boot-starter
2.2.1
2.
怎么发送一个同步消息
@Slf4j
public class SyncProducer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("group_suo");
producer.setNamesrvAddr("47.103.143.191:8201;47.103.143.191:8202");
producer.start();
Message msg = new Message("Topic3", "Tag", UUID.randomUUID().toString(),
("Hello RocketMQ , I`m sk4").getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
log.info("sendResult: ", JSON.toJSONString(sendResult));
producer.shutdown();
}
}
创建一个生产者,很多教程这样写,配置nameServer,启动,发送,关闭
有以下疑问
1,生产者应该封装成工具类
2,每次发消息,都要自己启动和关闭吗,mq没有自己做
先不这样做,我们的目的就是拿到生产者,然后发消息,工具类如下
@Component
public class RocketMQProducerUtil {
private static final Logger log = LoggerFactory.getLogger(RocketMQProducerUtil.class);
@Autowired(required = false)
private DefaultMQProducer defaultMQProducer;
public RocketMQProducerUtil() {
}
public SendResult sendMessage(String topic, String tags, String keys, String contentText) {
this.checkParam(topic, tags, keys, contentText);
Message message = new Message(topic, tags, keys, contentText.getBytes());
this.generateTraceId(message);
try {
SendResult sendResult = this.defaultMQProducer.send(message);
return sendResult;
} catch (Exception var7) {
log.error("rocketMq message send error :{}", var7.getMessage());
var7.printStackTrace();
throw new RRException("rocketMq message send error", var7);
}
}
针对,DefaultMQProducer,那么我们不自己创建bean,springboot是怎么做的,怎么加载到nameserver的?看下这个配置类,RocketMQAutoConfiguration,(比较奇怪在spring-factories没有找到这个配置类(因为它不是springboot原始组件),在start中,单例可以理解),这个配置类在rocketmq-start中。看下这个配置类信息,做的什么
1.配置信息
rocketmq.name-server 是必须的,要这样命名,多个;间隔
2.创建了生产者,消费者bean
详细流程可跟踪源码,最终调用
DefaultMQProducer 构造函数,创建bean
3.把生产者,消费者bean,加入
RocketMQTemplate 中,所以我们可以用这个类,获取生产者消费者,再去调用他们的方法
通过set方式注入
但是,创建生产者时,即使按照条件配置了,断点还是无法走到这一步,无法创建生产者bean,不知道为什么?
@ConditionalOnProperty( prefix = "rocketmq", value = {"name-server", "producer.group"} )
如果不使用springboot自己创建的bean,我们只有自己创建
@Component
public class MQProducerConfigure {
private static final Logger log = LoggerFactory.getLogger(MQProducerConfigure.class);
@Value("${rocketmq.producer.groupName:#{null}}")
private String producerGroupName;
@Value("${rocketmq.name-server:#{null}}")
private String nameserAddr;
@Value("${rocketmq.producer.maxMessageSize:131072}")
private int maxMessageSize;
@Value("${rocketmq.producer.sendMsgTimeout:10000}")
private int sendMsgTimeout;
private DefaultMQProducer producer;
public MQProducerConfigure() {
}
@Bean(name = { "defaultMQProducer" })
@Primary
public DefaultMQProducer getRocketMQProducer() {
if (!Objects.isNull(this.producerGroupName) && this.producerGroupName.trim().length() != 0) {
if (Objects.isNull(this.nameserAddr)) {
throw new RRException(5000001, "rocketmq.namesrvAddr is not setted");
} else {
this.producer = new DefaultMQProducer(this.producerGroupName);
this.producer.setNamesrvAddr(this.nameserAddr);
this.producer.setMaxMessageSize(this.maxMessageSize);
this.producer.setSendMsgTimeout(this.sendMsgTimeout);
this.producer.setVipChannelEnabled(false);
try {
this.producer.start();
log.info("rocketMQ is start !!groupName : {},nameserAddr:{}", this.producerGroupName,
this.nameserAddr);
} catch (MQClientException var5) {
log.error(String.format("rocketMQ start error,{}", var5.getMessage()));
var5.printStackTrace();
}
return this.producer;
}
} else {
log.info("RocketMQProducerConfig-getRocketMQProducer():{}",
"can not find producer group name [rocketmq.producer.groupName], will not create RocketMq producer");
return null;
}
}
}
通过断点可追踪到,先创建了生产者bean, 然后创建
RocketMQTemplate 时,报错
Caused by: org.apache.rocketmq.client.exception.MQClientException: The producer service state not OK, maybe started once, RUNNING
啥意思:生产者服务状态不正常,可能启动过一次,正在运行
对啊,创建bean时,确实调用了product.start(),为什么导致报错呢?
那把启动的代码注释掉...确实没有报错了
继续
用生产者发消息能否成功,真成功了,而且被正常监听到并消息
消费者
/**
* consumerMode=ConsumeMode.ORDERLY(这个是顺序消费,默认是ConsumeMode.CONCURRENT异步多线程消费)
*
* consumeMode = ConsumeMode.CONCURRENTLY, //默认值 ConsumeMode.CONCURRENTLY 并行处理 ConsumeMode.ORDERLY 按顺序处理
* messageModel = MessageModel.CLUSTERING, //消息模式:广播和集群,默认是集群
*/
@Component
@Slf4j
@RocketMQMessageListener(topic = "topic1", consumerGroup = "group_1")
public class MyConsumer implements RocketMQListener {
@Override
public void onMessage(MessageExt messageExt) {
log.info("消息内容 body:{}, tag:{}", new String(messageExt.getBody()), messageExt.getTags());
log.info("消费内容:{}", JSON.toJSONString(messageExt));
}
}
再继续发送消息,也是正常的,配置如下
至此,文章开头的常规步骤,自己定义配置类,创建bean,可以完成整个功能。
再回头定位,为什么springboot不能自己创建生产者bean,改下配置把生产组改成producer.group,把自定义配置类注释掉
详细流程可跟踪源码,最终调用
DefaultMQProducer 构造函数,创建bean
重新启动一下,居然进来了
且启动成功,重新发消息,正常发送及消费
再发送一个,也是正常,至此完全依赖springboot自动配置也正常了。
再说一下,怎么自动加载到配置信息的,看下这个属性类是(就是我们开头说的第3点)
RocketMQProperties,注意这样定义nameServer也可以匹配到配置文件中name-server,这个类定义了与生产者,消费者对应的属性,且均定义了默认值,可以简化配置,只需要定义nameServer,group,topic信息
然后用RocketMQTemplate发送消息也是正常的
到此,rocketmq集成springboot配置原理,大概理清了。