Spring boot 自定义Starter

一、使用场景
开发中,每建立一个项目,各插件重复使用,如项目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参数值信息是否启用成功。







猜你喜欢

转载自blog.csdn.net/u013218443/article/details/80665881