一、布景

Spring Boot现已呈现了好多年了,可是或许许多开发者,一向没有合适的机会开发归于自己的Spring Boot Starter组件,今日共享一个实在作业中的比如,共享怎么开发一个Spring Boot Starter组件,以及处理这个需求的进程。

二、Spring Boot Starter介绍

首要来看一段AI大模型对它的介绍:

Spring Boot Starter 是Spring Boot生态体系中的重要组成部分,它为开发者供给了一种快速发动Spring Boot应用程序的办法。

首要,Spring Boot Starter是什么?

Spring Boot Starter能够被理解为一种依靠的集合,也能够看作是一个空的项目,它由pom.xml文件装备了一堆jar包的组合。这些jar包都是预先装备好的,而且包含了各种不同的功用模块。例如,Spring

Boot Starter Web包含了用于处理HTTP恳求的类库,Spring Boot Starter Data JPA包含了用于数据访问的类库等。

其次,Spring Boot Starter处理了什么问题?

Spring Boot Starter处理了手动装备大量依靠项和参数的问题。在Spring Boot之前,假如要开发一个Web应用程序,需求手动增加许多依靠项,如Servlet、JSP、JSTL等,而且还需求装备许多参数,如数据源、事务管理器等。而经过运用Spring Boot Starter,开发者只需求增加一个Starter依靠,就能够轻松地集成各种不同的功用模块,而无需关怀底层的装备和集成细节。

最后,Spring Boot Starter的价值是什么?

Spring Boot Starter的价值在于它能够进步开发功率和代码质量,一同削减开发成本和复杂度。经过运用Starter,开发者能够专心于事务逻辑的完结,而不需求关怀底层的装备和集成细节。别的,Starter还支撑更快的迭代和部署,由于它们一般包含了一些可重用的依靠库和主动装备类。

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

关于日常的技术与事务学习,能够经常思考这三点是十分重要的。

三、本节需求描绘

本节经过针对一个用户行为剖析埋点途径的Java SDK进行封装,开宣布一个Spring Boot Starter组件,来让咱们知道怎么开发一个Starter。

虽然咱们或许没有注册GrowingIO的用户账号,可是不影响咱们开发Starter组件。

先来让AI大模型告知咱们Growing IO是什么软件,处理了什么样的问题?

GrowingIO是一个一站式数据增加引擎整体方案服务商,以数据智能剖析为中心,经过构建客户数据途径,打造增加营销闭环,协助企业提升数据驱动能力,赋能商业决策、完结事务增加。它专心于零售、电商、稳妥、酒旅航司、教育、内容社区等行业,致力于协助企业运用数据完结事务增加。

GrowingIO处理了企业在数据驱动事务增加方面的痛点。首要,它供给了一站式的处理方案,协助企业构建客户数据途径,整合不同途径的数据,完结数据的一致管理和剖析。其次,GrowingIO供给了强大的数据收集和剖析功用,能够实时剖析用户行为数据,然后更好地了解用户需求和行为,优化产品规划和运营战略。此外,GrowingIO还供给了增加营销闭环的服务,协助企业拟定并履行根据数据的营销战略,进步用户留存率、转化率和变现能力。

经过这个介绍,咱们知道了它是一个用户行为剖析埋点剖析的一个软件,能够在咱们事务体系中的一些关键的节点,埋上一个作业操作,上报给服务端的体系,然后进行一些动作与实践的行为剖析。

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

那么就防止不了要开发客户端的SDK,去上报一些动作的作业信息,这儿咱们选择了Java SDK。

咱们先来看没有运用Starter的时候,怎么运用这个Java SDK,这儿不详细描绘,咱们能够看下如下链接中的介绍:

官方SDK阐明文档:

docs.growingio.com/v3/develope…

官方SDK源码地址:

github.com/growingio/g…

建议咱们花1-5分钟过一下上面的比如,明白它做的作业。

为什么,要做这样一个Starter呢。

我的原因是:

(1)、现在它家暂未供给揭露的Starter组件,用它们这个原始的组件,集成起来不便利,一同需求自界说properties文件,无法在微服务架构下比较简略的与装备中心集成。

(2)、现在它的growingio sdk存在版本不同的区别,后续sdk版本晋级,和事务代码耦合,或许仍需改动,所以需求进行二次封装,选用灵活的starter的办法。

(3)、屏蔽原生的java SDK API完结,一同供给简略且友爱的API便利调用,从必定程度上对事务代码进行防腐与主动权操控。

(4)、GrowingIO默许的Java埋点SDK默许的API调用办法,封装较少,事务开发时或许需求进行某些转化与一致处理等。

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

以上这几点,也算是咱们本次的一个需求,根据以上几点咱们进行封装,一同这几点也是带着问题去思考的描绘,开发完结后,规划上是怎么处理这几点的。日常面试中,假如自己主动的想表达自己具有封装Starter的经验,那么或许面试官也会问你为什么要做这样一个作业。

四、Spring Boot Starter封装的套路

任何作业做起来都是有套路的,Spring Boot Starter也不例外,把握了某个作业的套路,会让自己具有一种结构化的表达和内容的共享,关于类似的东西,其实只需求了解套路是什么,第1过程,第2过程是什么。。。。这样内涵的实质原因不会变,剩下的仅仅时刻上的问题。

这儿共享一下简略的Starter封装的套路,本文暂时只介绍一个最小装备的Starter的开发办法。

(1)、第1步:界说一个XXXProperties的类文件,用于抽象化原有的装备特点与增加新的特点。

(2)、第2步:将中心的事务处理类,初始化中心事务处理类并注入到IOC中,一般写在XXXAutoConfiguration的类文件文件中。

(3)、第3步:为了防止运用者与Starter中包名路径不一致,声明一个spring.factories的文件,来供给一种扫描类到IOC中的途径。

以上是中心的3点,具有这根底的3点,能够完结一个根底的starter小组件。

在高档一点,咱们能够持续增加如下内容:

(4)、界说一个XXXXEnable形式+@Import形式的注解,用于操控Starter是否生效与动态注册对象Bean到IOC容器中。

(5)、运用许多注解,来区别当时starter组件中的先后顺序、环境区别、兼容性等等。

(6)、自界说一个AOP类和一个注解,用于动态标识哪些办法进行事务埋点操作,防止必定程度上的代码侵入。

总结如下:

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

五、GrowingIO Spring Boot Starter封装开端

5.1、第一步:新建项目工程,并抽象化SDK的gio.properties文件内容

1、新建工程,工程称号这儿界说为:growingio-spring-boot-stater

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

2、导入GrowingIO SDK等相关依靠

 <dependencies>
        <dependency>
            <groupId>io.growing.sdk.java</groupId>
            <artifactId>growingio-java-sdk</artifactId>
            <version>1.0.8</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.13</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>2.4.13</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.26</version>
        </dependency>
    </dependencies>

3、创立项目结构与抽象化gio.properties文件(该文件的样例在上面共享那个Github链接中),并创立相关的常量类,如下所示,中心的Properties代码如下:

@ConfigurationProperties(GrowingioProperties.GROWINGIO_PREFIX)
@Data
public class GrowingioProperties {
    /**
     * 默许前缀
     */
    public static final String GROWINGIO_PREFIX = "growingio";
    /**
     * 项目收集端地址
     */
    private String apiHost;
    /**
     * 项目ID
     */
    private String projectId;
    /**
     * 音讯发送间隔时刻,单位ms(默许 100)
     */
    private Integer sendMsgInterval = 100;
    /**
     * 音讯发送线程数量,默许为3
     */
    private Integer sendMsgThread = 3;
    /**
     * 音讯行列巨细
     */
    private Integer msgStoreQueueSize = 500;
    /**
     * 数据紧缩 false:不紧缩, true:紧缩 不紧缩可节约cpu,紧缩可省带宽
     */
    private Boolean compress = true;
    /**
     * 日志输出等级(debug | error)
     */
    private String loggerLevel = "debug";
    /**
     * 自界说日志输出完结类
     */
    private String loggerImplemention = "cn.aijavapro.growingio.log.GrowingioLogger";
    /**
     * 运转形式,test:仅输出音讯体,不发送音讯,production:发送音讯
     */
    private String runMode = "test";
    /**
     * http 衔接超时时刻,默许2000ms
     */
    private Integer connectionTimeout = 2000;
    /**
     * http 衔接读取时刻,默许2000ms
     */
    private Integer readTimeout = 2000;
    /**
     * 是否启用:自界说特点:标识是否启用,默许为不启用,非growing io 官方特点
     */
    private Boolean enable = false;
}

关于一个装备特点类来说,一般会运用ConfigurationProperties注解来界说特点装备。这个也是套路

然后GrowingioConstant的代码如下,仅仅封装了SDK的特点文件中的Key:

public class GrowingioConstant {
    /**
     * 项目收集端地址
     */
    public static final String API_HOST_KEY = "api.host";
    /**
     * 项目ID
     */
    public static final String PROJECT_ID_KEY = "project.id";
    /**
     * 音讯发送间隔时刻,单位ms(默许 100)
     */
    public static final String SEND_MSG_INTERVAL_KEY = "send.msg.interval";
    /**
     * 音讯发送线程数量,默许为3
     */
    public static final String SEND_MSG_THREAD_KEY = "send.msg.thread";
    /**
     * 音讯行列巨细
     */
    public static final String MSG_STORE_QUEUE_SIZE_KEY = "msg.store.queue.size";
    /**
     * 数据紧缩 false:不紧缩, true:紧缩 不紧缩可节约cpu,紧缩可省带宽
     */
    public static final String COMPRESS_KEY = "compress";
    /**
     * 日志输出等级(debug | error)
     */
    public static final String LOGGER_LEVEL_KEY = "logger.level";
    /**
     * 自界说日志输出完结类
     */
    public static final String LOGGER_IMPL_KEY = "logger.implemention";
    /**
     * 运转形式,test:仅输出音讯体,不发送音讯,production:发送音讯
     */
    public static final String RUN_MODE_KEY = "run.mode";
    /**
     * http 衔接超时时刻,默许2000ms
     */
    public static final String CONNECTION_TIMEOUT_KEY = "connection.timeout";
    /**
     * http 衔接读取时刻,默许2000ms
     */
    public static final String READ_TIMEOUT_KEY = "read.timeout";
}

咱们自界说一个Growing IO日志的完结类,用于测验阶段的日志输出,不依靠服务端依然能够运用,如下所示:

public class GrowingioLogger implements GioLoggerInterface {
    private static final Logger logger = LoggerFactory.getLogger(GrowingioLogger.class);
    @Override
    public void debug(String s) {
        logger.info("测验阶段日志输出:{}", s);
    }
    @Override
    public void error(String s) {
        logger.error("测验阶段日志输出:{}", s);
    }
}

然后工程结构截图如下所示:

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

5.2、第二步:界说一个恳求参数类,封装一个Service事务类

此过程的意图也是为运用者供给一个比较清晰易懂的进口事务处理类,防止运用者在事务代码中直接调用原生SDK,由于这个SDK他们家其他版本的与这个有些不一样,防止污染事务代码。

1、首要咱们看下原生的Java调用代码:

//作业行为音讯体
GIOEventMessage eventMessage = new GIOEventMessage.Builder()
    .eventTime(System.currentTimeMillis())            // 默许为体系当时时刻,选填
    .eventKey("3")                                    // 作业标识 (必填)
    .eventNumValue(1.0)                               // 打点作业数值 (选填)
    .loginUserId("417abcabcabcbac")                   // 带用登陆用户ID的 (选填)
    .addEventVariable("product_name", "苹果")          // 作业级变量 (选填)
    .addEventVariable("product_classify", "生果")      // 作业级变量 (选填)
    .addEventVariable("product_price", 14)            // 作业级变量 (选填)
    .build();
//上传作业行为音讯到服务器
GrowingAPI.send(eventMessage);

能够看到这段代码封装了当时一个事务的登录用户信息,和一些作业的变量,可是Growing IO SDK中,未供给其他addEventVariable办法,或许有些不便利,咱们能够封装几个中心的参数,通用的参数在Starter层面一致操控,所以咱们在封装一层恳求参数类,并供给一个Map的详细数据类:

public class EventMessageRequest implements Serializable {
    private static final long serialVersionUID = 892840357641768298L;
    private String eventKey;
    private String userId;
    private Map<String,Object> eventVariableMap;
    public EventMessageRequest(String eventKey, String userId, 
            Map<String, Object> eventVariableMap) {
        this.eventKey = eventKey;
        this.userId = userId;
        this.eventVariableMap = eventVariableMap;
    }
}

然后封装一个事务处理完结,一同界说了反常捕获,防止影响到主流程:

public class GrowingioServiceImpl implements GrowingioService {
    private static final Logger logger = LoggerFactory.getLogger(GrowingioServiceImpl.class);
    @Override
    public void sendEventMessage(EventMessageRequest request){
        try{
            if(StringUtils.isEmpty(request.getEventKey()) && StringUtils.isEmpty(request.getUserId())){
                return;
            }
            GIOEventMessage.Builder builder = new GIOEventMessage.Builder()
                    .eventTime(System.currentTimeMillis())
                    .eventKey(request.getEventKey())
                    .loginUserId(request.getUserId());
            Set<Map.Entry<String, Object>> entries = request.getEventVariableMap().entrySet();
            for(Map.Entry<String, Object> entry : entries){
                String key = entry.getKey();
                Object value = entry.getValue();
                if(value != null){
                    if(value instanceof String){
                        builder.addEventVariable(key, ((String) value));
                    }else if(value instanceof Long){
                        builder.addEventVariable(key, ((Long) value).toString());
                    }else if(value instanceof Integer){
                        builder.addEventVariable(key, ((Integer) value));
                    }else if(value instanceof Double){
                        builder.addEventVariable(key, ((Double) value));
                    }else if(value instanceof BigDecimal){
                        builder.addEventVariable(key, ((BigDecimal) value).doubleValue());
                    }else {
                        logger.warn("not support event variable value type:{}", key);
                    }
                }
            }
            //作业行为音讯体
            GIOEventMessage eventMessage = builder.build();
            //上传作业行为音讯到服务器,底层堵塞行列异步处理
            GrowingAPI.send(eventMessage);
        }catch (Exception e){
            logger.error("send growingio event message error:{}" , e.getMessage(),e);
        }
    }
}

5.3、第三步:自界说一个主动安装类,而且界说一个spi文件

1、首要界说一个autoConfiguration类,代码如下:

@Configuration
@EnableConfigurationProperties(GrowingioProperties.class)
public class GrowingioAutoConfiguration{
    private static final Logger logger = LoggerFactory.getLogger(GrowingioAutoConfiguration.class);
    @Autowired
    protected GrowingioProperties growingioProperties;
    @Bean
    public GrowingioService growingioService(){
        return new GrowingioServiceImpl();
    }
    public void checkProperties() {
        //校验并开端查看是否装备必填的特点
        if(StringUtils.isEmpty(growingioProperties.getApiHost())){
            throw new RuntimeException("growing properties api.host must be defined");
        }
        if(StringUtils.isEmpty(growingioProperties.getProjectId())){
            throw new RuntimeException("growing properties project.id must be defined");
        }
    }
    /**
     * 页面初始化履行函数
     */
    @PostConstruct
    private void init(){
        this.checkProperties();
        //初始化装备
        this.initGrowingioApiProperties();
    }
    private void initGrowingioApiProperties() {
        Properties properties = new Properties();
        properties.setProperty(GrowingioConstant.API_HOST_KEY, growingioProperties.getApiHost());
        properties.setProperty(GrowingioConstant.PROJECT_ID_KEY, growingioProperties.getProjectId());
        properties.setProperty(GrowingioConstant.SEND_MSG_INTERVAL_KEY, growingioProperties.getSendMsgInterval().toString());
        properties.setProperty(GrowingioConstant.SEND_MSG_THREAD_KEY, growingioProperties.getSendMsgThread().toString());
        properties.setProperty(GrowingioConstant.MSG_STORE_QUEUE_SIZE_KEY, growingioProperties.getMsgStoreQueueSize().toString());
        properties.setProperty(GrowingioConstant.COMPRESS_KEY, growingioProperties.getCompress().toString());
        properties.setProperty(GrowingioConstant.LOGGER_LEVEL_KEY, growingioProperties.getLoggerLevel());
        properties.setProperty(GrowingioConstant.LOGGER_IMPL_KEY, growingioProperties.getLoggerImplemention());
        properties.setProperty(GrowingioConstant.RUN_MODE_KEY, growingioProperties.getRunMode());
        properties.setProperty(GrowingioConstant.CONNECTION_TIMEOUT_KEY, growingioProperties.getConnectionTimeout().toString());
        properties.setProperty(GrowingioConstant.READ_TIMEOUT_KEY, growingioProperties.getReadTimeout().toString());
        //经过SDK中的这个API能够防止运用properties文件
        ConfigUtils.init(properties);
        logger.info("init load growingio starter api properties success,url:{},runmode:{},enable:{}",
                growingioProperties.getApiHost(),growingioProperties.getRunMode(),growingioProperties.getEnable());
    }
}

然后再resources文件夹下在创立一个meta-inf/spring.factoies文件,为了调用方包名不一样的时候,能够被扫描到:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    cn.aijavapro.growingio.config.GrowingioAutoConfiguration

这样一个基本的Spring Boot Starter组件就完结了,此刻工程截图如下所示:

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

六、测验运用

虽然咱们没有GrowingIO的账号和服务端的环境,可是咱们也是能够测验用的,GrowingIO SDK中供给了一调试测验形式,所以是只要在操控台中能够看到咱们的日志输出就代表starter开发成功了。

1、首要,新创立一个Spring Boot工程,然后对方才的starter项目进行引进,然后界说发动装备参数文件,并发动项目,看看体系发动中是否输出了发动日志。

引进依靠:

<dependency>
            <groupId>cn.aijavapro</groupId>
            <artifactId>growingio-spring-boot-stater</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

创立application.yml文件,并声明中心的几个参数装备,这儿指定了runMode为test形式,代表不会实在恳求的SDK的服务端。

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

然后咱们在一个Controller中模仿生成订单时候的一个事务埋点操作,代码如下所示:

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

然后发动项目,看看是否成功输出了发动日志:

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

然后,咱们经过CURL命令或者浏览器直接访问测验恳求,看看操控台是否有埋点日志输出

curl http://localhost:8080/order/save

正常会呈现如下的成果:

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

假如看到这个成果,阐明一个简易版的Spring Boot Starter现已生效了。假如后续对接服务端,能够直接发送恳求到服务端。

还在等什么,赶忙按照我的共享,去动手实践一下吧,实际行动了,才是学习技术最好的老师。

七、总结

今日共享了一个根据现成的SDK,给他封装成Starter的形式,后续我或许会持续共享,比如:

(1)、共享这个SDK的源码规划与思路,能够学到一种Java客户端SDK埋点的开发,异步行列发送等等,关于日常代码规划和作业也有必定的协助。

(2)、为当时Starter封装几个高档Starter具有的那几个特性。

(3)、测验模仿开发一个细巧的服务端,用于接纳这个客户端SDK上传的埋点日志。

在此进程中是否有困惑和疑问想要沟通的呢,欢迎一同沟通。

八、操练

感兴趣的小伙伴,既然学习了,也不能白学习,能够测验着做做如下操练操练,往往技术的进步在于一些故意操练上。比如:

(1)、测验根据神策剖析的Java SDK(神策数据官方 Java 埋点 SDK,是一款轻量级用于 Java 端的数据收集埋点 SDK)封装出一个Spring Boot Starter,神策剖析的SDK介绍地址:manual.sensorsdata.cn/sa/latest/z…,在进行这个SDK封装的时候,能够多从运用者的角度思考一下,假如经过封装Starter组件,来降低开发人员的集成难度,进步开发功率。

(2)、测验根据开源的ChatGPT的Java SDK,封装出一个Spring Boot Starter,SDK的介绍材料如下:github.com/PlexPt/chat…

一同本文也是作者首发于个人知识星球【觉悟的新世界程序员】中的一篇文章,也是在星球内正在共享的《开源规划系列专题》中的中的内容,假如想了解相关内容,能够来了解下,与我一同学习沟通。现在已更新的内容截图如下:

跟我一同学开源规划第1节:封装埋点GrowingIO Spring Boot Starter组件

十分等待与咱们一同学习、进步,喜爱的老铁能够保藏、点赞、关注、共享哦。

欢迎在评论区一同沟通哦。