Sping Boot入门到实战之实战篇(一):实现自定义Spring Boot Starter——阿里云消息队列服务Starter
在 Sping Boot入门到实战之入门篇(四):Spring Boot自动化配置 这篇中,我们知道Spring Boot自动化配置的实现,主要由如下几部分完成:
- @EnableAutoConfiguration注解
- SpringApplication类
- spring-boot-autoconfigure jar包
- 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的更多相关文章
- Spring Boot 入门之缓存和 NoSQL 篇(四)
原文地址:Spring Boot 入门之缓存和 NoSQL 篇(四) 博客地址:http://www.extlight.com 一.前言 当系统的访问量增大时,相应的数据库的性能就逐渐下降.但是,大多 ...
- (36)Spring Boot Cache理论篇【从零开始学Spring Boot】
Spring Boot Cache理论篇 在上一篇中我们介绍了Spring Boot集成Redis的实战例子,里面使用到了Spring Cache,那么什么是Spring Cache呢,本章将会做一个 ...
- 57. Spring 自定义properties升级篇【从零开始学Spring Boot】
之前在两篇文章中都有简单介绍或者提到过 自定义属性的用法: 25.Spring Boot使用自定义的properties[从零开始学Spring Boot] 51. spring boot属性文件之多 ...
- Spring Boot从入门到精通(一)搭建第一个Spring Boot程序
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过 ...
- 78. Spring Boot完美使用FastJson解析JSON数据【从零开始学Spring Boot】
[原创文章,转载请注明出处] 个人使用比较习惯的json框架是fastjson,所以spring boot默认的json使用起来就很陌生了,所以很自然我就想我能不能使用fastjson进行json解析 ...
- 56. spring boot中使用@Async实现异步调用【从零开始学Spring Boot】
什么是"异步调用"? "异步调用"对应的是"同步调用",同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执 ...
- 51. spring boot属性文件之多环境配置【从零开始学Spring Boot】
原本这个章节是要介绍<log4j多环境不同日志级别的控制的>但是没有这篇文章做基础的话,学习起来还是有点难度的,所以我们先一起了解下spring boot属性文件之多环境配置,当然文章中也 ...
- (16)Spring Boot使用Druid(编程注入)【从零开始学Spring Boot】
在上一节使用是配置文件的方式进行使用druid,这里在扩散下使用编程式进行使用Druid,在上一节我们新建了一个类:DruidConfiguration我在这个类进行编码: package com.k ...
- 自定义spring boot的自动配置
文章目录 添加Maven依赖 创建自定义 Auto-Configuration 添加Class Conditions 添加 bean Conditions Property Conditions Re ...
随机推荐
- linux kvm虚拟机快速构建及磁盘类型
KVM命令管理 virsh命令:用来管理各虚拟机的接口命令查看/创建/停止/关闭...支持交互模式格式:virsh 控制指令 [虚拟机名称] [参数] [root@room1pc01 桌面]# vir ...
- Django2.0文档
第四章 模板 1.标签 (1)if/else {% if %} 标签检查(evaluate)一个变量,如果这个变量为真(即,变量存在,非空,不是布尔值假),系统会显示在 {% if %} 和 {% e ...
- python︱函数、for、_name_杂记
新手入门python,开始写一些简单函数,慢慢来,加油~ 一.函数 def myadd(a=1,b=100): result = 0 i = a while i <= b: # 默认值为1+2+ ...
- Cortex-M3
大家听说过Cortex-M3吗?在嵌入式处理器的世界,cortex-M3是一位人见人爱的后生.它的成本和功耗低,可配置性很高.如今,很多ARM的工程师加入了cortex-M3的学习与开发中,WIZne ...
- SpringBoot特性
一.SpringBoot解决的问题 1.使编码变得简单 2.使配置变得简单 3.使部署变得简单 4.使监控变得简单 二.springboot主要特性 1.遵循习惯优于配置的原则.使用springboo ...
- Luogu P3412 仓鼠找$sugar$ $II$
Luogu P3412 仓鼠找\(sugar\) \(II\) 题目大意: 给定一棵\(n\)个点的树, 仓鼠每次移动都会等概率选择一个与当前点相邻的点,并移动到此点. 现在随机生成一个起点.一个终点 ...
- AngularJS中Model和Controller传值问题
最近由于工作原因,开始写点前端的东西.前两天刚开始了解AngularJS这门技术,当然,新手免不了会爬坑! 今天分享一篇关于--> 模型传参给Controller的实例: 需求: 具体是 首先 ...
- [.Net Core] 简单使用 Mvc 内置的 Ioc
简单使用 Mvc 内置的 Ioc 本文基于 .NET Core 2.0. 鉴于网上的文章理论较多,鄙人不才,想整理一份 Hello World(Demo)版的文章. 目录 场景一:简单类的使用 场景二 ...
- centos6.5下 hdp-2.4.2安装
(1)准备工作 /usr/sbin/sestatus -v getenforce1./usr/sbin/sestatus -v ##如果SELinux status参数为enabled即为开启状态SE ...
- log4j日志输出性能优化-缓存、异步
转载 1.log4j已成为大型系统必不可少的一部分,log4j可以很方便的帮助我们在程序的任何位置输出所要打印的信息,便于我们对系统在调试阶段和正式运行阶段对问题分析和定位.由于日志级别的不同,对系 ...