该组件属于抛砖引玉哈,基本是我工作时用到的通用技术环境的缩影。如果你公司有自己的私服仓库,可以征求架构组的同意,放上自己的组件,公开给大家使用,不断的打磨,完善自己的技术细节。

正篇开始,我会在以读者有一定Java基础的前提下开始带领大家去开发一个工具和配置方向的spring boot starter组件,嘿嘿,严肃起来了,现在是技术时间~

创建自己的spring boot starter

首先最重要的第一步就是如何创建自己的spring boot starter,这个很简单啊,直接用模板就行了

初始化组件包结构


创建一个spring boot项目,用https://start.spring.io/或者Idea创建都可以,删除启动类,把包精简成上面这个样子

创建spring.factories文件

resources包下手动创建一个META-INFO文件夹,并且在包下创建一个spring.factories文件,文件内容写,注意空格(使用Idea会有提示)

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.cloud.tool.config.ToolAutoConfiguration

spring.factories的工作原理类似于Java原生的SPI机制,在此基础上进行了优化,可以一个文件写多个接口,想深入了解的可以搜一下@EnableAutoConfiguration这个注解的工作原理。

创建统一注册类

import org.springframework.context.annotation.ComponentScan;
//统一注册BEAN
@ComponentScan(basePackages = "com.cloud.tool")
public class ToolAutoConfiguration {
}


ToolAutoConfiguration这个类主要是方便做bean的注册,@ComponentScan这个注解会扫描并加载属性basePackages指定的包路径下所有bean,就不用在spring.factories文件逐个写了,只用写这个类就行了。@SpringBootApplication启动类注解也使用了@ComponentScan。

pom文件配置

配置如下,着重关注下groupId和artifactId,这是组件在maven仓库里的坐标。finalname指定最终打包后的名称,install命令将包下载到本地,就是你在maven的setting.xml里配置的本地仓库,deploy命令会推送你的包到远程仓库(例如公司的私服)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache/POM/4.0.0" xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache/POM/4.0.0 https://maven.apache/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cloud.tool</groupId>
    <artifactId>general-components</artifactId>
    <version>1.0.0</version>
    <name>general-components</name>
    <description>通用组件</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <mavenpiler.source>1.8</mavenpiler.source>
        <mavenpiler.target>1.8</mavenpiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
            <version>2.3.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.17.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>3.0.4</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>toolbox</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.12.RELEASE</version>
            </plugin>
        </plugins>
    </build>
    <!-- 用来支持项目发布到私服中,用来配合deploy插件的使用 -->
    <distributionManagement>
        <repository>
            <id>xxx</id>
            <name>xxx</name>
            <url>http://xxx</url>
        </repository>
    </distributionManagement>
</project>

以上四步完成后就建立起了一个spring boot starter的架子,接下来就可以大展身手了(突然想到了一部番名:无职转生,到了异世界就拿出真本事,哈哈)

加密模块

加密用到了以下五个类,两个依赖和几项配置
JasyptField–提供注解JasyptField用于对象属性以及方法参数
JasyptMethod–提供注解JasyptMethod用于注解在方法上
JasyptHandler–由切面方式实现,使用时请务必注意切面使用禁忌
JasyptConstant–常量
JasyptMybatisHandler–mybatis处理扩展

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.4</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
# 加密盐值,如果报空指针找不到该行,查看代码是否有config类
jasypt.encryptor.password=wzy
# 加密前缀及后缀默认ENC( ),例如密文SCM(s0kO6zE9NteZrzUbpzeCE9PinvI)
jasypt.encryptor.property.prefix=SCM(
jasypt.encryptor.property.suffix=)
# 加密算法设置3.0.0以后
jasypt.encryptor.algorithm=PBEWithMD5AndDES
jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator

加密工具类我没有自己写,原因是jasypt实在是太好用了,https://github/ulisesbocchio/jasypt-spring-boot 我用的spring boot版本的,里面readme比较详细,http://www.jasypt/原版官网,不过我觉得普通用用,看看博客就行了,上手更快。
加密的话,我一开始是只写了一种,切面JasyptHandler做接口出入参的加密解密,后来同事反馈说咱们用mybatis直接扩展不就行了,好家伙说得妙啊,mybatis果然有这样的扩展类TypeHandler,然后根据这个写了个JasyptMybatisHandler

切面加密

切面的切入点是@JasyptMethod,目前测试过的有String,obj,List,如有没支持的可以自行扩充。逻辑也很简单,获取入参后,根据类型判断,如果是对象的话,会去反射获取所有字段,判断字段上有没有注解@JasyptField,如果有的话,进行加密解密

Mybatis扩展

这个是我们在项目里常用的,使用方式很简单,指定typeHandle为自定义的类就行了,基于mybatis提供的扩展窗口,对String类型的字段在填入和取出时分别进行加密和解密
使用时,如果是mybatis-plus,务必在表映射实体类上增加注解@TableName(autoResultMap = true)

使用时须在对应实体类字段上加 typeHandler = JasyptMybatisHandler.class

Redis+Lua工具包

使用Lua脚本,原因:
1.减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。使用lua脚本执行以上操作时,比redis普通操作快80%左右
2.原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
3.复用。客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。

redis单号生成器

单号按照keyPrefix+yyyyMMdd+4位流水号的格式生成
redis获取当前keyPrefix对应的key,如果没有则返回1,如果存在,判断是否大于9999,如果大于返回错误,如果小于就将value+1,并且设置过期时间直到今天结束。

redis漏斗算法限流器

基于Lua+Aop,切点是@LimitMethod,注解参数是同时运行次数,使用场景是前后端的接口
@Around运行实际方法前进行限流(使用次数自增),@AfterRunning后返还使用次数

配置类

BusinessBeanConfig

这个类用于帮助你排除bean用的,比如我就拿来排除掉公司框架里的全局异常监听

MyRedissonConfig

Redisson的配置类,主要是启动参数的配置以及编码的配置,这里全局设置的String,用这个主要是因为想要保证Redis数据的可读性

ThreadPoolConfig

线程池的配置,这里简单的进行了CPU型和IO型的配置,可以通过ToolProperties类在application配置类写简单参数,更具体的就自定义吧。这个线程池用的Spring包装后的ThreadPoolTaskExecutor,注意JDK原版的是ThreadPoolExecutor,Spring Boot项目的话,建议还是用包装后的

ToolApplicationContextInitializer

启动时一次性运行类,我一般用来做配置文件校验。因为有的配置给不了默认值,只能强制让同事写,比如系统中文名

ValidatorConfig

@Valid配置快速失败,类似于ArrayList的fail-fast机制,有错了就抛,不会校验后面的数据

美化Controller层

美化后效果,需要统一返回值并且配置全局异常监听

统一返回值

有一个类似于RemoteResult的类,包含状态码,消息,返回值,如果你有更多的内容需要输出那就扩展这个类

全局异常监听

简易版的会用到三个类
异常类ToolException,标准异常,错误码,错误信息。
抽象异常类AbstractException,这个类的主要作用是提供一个异常的架子,方便扩展,如果没啥需求,可以不用这个,只提供普通异常类就行
全局异常监听类ToolExceptionHandler,在这个类里面去监听不同的错误,根据不同的错误来进行对应的处理

代码地址

组件Gitee地址:https://gitee/cloudswzy/general-components

碎碎念

呀哈哈,我是开发时长三年的Java学习机!

接下来开启我的碎碎念,多比比一下,这是我的第一篇文章,值得纪念的第一步。

想聊聊开始写这篇文章的初衷,我自己是工作了三年,技术栈的话目前看来是不老不新,主流水平吧,公司的运维环境变化也是相当大,从原始的Linux部署jar包,到Jenkins一站式部署,到目前的devops一站式平台。技术环境倒是变动不大,spring boot开发,三年前是2.1.X版本+JDK8到现在也是这样,Cloud组件的话注册中心从consul更新到了nacos,配置中心从Apollo正转向nacos,网关用的spring gateway,监控用的是Prometheus+Grafana。组件的话都是用spring boot starter方式集成的,这点可能和好多公司不太一样,因为我们项目是对内开发的,都是一些相对较小的项目,所以不是多模块部署的方式(当然也可以做,但是考虑到各个项目相对独立,而且架构组弄的devops平台尚且不支持独立部署,隔壁一个开发组是多模块部署,发版要发接近一小时,抱歉,等不了,告辞)。

相信大家也能看出来,这样的业务场景下,技术的瓶颈往往并不是主要问题,因为基本遇不到瓶颈,我的项目大多都是单点部署就能撑住那几百并发,大部分项目都是专门给部分业务专员开发,比如我做供应链,有个项目是仓库进销存相关的,就给那十几个仓管用,好家伙,几乎没有什么并发。我现在最大的问题就在于没有业务场景,高大上的高并发,大数据,复杂业务三不沾,尴尬,大写的尴尬。

但是呢,为啥我还要写技术文章,因为,我喜欢搞事,哈哈!没有环境就创造环境,尽可能创造和发现问题,解决问题!从21年开始,我就在想,怎么才能做技术的突破,限于前面的环境问题,我要如何冲出目前的困境?我的答案是,开始搞事,疯狂地搞事!!!

团队对于技术基本没有限制,领导的唯一要求就是别出事就行,那就很好,我觉得就现在的业务环境咋搞都不会有太大问题,于是就开始放心的搞事(事实证明,一年了,基本没出啥大事情)。唯一的限制在于架构组提供的一系列基础套件会有不小的限制,对当时菜鸡的我造成了不小的阻碍,经常会遇到一些莫名其妙的问题,比如覆盖了基础套件的配置,或者自己写的不生效等等问题。

此前我一直致力于写一些工具类,类似于hutool那种,也会去主动规范自己的代码,还分析架构组提供的套件并且写了一篇讲解的文档放在部门的知识分享平台上,直到一个机会的到来。也很突然就是团队要重构一个门户网站,然后让我去带人设计开发整个门户网站,领导提了一些具体的要求,比如待办汇总,单点登录,消息通知,权限管理等等,剩下的就全权交由我们这些开发来掌控,特别意外,因为门户网站类似于基础底座,部门之前也没有团队做过,所以也没有PM来做前期需求整理,因此项目的自治权极大,基本就是只要主体功能没问题,剩下的随意设计,并且有接近两月的开发时间,好家伙,真的好家伙,这是个大机会,意味着我可以随心所欲的进行技术选型。

我和团队的其他小伙伴们一起来构思整个项目,当时第一版做的时候,确实是也不知道该做成什么样子,所幸就开始寻找GitHub上的优秀框架,来抄他们的功能,看我们缺什么就补什么,最后主要选的若依,当然也揉合了别的好项目。既然是门户网站,那么自然要集成老的项目,那么如何做到新老项目的对接和兼容呢?自然是以插件的方式啦,从这里开始我认识到了工具类的局限性,并且开启了我自己开发组件的道路。我写的都是spring boot项目,自然组件的最优选择就是用spring boot starter啦,从去年开始,做了单点登录组件,工具及配置组件,业务组件,日志收集组件四大块,自己也去选型引入了seata,elastic-job3.0等中间件。

特别感谢团队小伙伴对我搞事的包容和支持。虽然我每次拍板说我写的组件没问题,但是搞事终有失误,例如写BUG,写BUG,写BUG。哈哈,你们就该知道我嘴里说的稳定版本永远是下一个版本,组件最多迭代次数的记录应该是一周三次吧。感谢大家都热爱技术,愿意陪着我搞事,当然,还是那句老话,该用就得用,不用我就强制升级,哈哈。

下集预告

第二篇文章的话会写写日志收集模块,目前该模块使用Log4j2+Kafka+ElasticSearch+Kibana+Skywalking构建,目前我所在团队的日志量是日均百万级别,应该远远没到瓶颈,机器配置Kafka,ES均为三节点集群,单节点没测过。内容的话,会从log4j2配置,Kafka配置,es索引优化,kibana看板配置,skywalking链路追踪配置等多方面,手把手教你开发一个完整的日志收集模块。没有使用filebeat等采集工具的原因很复杂的啦,还是受到公司整体的限制,不方便去动容器上的东西,如果后面可行的会更新吧。

更多推荐

Spring Boot Starter开发指北(案例+代码地址)