likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 25

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

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

创建自己的spring boot starter

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

初始化组件包结构

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

创建一个spring boot项目,用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 {
}

Spring Boot Starter开发指北(案例+代码地址) 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.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/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>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.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实在是太好用了,github.com/ulisesbocch… 我用的spring boot版本的,里面readme比较详细,www.jasypt.org/ 原版官网,不过我觉得普通用用,看看博客就行了,上手更快。 加密的话,我一开始是只写了一种,切面JasyptHandler做接口出入参的加密解密,后来同事反馈说咱们用mybatis直接扩展不就行了,好家伙说得妙啊,mybatis果然有这样的扩展类TypeHandler,然后根据这个写了个JasyptMybatisHandler

切面加密

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

Mybatis扩展

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

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

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

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后返还使用次数

作用是限制同时运行线程数,只有限流,超过的抛出异常中断方法

redis幂等性校验

Spring Boot Starter开发指北(案例+代码地址) 基于Lua和AOP,切点是@IdempotencyCheck,注解参数是单次幂等性校验有效时间和幂等性校验Key,使用场景是前后端的接口

通知部分只有@Around,Key值默认默认为应用名(spring.application.name):当前方法名:当前登录人ID(没有SSO就是null):入参的md5值,如果checkKey不为空就会替换入参和当前登录人--->应用名:当前方法名:checkKey

作用是在checkTime时间内相同checkKey只能运行一次

常用配置

BusinessBeanConfig

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

MyRedissonConfig

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

ThreadPoolConfig

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

如有动态线程池的必要,考虑引入外部组件 dynamictp.cn/

ToolApplicationContextInitializer

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

ValidatorConfig

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

代码地址

组件Gitee地址:gitee.com/cloudswzy/g…

碎碎念

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

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

想聊聊开始写这篇文章的初衷,我自己是工作了三年,技术栈的话目前看来是不老不新,主流水平吧,公司的运维环境变化也是相当大,从原始的Linux部署jar包,到Jenkins一站式部署,到目前的devops一站式平台。技术环境倒是变动不大,spring boot开发,三年前是2.1.X版本+JDK8到现在也是这样,Cloud组件的话注册中心从consul更新到了nacos,配置中心从Apollo正转向nacos,网关用的spring gateway,监控用的是Prometheus+Grafana。组件的话都是用spring boot starter方式集成的。

公司整体的架构与常规的已完整产品为导向的Spring Cloud架构不同,是正在往以K8S为中心的架构迁移配合定制化DevOps平台,将除了业务之外的组件变得无感知,让开发更专注于业务的Spring Boot开发。架构组是很爽的,各种新技术随便玩,我也老和他们沟通交流,了解下相关知识。对内的业务开发相对来说比较枯燥,简而言之,就是搬砖,高并发就不用想了,公司内部系统顶天了也就是几千人用。复杂业务也沾不上,公司之前是采购了ERP系统的,我们现在做的事就是在精简ERP上面的老业务流。用一个个微服务来替代ERP上面的一些业务流,我们的PM去和用户沟通,重新设计和改造原有的业务流,但总的来说都是精简,所以没有什么特别复杂的业务。大数据量有,但是我现在接触不多,公司也有专门搞大数据的,我一般都是用数据组提供的服务。

但是呢,为啥我还要写技术文章,因为,我喜欢搞事,哈哈!没有环境就创造环境,尽可能创造和发现问题,解决问题!从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等采集工具的原因很复杂的啦,还是受到公司整体的限制,不方便去动容器上的东西,如果后面可行的会更新吧。

求赞,求评论,请求大佬互动