一、使用场景
开发中,每建立一个项目,各插件重复使用,如项目A使用redis 插件,项目B 也同时使用。 因此,使用SpringBoot中自定义Starter,方便解决项目中的插件重复引用。
二、 设计需求
1. pom引入
2. 注解开启
3. 配置注入
三、环境设置:
SpringBoot 1.5.10.RELEASE
jdk 1.8
maven 3.5.0
idea
四、使用示例
例:使用阿里ONS插件
1.在项目component中新建Module,命名为ons
设置 xml
<parent>
<groupId>com.example</groupId>
<artifactId>compontent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ons</artifactId>
<name>ons</name>
<description>Demo project for Spring Boot</description>
<packaging>jar</packaging>
2.在项目com.example.ons包下新建以下包
annotation 自定义注解包
client 插件API
constants 常量包
property 配置文件包
3.在新建的annotation 包下编写自定义注解@EnableONS
package com.example.ons.annotation;
import com.example.ons.client.MQClient;
import com.example.ons.property.AliyunOnsProperty;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author anqi
* @version 1.0
* @desc ONS注解
* @date 2018/6/11
* @see
* @since 1.0
*/
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Import({AliyunOnsProperty.class, MQClient.class})
public @interface EnableONS {
}
4.在constants包下配置自己项目中使用的常量值
package com.example.ons.constants;
import javax.annotation.PostConstruct;
/**
* @author anqi
* @version 1.0
* @desc MQ 常量
* @date 2018/6/11
* @see
* @since 1.0
*/
public class MQConstants {
/**
* 默认TOPIC
*/
public static String DEFAULT_TOPIC;
private String profile;
@PostConstruct
public void init() {
switch (profile){
case "dev":
DEFAULT_TOPIC = TOPIC.DEVELOP_DEFAULT_TOPIC.getName();
break;
case "uat":
DEFAULT_TOPIC = TOPIC.DEFAULT_TOPIC.getName();
break;
case "online":
DEFAULT_TOPIC = TOPIC.ONLINE_DEFAULT_TOPIC.getName();
break;
case "pre-online":
DEFAULT_TOPIC = TOPIC.PRE_ONLINE_DEFAULT_TOPIC.getName();
break;
}
if (null == DEFAULT_TOPIC || "".equals(DEFAULT_TOPIC)) {
throw new RuntimeException("DEFAULT_TOPIC 不能为空!");
}
}
/**
* MQ topic
*/
public enum TOPIC {
/* uat - 环境 */
DEFAULT_TOPIC("DEFAULT_TOPIC"),
/* dev - 环境 */
DEVELOP_DEFAULT_TOPIC("DEVELOP_DEFAULT_TOPIC"),
/* online - 环境 */
ONLINE_DEFAULT_TOPIC("ONLINE_DEFAULT_TOPIC"),
/* pre-online - 环境 */
PRE_ONLINE_DEFAULT_TOPIC("PRE_ONLINE_DEFAULT_TOPIC");
private String name;
TOPIC (String name) {
this.name = name;
}
public String getName() {
return name;
}
}
/**
* MQ tag
*/
public enum TAG {
TAG_DEFAULT,
/*发送 短信*/
TAG_SEND_SMS,
/*发送 邮件*/
TAG_SEND_MAIL,
/*发送 钉钉消息*/
TAG_SEND_DINGDING,
/*发送 支付成功通知*/
TAG_SEND_APPT_PAY_SUCCESS,
/*套支付 支付成功通知*/
TAG_SEND_PACKAGE_PAY_SUCCESS,
/*自由训练卡 支付成功通知*/
TAG_SEND_TRAINCARD_PAY_SUCCESS
}
}
5.在property包下设置配置参数接收类
package com.example.ons.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author anqi
* @version 1.0
* @desc 阿里云 ons 配置参数
* @date 2018/6/11
* @see
* @since 1.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.ons")
public class AliyunOnsProperty {
/**
* 订阅的 topic
*/
private String topic;
/**
* 在 MQ 控制台创建的 Producer ID
*/
private String producerId;
/**
* 在 MQ 控制台创建的 Consumer ID
*/
private String consumerId;
/**
* 鉴权用 AccessKey,在阿里云服务器管理控制台创建
*/
private String accessKey;
/**
* 鉴权用 SecretKey,在阿里云服务器管理控制台创建
*/
private String secretKey;
/**
* 设置 TCP 接入域名
*/
private String onsAddr;
}
6.在client编写插件连接信息,暴露插件使用接口
package com.example.ons.client;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.openservices.ons.api.*;
import com.aliyun.openservices.ons.api.exception.ONSClientException;
import com.example.ons.property.AliyunOnsProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Properties;
/**
* @author anqi
* @version 1.0
* @desc 阿里云 mq
* @date 2018/6/11
* @see
* @since 1.0
*/
public class MQClient {
private final static Logger LOGGER = LoggerFactory.getLogger(MQClient.class);
@Autowired
private AliyunOnsProperty aliyunOnsProperty;
private static AliyunOnsProperty aliyunOnsPropertyBean = new AliyunOnsProperty();
/**
* ons mq 实例
*/
private static Producer producer;
/**
* 项目使用的 topic, 数据自${aliyun.ons.topic} 读取
*/
public static String TOPIC;
/**
* 初始化mq
*/
@PostConstruct
public synchronized void init() {
BeanUtils.copyProperties(aliyunOnsProperty, aliyunOnsPropertyBean);
long now = System.currentTimeMillis();
LOGGER.info("获取ons配置信息...");
checkConfig(aliyunOnsPropertyBean);
LOGGER.info("MQClient.Producer 初始化...");
LOGGER.info("MQClient.Producer producerId [{}]", aliyunOnsPropertyBean.getProducerId());
LOGGER.info("MQClient.Producer ONSAddr [{}]", aliyunOnsPropertyBean.getOnsAddr());
Properties properties = new Properties();
// 您在 MQ 控制台创建的 Producer ID
properties.put(PropertyKeyConst.ProducerId, aliyunOnsPropertyBean.getProducerId());
// 鉴权用 AccessKey,在阿里云服务器管理控制台创建
properties.put(PropertyKeyConst.AccessKey, aliyunOnsPropertyBean.getAccessKey());
// 鉴权用 SecretKey,在阿里云服务器管理控制台创建
properties.put(PropertyKeyConst.SecretKey, aliyunOnsPropertyBean.getSecretKey());
// 设置 TCP 接入域名(此处以公共云的公网接入为例)
properties.put(PropertyKeyConst.ONSAddr, aliyunOnsPropertyBean.getOnsAddr());
producer = ONSFactory.createProducer(properties);
// 在发送消息前,必须调用 start 方法来启动 Producer,只需调用一次即可
producer.start();
LOGGER.info("MQClient.Producer 启动完成, 总耗时 {} 毫秒!", (System.currentTimeMillis() - now));
// 设置topic
TOPIC = aliyunOnsPropertyBean.getTopic();
LOGGER.info("设置默认TOPIC: {}", aliyunOnsPropertyBean.getTopic());
LOGGER.info("MQClient.Producer create success...");
}
/**
* 重连
*/
public static synchronized void reInit() {
LOGGER.info("自动重连 ons ...");
long now = System.currentTimeMillis();
LOGGER.info("获取ons配置信息...");
//TODO 验证重连时配置信息是否存在
checkConfig(aliyunOnsPropertyBean);
LOGGER.info("MQClient.Producer 初始化...");
LOGGER.info("MQClient.Producer producerId [{}]", aliyunOnsPropertyBean.getProducerId());
LOGGER.info("MQClient.Producer ONSAddr [{}]", aliyunOnsPropertyBean.getOnsAddr());
Properties properties = new Properties();
properties.put(PropertyKeyConst.ProducerId, aliyunOnsPropertyBean.getProducerId());
properties.put(PropertyKeyConst.AccessKey, aliyunOnsPropertyBean.getAccessKey());
properties.put(PropertyKeyConst.SecretKey, aliyunOnsPropertyBean.getSecretKey());
properties.put(PropertyKeyConst.ONSAddr, aliyunOnsPropertyBean.getOnsAddr());
producer = ONSFactory.createProducer(properties);
producer.start();
LOGGER.info("MQClient.Producer 启动完成, 总耗时 {} 毫秒!", (System.currentTimeMillis() - now));
LOGGER.info("自动重连 ons 完成...");
// 设置topic
TOPIC = aliyunOnsPropertyBean.getTopic();
LOGGER.info("设置默认TOPIC: {}", aliyunOnsPropertyBean.getTopic());
LOGGER.info("MQClient.Producer create success...");
}
/**
* 销毁 MQ方法(需在应用停止时调用)
*/
@PreDestroy
public synchronized void destroy() {
long now = System.currentTimeMillis();
LOGGER.info("销毁 MQClient.Producer 开始...");
producer.shutdown();
LOGGER.info("销毁 MQClient.Producer 完成...消耗:{}毫秒!", System.currentTimeMillis() - now);
}
/**
* 同步发送消息,只要不抛异常就表示成功
*
* @param tag Message Tag
* @param body Message Body, 任意二进制形式的数据, 需要 Producer 与 Consumer 协商好一致的序列化和反序列化方式
* @param key 代表消息的业务关键属性,全局唯一,以方便您在无法正常收到消息情况下,可通过 MQ 控制台查询消息并补发; 注意:不设置也不会影响消息正常收发
*/
public static SendResult send(String tag, byte[] body, String key) {
if (null == TOPIC || "".equals(TOPIC)) {
throw new RuntimeException(" TOPIC 未配置!");
}
Message msg = new Message(TOPIC, tag, body);
if (null != key && !"".equals(key)) {
msg.setKey(key);
}
SendResult sendResult = null;
try {
// 发送消息,只要不抛异常就是成功
sendResult = producer.send(msg);
} catch (ONSClientException e) {
LOGGER.error(e.getMessage());
MQClient.reInit();
try {
sendResult = producer.send(msg);
} catch (Exception exception) {
LOGGER.error("MQ 重连后,发送异常: {}!", exception.getMessage());
e.printStackTrace();
}
}
// 发送成功 打印 Message ID,以便用于消息发送状态查询
LOGGER.info("Send Message success. Message ID is: {}; ", sendResult.getMessageId());
return sendResult;
}
/**
* 同步发送消息,只要不抛异常就表示成功
*
* @param topic 消息所属的 Topic 名称
* @param tag Message Tag
* @param body Message Body, 任意二进制形式的数据, 需要 Producer 与 Consumer 协商好一致的序列化和反序列化方式
* @param key 代表消息的业务关键属性,全局唯一,以方便您在无法正常收到消息情况下,可通过 MQ 控制台查询消息并补发; 注意:不设置也不会影响消息正常收发
*/
public static SendResult send(String topic, String tag, byte[] body, String key) {
Message msg = new Message(topic, tag, body);
if (null != key && !"".equals(key)) {
msg.setKey(key);
}
SendResult sendResult = null;
try {
// 发送消息,只要不抛异常就是成功
sendResult = producer.send(msg);
} catch (ONSClientException e) {
LOGGER.error(e.getMessage());
MQClient.reInit();
try {
sendResult = producer.send(msg);
} catch (Exception exception) {
LOGGER.error("MQ 重连后,发送异常: {}!", exception.getMessage());
e.printStackTrace();
}
}
// 发送成功 打印 Message ID,以便用于消息发送状态查询
LOGGER.info("Send Message success. Message ID is: {}; ", sendResult.getMessageId());
return sendResult;
}
/**
* 发送消息,异步Callback形式
*
* @param topic 消息所属的 Topic 名称
* @param tag Message Tag
* @param body Message Body, 任意二进制形式的数据, 需要 Producer 与 Consumer 协商好一致的序列化和反序列化方式
* @param key 代表消息的业务关键属性,全局唯一,以方便您在无法正常收到消息情况下,可通过 MQ 控制台查询消息并补发; 注意:不设置也不会影响消息正常收发
*/
public static SendResult[] sendAsync(String topic, String tag, byte[] body, String key) {
Message msg = new Message(topic, tag, body);
if (null != key && !"".equals(key)) {
msg.setKey(key);
}
final SendResult[] result = {null};
try {
// 发送消息,异步Callback形式
producer.sendAsync(msg, new SendCallback() {
@Override
public void onSuccess(final SendResult sendResult) {
assert sendResult != null;
result[0] = sendResult;
LOGGER.info(JSONObject.toJSONString(sendResult));
}
@Override
public void onException(final OnExceptionContext context) {
//出现异常意味着发送失败,为了避免消息丢失,建议缓存该消息然后进行重试。
LOGGER.error(context.getException().getMessage());
}
});
} catch (ONSClientException e) {
MQClient.reInit();
producer.sendAsync(msg, new SendCallback() {
@Override
public void onSuccess(final SendResult sendResult) {
assert sendResult != null;
result[0] = sendResult;
LOGGER.info(JSONObject.toJSONString(sendResult));
}
@Override
public void onException(final OnExceptionContext context) {
//出现异常意味着发送失败,为了避免消息丢失,建议缓存该消息然后进行重试。
LOGGER.error(context.getException().getMessage());
}
});
}
// 打印 Message ID,以便用于消息发送状态查询
LOGGER.info("Send Message success. Message ID is: {}; ", result[0].getMessageId());
return result;
}
/**
* 校验配置属性是否配置
*/
private static void checkConfig(AliyunOnsProperty onsProperty) {
Assert.notNull(onsProperty, "aliyun.ons 未配置!");
if (null == onsProperty.getTopic() || "".equals(onsProperty.getTopic())) {
throw new RuntimeException("aliyun.ons.topic 未配置!");
}
if (null == onsProperty.getProducerId() || "".equals(onsProperty.getProducerId())) {
throw new RuntimeException("aliyun.ons.producerId 未配置!");
}
if (null == onsProperty.getAccessKey() || "".equals(onsProperty.getAccessKey())) {
throw new RuntimeException("aliyun.ons.accessKey 未配置!");
}
if (null == onsProperty.getSecretKey() || "".equals(onsProperty.getSecretKey())) {
throw new RuntimeException("aliyun.ons.secretKey 未配置!");
}
if (null == onsProperty.getOnsAddr() || "".equals(onsProperty.getOnsAddr())) {
throw new RuntimeException("aliyun.ons.onsAddr 未配置!");
}
}
}
7. 父pom文件修改
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>compontent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>ons</module>
<module>testcommpontent</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--aliyun-->
<aliyun.ons.client.version>1.7.0.Final</aliyun.ons.client.version>
<compontent.version>1.0.0-SNAPSHOT</compontent.version>
</properties>
<dependencies>
<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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
<!--aliyun - mq-->
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>ons-client</artifactId>
<version>${aliyun.ons.client.version}</version>
</dependency>
</dependencies>
</project>
五、使用测试
1.在test pom下添加ons jar包依赖
<dependency>
<groupId>com.example.ons</groupId>
<artifactId>ons</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
2.在resources 包下application.yml 文件中添加以下配置信息
# 阿里云配置
aliyun:
# ons mq
ons:
topic: you-topic
producer-id: you-producer-id
access-key: you-access-key
secret-key: you-secret-key
ons-addr: you-ons-addr
3.编写TestcommpontentApplication启动类
4.添加@EnableONS 注解
package com.example.compontent.test;
import com.example.ons.Annotation.EnableONS;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableONS
public class TestcommpontentApplication {
public static void main(String[] args) {
SpringApplication.run(TestcommpontentApplication.class, args);
}
}
5.在MQClient中获取配置文件信息处标记断点,Debug模式下启动TestcommpontentApplication启动类,观察aliyunOnsPropertyBean参数值信息是否启用成功。