在 Sping Boot入门到实战之入门篇(四):Spring Boot自动化配置 这篇中,我们知道Spring Boot自动化配置的实现,主要由如下几部分完成:

  1. @EnableAutoConfiguration注解
  2. SpringApplication类
  3. spring-boot-autoconfigure jar包
  4. spring.factories文件

  官方提供的starter,大多包含两个jar包: 一个starter——没有任何实现,只用来管理依赖(spring.providers文件声明),一个autoconfigure包——包含所有具体实现,包括自动配置类,及META-INF/spring.factories文件。自定义starter的时候,可以合并写到一个。

  官方提供的starter,命名遵循spring-boot-starter-xx, 自定义starter,命名遵循xx-spring-boot-starter。

  本文基于阿里云消息队列RocketMQ服务(https://help.aliyun.com/document_detail/43349.html?spm=a2c4g.11186623.3.2.Ui5KeU),实现一个自定义starter,以实现定时消息与延迟消息(如订单多久未支付自动关闭等)发送与接收功能的快速开发。源码地址: mq-spring-boot-starter

  

  1 创建mq-spring-boot-starter maven项目。pom.xml中引入依赖:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>ons-client</artifactId>
<version>1.7.0.Final</version>
</dependency>
</dependencies>

  2 定义属性配置类。读取属性配置文件中以"aliyun.mq"开头的属性。

@ConfigurationProperties(prefix = "aliyun.mq")
public class MqPropertiesConfig {
private String onsAddr;
private String topic;
private String accessKey;
private String secretKey;
private Properties producer;
private Properties consumer;
private String tagSuffix; setter/getter;
}

  3 定义自动配置类。其中 @ConditionalOnProperty(prefix = "aliyun.mq.consumer",value = "enabled",havingValue = "true") 表示当配置的属性中,存在属性aliyun.mq.consumer.enabled,且值为true时,才实例化该Bean。因为某些应用,只需要生产者或消费者,可以通过这个属性来控制是否实例化对应Bean。

@Configuration
@EnableConfigurationProperties(MqPropertiesConfig.class)
public class MqAutoConfig { @Autowired
private MqPropertiesConfig propConfig; @Bean(initMethod="start", destroyMethod = "shutdown")
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "aliyun.mq.producer",value = "enabled",havingValue = "true")
public MqTimerProducer mqTimerProducer(){
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.ProducerId, propConfig.getProducer().getProperty("producerId"));
properties.setProperty(PropertyKeyConst.AccessKey, propConfig.getAccessKey());
properties.setProperty(PropertyKeyConst.SecretKey, propConfig.getSecretKey());
properties.setProperty(PropertyKeyConst.ONSAddr, propConfig.getOnsAddr());
properties.setProperty("topic", propConfig.getTopic());
return new MqTimerProducer(properties);
} @Bean(initMethod="start", destroyMethod = "shutdown")
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "aliyun.mq.consumer",value = "enabled",havingValue = "true")
public MqConsumer mqConsumer(){
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.ConsumerId, propConfig.getConsumer().getProperty("consumerId"));
properties.setProperty(PropertyKeyConst.AccessKey, propConfig.getAccessKey());
properties.setProperty(PropertyKeyConst.SecretKey, propConfig.getSecretKey());
properties.setProperty(PropertyKeyConst.ONSAddr, propConfig.getOnsAddr());
properties.setProperty("topic", propConfig.getTopic());
return new MqConsumer(properties);
}
}

  

  4. 定义生产者。send方法采用同步的方式将消息内容body,经过一定延迟delay后,发送到指定消息队列topic,且标签为tag(消费者可以根据tag过滤同一个topic的消息)。sendAsync则通过异步的方式发送消息,消息发送完成后,通过指定的回调SendCallback自定义处理。这里默认回调只是以日志进行记录。

public class MqTimerProducer {
private final static Logger LOG = LoggerFactory.getLogger(MqTimerProducer.class);
private Properties properties;
private Producer producer;
private String topic; public MqTimerProducer(Properties properties) {
if (properties == null || properties.get(PropertyKeyConst.ProducerId) == null
|| properties.get(PropertyKeyConst.AccessKey) == null
|| properties.get(PropertyKeyConst.SecretKey) == null
|| properties.get(PropertyKeyConst.ONSAddr) == null
|| properties.get("topic") == null) {
throw new ONSClientException("producer properties not set properly.");
}
this.properties = properties;
this.topic = properties.getProperty("topic");
} public void start() {
this.producer = ONSFactory.createProducer(this.properties);
this.producer.start();
} public void shutdown() {
if (this.producer != null) {
this.producer.shutdown();
}
} public void send(String tag, String body, long delay) {
LOG.info("start to send message. [topic: {}, tag: {}, body: {}, delay: {}]", topic, tag, body, delay);
if (topic == null || tag == null || body == null) {
throw new RuntimeException("topic, tag, or body is null.");
}
Message message = new Message(topic, tag, body.getBytes());
message.setStartDeliverTime(System.currentTimeMillis() + delay);
SendResult result = this.producer.send(message);
LOG.info("send message success. ", result.toString());
} public void sendAsync(String tag, String body, long delay) {
this.sendAsync(tag, body, delay, new DefaultSendCallback());
} public void sendAsync(String tag, String body, long delay, SendCallback sendCallback) {
LOG.info("start to send message async. [topic: {}, tag: {}, body: {}, delay: {}]", topic, tag, body, delay);
if (topic == null || tag == null || body == null) {
throw new RuntimeException("topic, tag, or body is null.");
}
Message message = new Message(topic, tag, body.getBytes());
message.setStartDeliverTime(System.currentTimeMillis() + delay);
this.producer.sendAsync(message, sendCallback);
} setter/getter;
}

  5 定义消费者。subscribe方法对指定topic的某些标签tags进行消息订阅,当有该topic下带有这些tags(满足其中一个即可)的消息到达时,交由messageListener处理。这里定义了一个抽象类AbstractMessageListener,通过模板方法将消息的处理逻辑统一(正常消费,commit;出现异常,重新消费),消费者只需要继承AbstractMessageListener,实现handle方法完成消息消费即可。

public class MqConsumer {

    private final static Logger LOG = LoggerFactory.getLogger(MqConsumer.class);
private Properties properties;
private Consumer consumer;
private String topic; public MqConsumer(Properties properties) {
if (properties == null || properties.get(PropertyKeyConst.ConsumerId) == null
|| properties.get(PropertyKeyConst.AccessKey) == null
|| properties.get(PropertyKeyConst.SecretKey) == null
|| properties.get(PropertyKeyConst.ONSAddr) == null
|| properties.get("topic") == null) {
throw new ONSClientException("consumer properties not set properly.");
}
this.properties = properties;
this.topic = properties.getProperty("topic");
} public void start() {
this.consumer = ONSFactory.createConsumer(properties);
this.consumer.start();
} public void shutdown() {
if (this.consumer != null) {
this.consumer.shutdown();
}
} /**
* @param tags 多个tag用'||'拼接,所有用*
* @param messageListener
*/
public void subscribe(String tags, AbstractMessageListener messageListener) {
LOG.info("subscribe [topic: {}, tags: {}, messageListener: {}]", topic, tags, messageListener.getClass().getCanonicalName());
consumer.subscribe(topic, tags, messageListener);
}
}
public abstract class AbstractMessageListener implements MessageListener {

    private final static Logger LOG = LoggerFactory.getLogger(AbstractMessageListener.class);

    public abstract void handle(String body);

    @Override
public Action consume(Message message, ConsumeContext context) {
LOG.info("receive message. [topic: {}, tag: {}, body: {}, msgId: {}, startDeliverTime: {}]", message.getTopic(), message.getTag(), new String(message.getBody()), message.getMsgID(), message.getStartDeliverTime());
try {
handle(new String(message.getBody()));
LOG.info("handle message success.");
return Action.CommitMessage;
} catch (Exception e) {
//消费失败
LOG.warn("handle message fail, requeue it.", e);
return Action.ReconsumeLater;
}
}
}

  6 前面已完成了所有消息队列服务相关功能的代码实现,要使引用项目自动进行配置,还需定义META-INF/spring.factories文件,将自动配置类赋值给org.springframework.boot.autoconfigure.EnableAutoConfiguration。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ieyecloud.springboot.mq.config.MqAutoConfig

  7 使用

  7.1 pom.xml引入依赖,当前version:1.0.0-SNAPSHOT

<dependency>
<groupId>com.ieyecloud</groupId>
<artifactId>mq-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

  7.2 application配置文件中添加相应配置

aliyun:
mq:
onsAddr: http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet
topic: mulin_topic_test
accessKey: xxx
secretKey: xxx
producer:
enabled: true #为false表示不引入producer,为true则producerId必须提供
producerId: xxx
consumer:
enabled: true #为false表示不引入consumer,为true则consumerId必须提供
consumerId: xxx

  7.3 使用producer,consumer只需要在相应类中依需要注入对应实例

@Autowired
private MqTimerProducer producer; @Autowired
private MqConsumer consumer;

  7.4 consumer监听处理类实现,继承AbstractMessageListener类,实现handle方法即可,如

 @Component
public class QuestionStatusMessageListener extends AbstractMessageListener{ @Autowired
private QuickQuestionService questionService; @Override
public void handle(String s) {
QuestionStatusMessage message = JsonUtil.fromJson(s, QuestionStatusMessage.class);
questionService.updateStatus(message.getQid(), message.getCs(), message.getTs());
}
}

Sping Boot入门到实战之实战篇(一):实现自定义Spring Boot Starter——阿里云消息队列服务Starter的更多相关文章

  1. Spring Boot 入门之缓存和 NoSQL 篇(四)

    原文地址:Spring Boot 入门之缓存和 NoSQL 篇(四) 博客地址:http://www.extlight.com 一.前言 当系统的访问量增大时,相应的数据库的性能就逐渐下降.但是,大多 ...

  2. (36)Spring Boot Cache理论篇【从零开始学Spring Boot】

    Spring Boot Cache理论篇 在上一篇中我们介绍了Spring Boot集成Redis的实战例子,里面使用到了Spring Cache,那么什么是Spring Cache呢,本章将会做一个 ...

  3. 57. Spring 自定义properties升级篇【从零开始学Spring Boot】

    之前在两篇文章中都有简单介绍或者提到过 自定义属性的用法: 25.Spring Boot使用自定义的properties[从零开始学Spring Boot] 51. spring boot属性文件之多 ...

  4. Spring Boot从入门到精通(一)搭建第一个Spring Boot程序

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过 ...

  5. 78. Spring Boot完美使用FastJson解析JSON数据【从零开始学Spring Boot】

    [原创文章,转载请注明出处] 个人使用比较习惯的json框架是fastjson,所以spring boot默认的json使用起来就很陌生了,所以很自然我就想我能不能使用fastjson进行json解析 ...

  6. 56. spring boot中使用@Async实现异步调用【从零开始学Spring Boot】

    什么是"异步调用"? "异步调用"对应的是"同步调用",同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执 ...

  7. 51. spring boot属性文件之多环境配置【从零开始学Spring Boot】

    原本这个章节是要介绍<log4j多环境不同日志级别的控制的>但是没有这篇文章做基础的话,学习起来还是有点难度的,所以我们先一起了解下spring boot属性文件之多环境配置,当然文章中也 ...

  8. (16)Spring Boot使用Druid(编程注入)【从零开始学Spring Boot】

    在上一节使用是配置文件的方式进行使用druid,这里在扩散下使用编程式进行使用Druid,在上一节我们新建了一个类:DruidConfiguration我在这个类进行编码: package com.k ...

  9. 自定义spring boot的自动配置

    文章目录 添加Maven依赖 创建自定义 Auto-Configuration 添加Class Conditions 添加 bean Conditions Property Conditions Re ...

随机推荐

  1. Linux常用软件

    网络应用 即时聊天 pidgin 支持多协议,如msn, yahoo, icq, irc ... eva QQ 聊天客户端,KDE程序,推荐 Skype 网络电话,网络聊天,推荐 lumaqq Jav ...

  2. 3.3 与Cache相关的PCI总线事务

    PCI总线规范定义了一系列与Cache相关的总线事务,以提高PCI设备与主存储器进行数据交换的效率,即DMA读写的效率.当PCI设备使用DMA方式向存储器进行读写操作时,一定需要经过HOST主桥,而H ...

  3. java.lang.ArrayIndexOutOfBoundsException

    1.错误描述 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0 at com.you.m ...

  4. 初识 systemd

    从 init 系统说起 linux 操作系统的启动首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化.内核初始化的最后一步就是启动 PID ...

  5. .net core 环境安装失败,错误:0x80072EE2

    安装[DotNetCore.1.0.1-VS2015Tools.Preview2.0.3.exe]失败,提示这个界面 查看log发现,发现猫腻,然后copy下链接,用迅雷手动下载[AspNetCore ...

  6. meta的各种参数

    <!DOCTYPE html> <!-- 使用 HTML5 doctype,不区分大小写 --> <html lang="zh-cmn-Hans"&g ...

  7. class-k近邻算法kNN

    1 k近邻算法2 模型2.1 距离测量2.2 k值选择2.3 分类决策规则3 kNN的实现--kd树3.1 构造kd树3.2 kd树搜索 1 k近邻算法 k nearest neighbor,k-NN ...

  8. 畅通工程 HDU - 1863

    省政府"畅通工程"的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可).经过调查评估,得到的统计表中列出了有可能建设公路的若干条道 ...

  9. 对于多线程下Servlet以及Session的一些理解

    今天,小伙伴突然问到了Servlet是不是线程安全的问题.脑子当时一卡壳,只想到了单实例多线程.这里做一些总结. Servlet体系是建立在Java多线程的基础之上的,它的生命周期是由Tomcat来维 ...

  10. 【BZOJ4816】数字表格(莫比乌斯反演)

    [BZOJ4816]数字表格(莫比乌斯反演) 题面 BZOJ 求 \[\prod_{i=1}^n\prod_{j=1}^mf[gcd(i,j)]\] 题解 忽然不知道这个要怎么表示... 就写成这样吧 ...