提供spring boot扩展包,包含自动装配、starter、一些工具类等。
1. 关于
1.1. 简介
spring-boot-extension是一个拓展Spring Boot的库,内置一些SpringBoot未包含的Starter包
同时也补充的各种工具类,用于简化开发、提升开发效率
项目按职责拆分为几个部分:
-
spring-auto-service:编译期注解处理器,用于生成 Spring Boot 自动装配文件、spring.factories和aot.factories。 -
spring-extension-commons:通用工具、表达式解析、Jackson、HTTP 客户端、Servlet 包装等基础能力。 -
spring-extension-context:第三方组件与 Spring 的集成能力,例如 Curator、Disruptor、动态数据源、限流、分布式锁、二维码、MapStruct、MyBatis 等。 -
spring-boot-extension-autoconfigure:面向 Spring Boot 的自动配置模块。 -
spring-boot-extension-starters:按功能拆分的 starter,便于业务项目按需引入。 -
spring-testcontainers-support:补充 Testcontainers 与 Spring Boot service connection 的适配。
推荐业务项目优先引入 BOM,然后只引入需要的 starter。需要自定义扩展时,再直接使用 spring-extension-commons
或 spring-extension-context 中的基础 API。
2. 安装
2.1. 依赖相关
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>spring-extension-dependencies</artifactId>
<version>${version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
implementation platform('io.github.livk-cloud:spring-extension-dependencies:$version')
implementation(platform("io.github.livk-cloud:spring-extension-dependencies:${version}"))
最小BOM依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>spring-extension-bom</artifactId>
<version>${version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
implementation platform('io.github.livk-cloud:spring-extension-bom:$version')
implementation(platform("io.github.livk-cloud:spring-extension-bom:${version}"))
2.3. 使用建议
spring-extension-dependencies 管理项目自身依赖及常用第三方依赖版本,适合直接接入业务项目。
spring-extension-bom 更轻量,只管理 spring-boot-extension 发布的模块版本,适合已经有统一依赖治理的平台项目。
引入 BOM 后,业务模块中的 starter 可以省略版本:
dependencies {
implementation(platform("io.github.livk-cloud:spring-extension-dependencies:${version}"))
implementation("io.github.livk-cloud:redisson-spring-boot-starter")
implementation("io.github.livk-cloud:distributed-lock-spring-boot-starter")
}
如果只是使用工具类,不需要 Spring Boot 自动配置,可以只引入 spring-extension-commons。
3. 使用指导
3.1. spring boot装配文件自动生成
根据代码定义生成spring boot的自动装配文件和spring.factories、aot.factories
该模块是编译期注解处理器,注解保留策略为 SOURCE。
它不会在运行时参与逻辑,只负责在编译阶段根据注解生成 Spring 约定位置的资源文件。
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>spring-auto-service</artifactId>
<version>${version}</version>
<scope>provided</scope>
</dependency>
compileOnly 'io.github.livk-cloud:spring-auto-service:${version}'
annotationProcessor 'io.github.livk-cloud:spring-auto-service:${version}'
compileOnly("io.github.livk-cloud:spring-auto-service:${version}")
annotationProcessor("io.github.livk-cloud:spring-auto-service:${version}")
3.1.1. @SpringAutoService使用示例
@SpringAutoService 默认生成 Spring Boot 自动配置导入文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
如果传入自定义注解类型,会生成该注解对应的 imports 文件,适合配合 @AutoImport 或 SpringAbstractImportSelector
使用。
@Component
@SpringAutoService
public class SpringContextHolder implements BeanFactoryAware, ApplicationContextAware, DisposableBean {
}
生成文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.livk.commons.spring.context.SpringContextHolder
@AutoConfiguration
@ConditionalOnClass(WebClient.class)
@SpringAutoService(com.livk.commons.http.annotation.EnableWebClient.class)
public class WebClientConfiguration {
}
生成文件 META-INF/spring/com.livk.commons.http.annotation.EnableWebClient.imports
com.livk.commons.http.WebClientConfiguration
3.1.2. @SpringFactories 使用示例
指定接口为spring.factories的Key
@SpringFactories(org.springframework.boot.env.EnvironmentPostProcessor)
public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {
}
生成文件 META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.livk.commons.spring.TraceEnvironmentPostProcessor
当前类如果仅仅只有一个接口,可以不指定,自动生成
@SpringFactories
public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {
}
生成文件 META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.livk.commons.spring.TraceEnvironmentPostProcessor
3.1.3. @AotFactories 使用示例
指定接口为aot.factories的Key
@AotFactories(org.springframework.boot.env.EnvironmentPostProcessor)
public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {
}
生成文件 META-INF/spring/aot.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.livk.commons.spring.TraceEnvironmentPostProcessor
3.1.4. 使用注意
-
当前类只实现一个接口时,
@SpringFactories和@AotFactories可以自动推断 key。 -
当前类实现多个接口时,建议显式指定
value,避免生成结果不符合预期。 -
@SpringAutoService标记的类通常也会带有 Spring Boot 的@AutoConfiguration或普通配置类注解。 -
Gradle 项目需要同时配置
compileOnly与annotationProcessor,否则编译期无法执行处理器。
当前类如果仅仅只有一个接口,可以不指定,自动生成
@AotFactories
public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {
}
生成文件 META-INF/spring/aot.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.livk.commons.spring.TraceEnvironmentPostProcessor
3.2. starter 选择指南
spring-boot-extension-starters 按功能拆分 starter,业务项目建议按需引入。
starter 会带上对应的 context 与 autoconfigure 模块,通常不需要再手动引入底层模块。
3.2.1. starter 与功能对应关系
| starter | 核心能力 | 常见依赖/前置条件 |
|---|---|---|
curator-spring-boot-starter |
Zookeeper Curator 客户端、 |
需要可访问的 Zookeeper |
disruptor-spring-boot-starter |
Disruptor 事件队列扫描、生产者与消费者集成 |
需要声明
|
dynamic-datasource-spring-boot-starter |
动态数据源、数据源切换 AOP |
需要配置 |
fesod-spring-boot-starter |
Excel 导入导出,支持 MVC 与 WebFlux |
需要定义 Excel 解析 listener |
limit-spring-boot-starter |
方法级限流 |
分布式场景建议配合 Redisson |
distributed-lock-spring-boot-starter |
方法级分布式锁 |
需要 Redisson 或 Curator 作为锁实现 |
mapstruct-spring-boot-starter |
MapStruct 转换器发现、缓存和
|
需要 MapStruct 生成转换器 Bean |
mybatis-extension-boot-starter |
MyBatis 字段注入、SQL 耗时监控、JSON 类型处理支持 |
需要 MyBatis 环境 |
qrcode-spring-boot-starter |
二维码请求解析与响应渲染 |
支持 MVC 与 WebFlux |
redisearch-spring-boot-starter |
RediSearch 连接工厂与模板 |
需要 Redis Stack / RediSearch 服务 |
redisson-spring-boot-starter |
RedissonClient 自动配置 |
需要 Redis 或 Redis Cluster |
browscap-spring-boot-starter |
基于 BrowsCap 的 User-Agent 解析 |
适合使用 BrowsCap 数据的项目 |
yauaa-spring-boot-starter |
基于 YAUAA 的 User-Agent 解析 |
适合需要 YAUAA 解析能力的项目 |
3.2.2. 依赖示例
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>distributed-lock-spring-boot-starter</artifactId>
</dependency>
implementation 'io.github.livk-cloud:redisson-spring-boot-starter'
implementation 'io.github.livk-cloud:distributed-lock-spring-boot-starter'
implementation("io.github.livk-cloud:redisson-spring-boot-starter")
implementation("io.github.livk-cloud:distributed-lock-spring-boot-starter")
3.2.3. 选择建议
如果只使用通用工具类,选择 spring-extension-commons。
如果需要手动组装基础能力,选择 spring-extension-context。
如果是 Spring Boot 业务项目,优先选择具体 starter。
多个 starter 可以组合使用,例如:
-
redisson-spring-boot-starter+distributed-lock-spring-boot-starter:使用 Redisson 分布式锁。 -
redisson-spring-boot-starter+limit-spring-boot-starter:使用 Redisson 做分布式限流。 -
curator-spring-boot-starter+distributed-lock-spring-boot-starter:使用 Curator 分布式锁。 -
browscap-spring-boot-starter或yauaa-spring-boot-starter:二选一接入 User-Agent 解析。
不建议一次性引入所有 starter。按功能引入可以减少无关第三方依赖,也能避免多个同类 Bean 同时存在导致自动配置选择不符合预期。
3.3. spring通用工具拓展
提供一些通用、工具类方便开发
该模块不强制绑定具体业务场景,适合在 starter、自定义自动配置和普通业务代码中复用。
其中 annotation、aop、expression
更偏扩展开发;http、jackson、web、util 更偏日常业务工具。
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>spring-extension-commons</artifactId>
<version>${version}</version>
</dependency>
implementation 'io.github.livk-cloud:spring-extension-commons:${version}'
implementation("io.github.livk-cloud:spring-extension-commons:${version}")
3.3.1. annotation
annotation 包用于简化基于注解的 Spring 扩展开发,适合自定义 @EnableXxx、扫描器或自动导入配置类的场景。
SpringAbstractImportSelector
通过继承AutoConfigurationImportSelector,可以实现自动导入spring的配置文件,导入文件为META-INF/spring/{annotationClass}.imports,需要使用org.springframework.context.annotation.Import进行注册
使用示例:
public @interface Http{
}
public class HttpImportSelector extends SpringAbstractImportSelector<Http>{
}
@AutoImport
使用AutoImport自动进行注册,作用于注解上,功能同SpringAbstractImportSelector类似
使用示例:
@AutoImport
public @interface Http{
}
编译后的配置文件示例:
META-INF/spring/com.example.Http.imports
文件内容为需要导入的配置类全限定名,每行一个。
3.3.2. aop
aop 包封装了基于注解的 Advisor 模板,减少重复编写 Pointcut、MethodInterceptor
和注解解析逻辑。
AnnotationAbstractPointcutAdvisor
使用注解处理AOP的通用切点及表达式
使用示例
public class LockInterceptor extends AnnotationAbstractPointcutAdvisor<OnLock>{
@Override
protected Object invoke(MethodInvocation invocation, OnLock onLock){
//AOP处理等同于org.aspectj.lang.annotation.Around
}
@Override
public Pointcut getPointcut(){
//实现切入点
}
}
将注解作为泛型,自动获取到注解信息
AnnotationAbstractPointcutTypeAdvisor
定制化拓展
使用示例
public class LockInterceptor extends AnnotationAbstractPointcutTypeAdvisor<OnLock>{
@Override
protected Object invoke(MethodInvocation invocation, OnLock onLock){
//
}
@Override
protected AnnotationPointcutType pointcutType() {
//默认实现
return AnnotationAutoPointcut.auto();
}
}
AnnotationPointcut
提供多种可选方案
-
forType基于类级别的拦截等价于(@Around(@within(Annotation)))
-
forMethod基于方法级别的拦截等价于(@Around(@annotation(Annotation)))
-
forTypeOrMethod基于类或方法级别的拦截等价于(@Around(@annotation(Annotation)||@within(Annotation)))
-
forTarget根据Annotation Target推断(如果仅有TYPE、则为TYPE级别。如果仅有METHOD、则为METHOD级别。如果同时都有则为TYPE_OR_METHOD级别。以上情况都无法出现则抛出异常)
3.3.3. expression
expression 包提供统一的表达式解析接口,将方法参数、Map、Spring Environment 等上下文包装为
Context,再交给不同表达式引擎计算。
限流、分布式锁等组件可以用它解析动态 key。
AbstractExpressionResolver
ExpressionResolver抽象实现
将Map或者Method等信息转成Context
使用示例:
public class MyExpressionResolver extends AbstractExpressionResolver {
@Override
public <T> T evaluate(String value, Context context, Class<T> returnType) {
//TODO:解析表达式
}
//重写此方法用于调整Context的解析
public MyExpressionResolver(ContextFactory contextFactory){
super(contextFactory);
}
//也可以使用默认
public MyExpressionResolver(){
}
}
CacheExpressionResolver
用于对表达式解析进行缓存构建
同时添加spring-environment的支持
使用示例:
public class MyExpressionResolver extends CacheExpressionResolver<Expression> {
//将表达式转成不同组件的表达式类
@Override
protected Expression compile(String value) throws Throwable {
return null;
}
//根据组件的表达式进行计算
@Override
protected <T> T calculate(Expression expression, Context context, Class<T> returnType) throws Throwable {
return null;
}
//将上下文转成组件的上下文
@Override
protected EvaluationContext transform(Context context) {
return null;
}
}
内置ExpressionResolver
-
SpringExpressionResolver → 根据SpringEL表达式进行解析
-
AviatorExpressionResolver → 根据Aviator表达式进行解析(需要重新引入jar)
-
FreeMarkerExpressionResolver → 根据FreeMarker表达式进行解析(需要重新引入jar)
-
JexlExpressionResolver → 根据Apache Commons Jexl3表达式进行解析(需要重新引入jar)
-
MvelExpressionResolver → 根据Mvel 2表达式进行解析(需要重新引入jar)
3.3.4. http
http 包提供 HTTP 客户端自动导入注解,可按需创建
WebClient、RestClient 或 OkHttp 支持组件。
EnableHttpClient
EnableHttpClient会根据value值自动导入对应的http客户端
使用示例:
@Slf4j
@EnableHttpClient({
HttpClientType.WEB_CLIENT,
HttpClientType.REST_CLIENT
})
public class App {
@Bean
public ApplicationRunner applicationRunner(WebClient webClient,
RestClient restClient) {
return args -> {
log.info("webClient:{}", webClient);
log.info("restClient:{}", restClient);
};
}
}
同时提供了@EnableRestClient、@EnableWebClient作为快捷方式
3.3.5. jackson
jackson 包提供 JSON 序列化、反序列化、JsonNode 操作和类型构造的便捷封装。
JacksonSupport
Jackson便捷开发工具,通过包装ObjectMapper实现各种JSON转换
使用示例:
public static void main(String[] args){
JacksonSupport json = new JacksonSupport(new JsonMapper());
}
各种工具类
com.livk.commons.jackson.JsonMapperUtils 用于JsonMapper默认配置进行快速序列化和反序列化
com.livk.commons.jackson.JsonNodeUtils 用于JsonNode快速操作
com.livk.commons.jackson.TypeFactoryUtils 用于获取Jackson类型工具
3.3.6. util
util 包收纳通用工具类,可以按用途大致分为:
-
注解与反射:
AnnotationMetadataResolver、AnnotationValueExtractor、ReflectionUtils、ClassUtils。 -
Bean 与泛型:
BeanLambda、BeanUtils、GenericWrapper、GenericsByteBuddy。 -
Web 辅助:
HttpServletUtils、HttpReactiveUtils、MultiValueMapSplitter。 -
数据结构与 ID:
Pair、TreeNode、SnowflakeIdGenerator。 -
配置与上下文:
YamlUtils、ContextSnapshots。
AnnotationMetadataResolver
根据包名查找含有某些注解的类
使用示例:
public class Main{
public static void main(String[] args){
AnnotationMetadataResolver resolver = new AnnotationMetadataResolver();
resolver.find(MyAnnotation.class,"com.livk.resolver");
}
}
BeanLambda
根据lambda解析Field和Method
使用示例:
public class Main{
public static void main(String[] args){
String methodName = BeanLambda.methodName(Maker::getNo);
Method method = BeanLambda.method(Maker::getNo);
String fieldName = BeanLambda.fieldName(Maker::getNo);
Field field = BeanLambda.field(Maker::getNo);
}
}
ContextSnapshots
ContextSnapshots提供对线程池等进行包装、实现micrometer行为传播
使用示例:
public static void main(String[] args){
ExecutorService service = Executors.newCachedThreadPool();
ExecutorService wrap = ContextSnapshots.wrap(service);
wrap.submit(()->{});
}
GenericWrapper
进行类包装
使用示例:
public class Main{
public static void main(String[] args){
GenericWrapper<?> wrapper = GenericWrapper.of("123");
wrapper.unwrap()
}
}
GenericsByteBuddy
根据ByteBuddy重新处理,适配泛型以及高版本JDK
使用示例:
private static <S extends RediSearchConnectionFactory> Class<? extends S> createFactoryClass(
Class<? extends AbstractRedisClient> clientType) {
TypeDescription definitions = TypeDescription.ForLoadedType.of(RediSearchConnectionFactory.class);
try (DynamicType.Unloaded<S> unloaded = new GenericsByteBuddy().<S>subclass(definitions)
.name(FactoryProxySupport.class.getPackageName() + "." + clientType.getSimpleName()
+ "$ProxyConnectionFactory")
.defineField("client", clientType, Modifier.PRIVATE | Modifier.FINAL)
.defineConstructor(Modifier.PUBLIC)
.withParameters(clientType)
.intercept(MethodCall.invoke(Object.class.getConstructor())
.andThen(FieldAccessor.ofField("client").setsArgumentAt(0)))
.method(ElementMatchers.named("connect").and(ElementMatchers.takesArguments(1)))
.intercept(MethodDelegation.to(ConnectWithCodecInterceptor.class))
.method(ElementMatchers.named("close"))
.intercept(MethodDelegation.to(CloseInterceptor.class))
.make()) {
return unloaded.load(ClassUtils.getDefaultClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
}
catch (NoSuchMethodException ex) {
throw new IllegalArgumentException(ex);
}
}
MultiValueMapSplitter
字符串分割成MultiValueMap,例如String str = "root=1,2,3&root=4&a=b&a=c"
使用示例:
public class Main{
public static void main(String[] args){
String str = "root=1,2,3&root=4&a=b&a=c";
Map<String, List<String>> map = Map.of("root", List.of("1", "2", "3", "4"), "a", List.of("b", "c"));
MultiValueMap<String, String> multiValueMap = MultiValueMapSplitter.of("&", "=").split(str, ",");
assertEquals(CollectionUtils.toMultiValueMap(map), multiValueMap);
}
}
Pair
一个简单的KV键值对,Jackson序列化进行Map.Entity一样的适配
使用示例:
public class Main{
public static void main(String[] args){
Pair<String, String> pair = Pair.of("username", "password");
}
}
3.3.7. web
web 包主要面向 Servlet 场景,提供请求参数、请求体和响应体包装能力。
适合在 Filter、Interceptor 或测试中改写请求/响应内容。
HttpParameters
http请求Parameter参数,类似于org.springframework.http.HttpHeaders
使用示例:
public class Main{
public static void main(String[] args){
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("username", "livk", "root", "admin");
request.addParameter("password", "123456");
MultiValueMap<String, String> params = WebUtils.params(request);
HttpParameters parameters = new HttpParameters(params);
}
}
RequestWrapper
HttpServletRequest包装类,用于修改body、添加header、添加param
使用示例:
public class Main{
public static void main(String[] args){
MockHttpServletRequest request = new MockHttpServletRequest();
RequestWrapper wrapper = new RequestWrapper(request);
wrapper.body(JsonMapperUtils.writeValueAsBytes(Map.of("root", "root")));
wrapper.addHeader("Content-Type", "application/json");
wrapper.addParameter("username", "livk");
wrapper.addParameter("username", new String[]{"root", "admin"});
}
}
ResponseWrapper
HttpServletResponse包装类,用于修改body
使用示例:
public class Main{
public static void main(String[] args){
MockHttpServletResponse response = new MockHttpServletResponse();
ResponseWrapper wrapper = new ResponseWrapper(response);
wrapper.replaceBody(JsonMapperUtils.writeValueAsBytes(Map.of("root", "root")));
}
}
3.4. spring 组件拓展
兼容Spring的基础包
提供一些第三方包与spring整合的拓展,包括一些自定义拓展
如果只需要基础 API、自定义 Bean 或手动装配,可以引入 spring-extension-context。
如果希望交给 Spring Boot 自动配置,业务项目通常直接引入对应 starter。
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>spring-extension-context</artifactId>
<version>${version}</version>
</dependency>
implementation 'io.github.livk-cloud:spring-extension-context:${version}'
implementation("io.github.livk-cloud:spring-extension-context:${version}")
兼容SpringBoot的拓展包
使用spring boot的自动装配特性,自定义配置文件来覆盖官方的配置
spring-boot-extension-autoconfigure 通常由 starter 间接引入。直接引入该模块时,需要自行保证第三方依赖存在,例如
Redisson、Curator、MyBatis、ZXing 等。
3.4.1. starter 快速索引
| 功能 | starter | 说明 |
|---|---|---|
Curator |
curator-spring-boot-starter |
注册 CuratorFramework、CuratorTemplate 和健康检查 |
Disruptor |
disruptor-spring-boot-starter |
扫描事件并注册 SpringDisruptor |
动态数据源 |
dynamic-datasource-boot-starter |
注册 DynamicDatasource 与切换拦截器 |
Excel |
fesod-spring-boot-starter |
注册 Excel 参数解析器和返回值处理器 |
限流 |
limit-spring-boot-starter |
注册限流 AOP 拦截器 |
分布式锁 |
distributed-lock-boot-starter |
注册分布式锁 AOP 拦截器 |
MapStruct |
mapstruct-spring-boot-starter |
注册 MapstructService 与转换器仓库 |
MyBatis |
mybatis-extension-boot-starter |
注册字段注入、SQL 监控等扩展 |
二维码 |
qrcode-spring-boot-starter |
注册二维码请求解析和响应渲染 |
RediSearch |
redisearch-spring-boot-starter |
注册 RediSearchConnectionFactory 和模板 |
Redisson |
redisson-spring-boot-starter |
注册 RedissonClient |
UserAgent |
browscap-spring-boot-starter / yauaa-spring-boot-starter |
注册 User-Agent 解析器 |
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>spring-boot-extension-autoconfigure</artifactId>
<version>${version}</version>
</dependency>
implementation 'io.github.livk-cloud:spring-boot-extension-autoconfigure:${version}'
implementation("io.github.livk-cloud:spring-boot-extension-autoconfigure:${version}")
3.4.2. curator
CuratorOperations
Curator相关所有对zookeeper的操作
由CuratorTemplate进行实现
采用CuratorFramework作为底层实现
使用示例
public static void main(String[] args){
CuratorFramework framework = createFramework();
framework.start();
CuratorTemplate template = new CuratorTemplate(framework);
}
curator-spring-boot-starter
artifactId: curator-spring-boot-starter
根据CuratorProperties自动注册CuratorFramework、RetryPolicy、CuratorTemplate以及Curator Actuator
配置
配置前缀为 spring.zookeeper.curator。
spring:
zookeeper:
curator:
connect-string: localhost:2181
base-sleep-time-ms: 50
max-retries: 10
max-sleep-ms: 500
block-until-connected-wait: 10
block-until-connected-unit: seconds
session-timeout: 60s
connection-timeout: 15s
注册 starter 后可直接注入 CuratorFramework 或 CuratorTemplate
使用;Actuator 存在时会注册 Curator 健康检查。
3.4.3. disruptor
Disruptor 组件用于在 Spring 中注册基于 LMAX Disruptor 的事件队列。
通过 @DisruptorScan 扫描事件模型,再通过生产者发送事件,由消费者处理。
DisruptorScan
类似于MyBatisScan用与扫描DisruptorEvent标记的实体
根据实体和DisruptorEvent生成SpringDisruptor<DisruptorEventWrapper<T>>队列
使用示例
@DisruptorScan("com.livk.disruptor")
public class Config{
}
通常在启动类或配置类上声明扫描包:
@SpringBootApplication
@DisruptorScan(basePackages = "com.example.disruptor")
public class DisruptorApplication {
}
DisruptorEventProducer
Disruptor生产者
使用示例
@org.springframework.stereotype.Component
public class DisruptorMqServiceImpl implements DisruptorMqService {
private final DisruptorEventProducer<MessageModel> producer;
public DisruptorMqServiceImpl(SpringDisruptor<MessageModel> disruptor) {
producer = new DisruptorEventProducer<>(disruptor);
}
@Override
public void send(String message) {
log.info("record the message: {}", message);
producer.send(toMessageModel(message));
}
@Override
public void batch(List<String> messages) {
List<MessageModel> messageModels = messages.stream().map(this::toMessageModel).toList();
producer.sendBatch(messageModels);
}
private MessageModel toMessageModel(String message) {
return MessageModel.builder().message(message).build();
}
}
DisruptorEventConsumer
Disruptor消费者
使用示例
@org.springframework.stereotype.Component
public class Consumer implements DisruptorEventConsumer<MessageModel> {
private final ApplicationContext applicationContext;
@Override
public void onEvent(MessageModel wrapper, long sequence, boolean endOfBatch) {
log.info("消费者消费的信息是:{} :{} :{} id:{}", wrapper, sequence, endOfBatch, applicationContext.getId());
}
}
3.4.4. dynamic
动态数据源组件基于 DynamicDatasource 和 AOP 拦截器完成数据源切换。
手动模式可以直接注册 DynamicDatasource,Spring Boot 项目推荐使用 dynamic-datasource-boot-starter
和 @EnableDynamicDatasource。
DynamicDatasource
动态数据源,使用DataSourceContextHolder进行数据源切换
使用示例
@Configuration
public class Config {
@Bean
public DynamicDatasource dynamicDatasource() {
DynamicDatasource dynamicDatasource = new DynamicDatasource();
dynamicDatasource.setTargetDataSources(datasourceMap);
dynamicDatasource.setDefaultTargetDataSource(datasourcePrimary);
return dynamicDatasource;
}
}
dynamic-datasource-boot-starter
artifactId: dynamic-datasource-boot-starter
根据DynamicDatasourceProperties注册DynamicDatasource和DataSourceInterceptor
需要添加EnableDynamicDatasource到Spring Configuration
配置
配置前缀为 spring.dynamic,其中 primary 必须指向 datasource
中已存在的数据源名称。
spring:
dynamic:
primary: mysql
datasource:
mysql:
url: jdbc:mysql://localhost:3306/demo
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
pgsql:
url: jdbc:postgresql://localhost:5432/demo
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
@EnableDynamicDatasource
@SpringBootApplication
public class DynamicApplication {
}
@DynamicSource 可标记在类或方法上,方法级别通常用于覆盖类级别默认数据源。
3.4.5. fesod
Fesod 组件封装 FastExcel 的导入导出能力,并接入 Spring MVC / WebFlux 方法参数解析和返回值处理。
Excel导入
使用注解 @PostMapping 解析Excel(支持Spring Webflux)
@ExcelParam指定文件名称
parse使用自定义封装fesod的解析器 com.livk.excel.mvc.listener.InfoExcelListener
@RestController
public class InfoController {
@RequestExcel(parse = InfoExcelListener.class)
@PostMapping("uploadList")
public HttpEntity<List<Info>> uploadList(@ExcelParam List<Info> dataExcels) {
return ResponseEntity.ok(dataExcels);
}
@RequestExcel(parse = InfoExcelListener.class)
@PostMapping("upload")
public HttpEntity<List<Info>> upload(@ExcelParam List<Info> dataExcels) {
return ResponseEntity.ok(dataExcels);
}
@RequestExcel(parse = InfoExcelListener.class)
@PostMapping("uploadMono")
public Mono<HttpEntity<List<Info>>> uploadMono(@ExcelParam Mono<List<Info>> dataExcels) {
return dataExcels.map(ResponseEntity::ok);
}
}
@RequestExcel 用于声明上传 Excel 的解析器,@ExcelParam 用于绑定解析后的数据。
WebFlux 场景可以使用 Mono<List<T>> 接收解析结果。
Excel导出
使用注解 @ResponseExcel 或者 @ExcelController 解析Excel(支持Spring
Webflux)
fileName指定下载文件名
suffix指定Excel后缀 默认xlsm
使用ExcelController之后,fileName为out,suffix为xlsm
返回结果为 List<?> Mono<List<?>>
Flux<?> 是sheet名称即为sheet
返回结果为 Map<String,?> Mono<Map<String,?>>
是sheet名称即为map key
@RestController
@RequiredArgsConstructor
public class InfoController {
@ResponseExcel(fileName = "outFile")
@RequestExcel(parse = InfoExcelListener.class)
@PostMapping("uploadDownLoad")
public List<Info> uploadDownLoadMono(@ExcelParam List<Info> dataExcels) {
return dataExcels;
}
@ResponseExcel(fileName = "outFile")
@RequestExcel(parse = InfoExcelListener.class)
@PostMapping("uploadDownLoadMono")
public Mono<List<Info>> uploadDownLoadMono(@ExcelParam Mono<List<Info>> dataExcels) {
return dataExcels;
}
@ResponseExcel(fileName = "outFile")
@RequestExcel(parse = InfoExcelListener.class)
@PostMapping("uploadDownLoadFlux")
public Flux<Info> uploadDownLoadFlux(@ExcelParam Mono<List<Info>> dataExcels) {
return dataExcels.flatMapMany(Flux::fromIterable);
}
}
@ExcelController
public class Info2Controller {
@PostMapping("download")
public Map<String, List<Info>> download(@RequestBody List<Info> dataExcels) {
return dataExcels.stream()
.collect(Collectors.groupingBy(info -> String.valueOf(Long.parseLong(info.getPhone()) % 10)));
}
}
@ResponseExcel 可以标记在方法上;@ExcelController 可以标记在类上,对类内返回结果统一进行
Excel 输出处理。
返回 Map<String, ?> 时,map key 会作为 sheet 名称。
3.4.6. limit
限流组件通过 @Limit 标记方法,并由 LimitInterceptor 拦截执行。
默认提供 RedissonLimitExecutor,适合多实例部署下共享限流状态。
Limit注解
标记方法,用于对此方法进行限流
使用示例
public class UserService {
@Limit(key = "livk:user", rate = 2, rateInterval = 30)
public void save(User user) {
}
}
@Limit 常用属性:
-
key:限流资源 key,建议按业务域命名。 -
rate:单位时间内允许的令牌数。 -
rateInterval:单位时间长度。 -
rateIntervalUnit:时间单位,默认SECONDS。 -
restrictIp:是否按请求 IP 追加限制维度。 -
handler:触发限流后的自定义处理器。
3.4.7. lock
分布式锁组件通过 @DistLock 标记方法,并由 DistributedLockInterceptor
统一执行加锁、释放锁和异常处理。
内置 Redisson 与 Curator 两类实现。
DistLock注解
标记方法,用于对此方法进行分布式锁
使用示例 key支持SpEL表达式,同时支持公平锁、读锁、写锁,并支持异步
在方法上添加 @OnLock(scope = LockScope.DISTRIBUTED_LOCK) (当前分布式锁仅支持Redisson、Curator)+
public class UserService {
@PostMapping("/buy/distributed")
@DistLock(key = "shop")
public void buyLocal(@RequestParam(defaultValue = "2") Integer count) {
}
}
@DistLock 常用属性:
-
key:锁名称,支持表达式生成动态 key。 -
type:锁类型,支持LOCK、FAIR、READ、WRITE。 -
leaseTime:锁租约时间,默认10。 -
waitTime:等待获取锁时间,默认3。 -
async:是否使用异步锁能力。
3.4.8. mapstruct
MapStruct 组件提供两类转换方式:
-
自定义双向转换器:继承
com.livk.context.mapstruct.converter.Converter,通过MapstructService使用。 -
Spring 单向转换器:继承
org.springframework.core.convert.converter.Converter,通过 SpringConversionService使用。
使用示例
自定义转换器
继承 com.livk.context.mapstruct.converter.Converter
并添加注解 @Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserConverter extends Converter<User, UserVO> {
@Mapping(target = "password", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "createTime", source = "createTime", dateFormat = DateUtils.YMD_HMS)
@Mapping(target = "type", source = "type", numberFormat = "#")
@Override
User getSource(UserVO userVO);
@Mapping(target = "createTime", source = "createTime", dateFormat = DateUtils.YMD_HMS)
@Mapping(target = "type", source = "type", numberFormat = "#")
@Override
UserVO getTarget(User user);
}
Spring转换器 继承 org.springframework.core.convert.converter.Converter
并添加注解 @Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserSpringConverter extends Converter<User, UserVO> {
@Mapping(target = "createTime", source = "createTime", dateFormat = DateUtils.YMD_HMS)
@Mapping(target = "type", source = "type", numberFormat = "#")
@Override
UserVO convert(@Nullable User user);
}
使用 `MapstructService`操作转换自定义转换器
使用 `ConversionService`操作转换Spring转换器
@RestController
@RequestMapping("user")
@RequiredArgsConstructor
public class UserController {
public static final List<User> USERS = List.of(
new User().setId(1).setUsername("livk").setPassword("123456").setType(1).setCreateTime(new Date()),
new User().setId(2).setUsername("livk2").setPassword("123456").setType(2).setCreateTime(new Date()),
new User().setId(3).setUsername("livk3").setPassword("123456").setType(3).setCreateTime(new Date()));
// 自定义双向转换
private final MapstructService service;
// spring单向转换
private final ConversionService conversionService;
private final ConversionServiceAdapter conversionServiceAdapter;
@PostConstruct
public void init() {
System.out.println(conversionService.convert(USERS.get(0), UserVO.class));
service.convert(USERS, UserVO.class).forEach(System.out::println);
SpringContextHolder.getBean(MapstructService.class).convert(USERS, UserVO.class).forEach(System.out::println);
}
@GetMapping
public HttpEntity<Map<String, List<UserVO>>> list() {
List<UserVO> userVOS = USERS.stream().map(user -> conversionService.convert(user, UserVO.class))
.filter(Objects::nonNull).toList();
return ResponseEntity
.ok(Map.of("spring", userVOS,
"customize", service.convert(USERS, UserVO.class).toList()));
}
@GetMapping("/{id}")
public HttpEntity<Map<String, UserVO>> getById(@PathVariable Integer id) {
User u = USERS.stream().filter(user -> user.getId().equals(id)).findFirst().orElse(new User());
UserVO userVOSpring = conversionService.convert(u, UserVO.class);
return ResponseEntity.ok(Map.of("customize", service.convert(u, UserVO.class),
"spring", userVOSpring,
"adapter", conversionServiceAdapter.mapUserToUserVO(u)));
}
}
3.4.9. mybatis
MyBatis 扩展提供 SQL 字段自动注入、JSON 类型处理器和 SQL 执行耗时监控。
内置SQL数据注入工具
@Data
public class User implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Integer id;
private String username;
@JsonIgnore
@SqlInject(fill = SqlFill.INSERT, supplier = VersionInject.class)
private Integer version;
@JsonFormat(pattern = DateUtils.YMD_HMS, timezone = "GMT+8")
@SqlInject(fill = SqlFill.INSERT, time = InjectType.DATE)
private Date insertTime;
@JsonFormat(pattern = DateUtils.YMD_HMS, timezone = "GMT+8")
@SqlInject(fill = SqlFill.INSERT_UPDATE, time = InjectType.DATE)
private Date updateTime;
}
并且添加com.livk.context.mybatis.SqlDataInjection至MyBatis
@SqlInject 常用属性:
-
fill:填充时机,INSERT只在插入时填充,INSERT_UPDATE在插入和更新时填充。 -
supplier:自定义填充值提供器,推荐使用。 -
time:旧版时间类型填充方式,已废弃,建议迁移到supplier。
数据库JSON类型转换处理(目前只支持MySQL、Postgres)
com.livk.context.mybatis.type.mysql.MysqlJsonTypeHandler
com.livk.context.mybatis.type.postgres.PostgresJsonTypeHandler
添加至MyBatis
3.4.10. qrcode
二维码组件基于 ZXing,支持二维码文本解析和将接口返回值渲染为二维码图片。
同时支持 Spring MVC 与 Spring WebFlux。
二维码导入解析
使用注解 @PostMapping 解析二维码(支持Spring Webflux)
RequestQrCodeText指定文件名称
@RestController
@RequestMapping("qrcode")
public class QRCodeController {
@PostMapping
public Mono<String> textCode(@RequestQrCodeText(fileName = "file") Mono<String> text) {
return text.log();
}
}
文本或者实体导入二维码
使用注解 ResponseQrCode 或者 QrCodeController 解析Excel(支持Spring
Webflux)
@RestController
@RequestMapping("qrcode")
public class QRCodeController {
@ResponseQrCode
@GetMapping
public String text(String text) {
return text;
}
@ResponseQrCode
@GetMapping("mono")
public Mono<String> textMono(String text) {
return Mono.just(text);
}
}
@ResponseQrCode 支持配置二维码尺寸、颜色和图片类型:
@ResponseQrCode(width = 300, height = 300, type = PicType.PNG)
@GetMapping("png")
public String png(String text) {
return text;
}
也可以直接返回 QrCodeEntity 以便在代码中动态设置尺寸和颜色。
3.4.11. redisearch
封装 RediSearchTemplate、StringRediSearchTemplate 来操作 RediSearch。
自动配置基于 lettuce-mod 创建 RediSearchConnectionFactory,并在存在 Jackson 时提供 JSON
序列化的模板。
配置
配置前缀为 spring.redisearch,默认连接 localhost:6379 的 0
号库。
spring:
redisearch:
host: localhost
port: 6379
database: 0
username:
password:
ssl: false
timeout: 3s
client-name: redisearch-client
pool:
enabled: true
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
集群模式可启用 cluster 配置:
spring:
redisearch:
cluster:
enabled: true
nodes:
- 127.0.0.1:6379
- 127.0.0.1:6380
max-redirects: 3
使用模板
RediSearchTemplate<String, Object>
适合对象读写,StringRediSearchTemplate 适合字符串场景。
如果需要调整 lettuce 的底层资源,可以注册 ClientResourcesBuilderCustomizer。
@Service
public class ArticleSearchService {
private final StringRediSearchTemplate stringRediSearchTemplate;
public ArticleSearchService(StringRediSearchTemplate stringRediSearchTemplate) {
this.stringRediSearchTemplate = stringRediSearchTemplate;
}
}
3.4.12. redisson-spring-boot-starter
artifactId: redisson-spring-boot-starter
适配 Spring Boot 的 yaml、properties 配置文件,使用 Redisson 原生 Config
模型完成配置绑定。
自动配置会注册 RedissonClient,并优先读取 spring.redisson.config 下的
Redisson 配置;未配置时会回退到 Spring Boot Redis 单机配置创建客户端。
单机配置
spring:
redis:
host: localhost
port: 6379
password:
redisson:
config:
single-server-config:
address: redis://127.0.0.1:6379
database: 0
connection-pool-size: 64
connection-minimum-idle-size: 24
集群配置
spring:
redisson:
config:
cluster-servers-config:
node-addresses:
- redis://127.0.0.1:6379
- redis://127.0.0.1:6380
scan-interval: 2000
使用
引入 starter 后可直接注入 RedissonClient,分布式锁、限流等组件也可以复用同一个客户端。
@Service
public class LockService {
private final RedissonClient redissonClient;
public LockService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public void lock() {
RLock lock = redissonClient.getLock("demo:lock");
lock.lock();
try {
// business
}
finally {
lock.unlock();
}
}
}
3.4.13. sequence
发号器,可自定义datasource、redis、snowflake等发号策略
datasource发号器内置Postgres、Mysql、H2等数据库发号策略
未支持的数据库实现 com.livk.context.sequence.support.db.SequenceDbHelper 并注册到
META-INF/spring.factories 文件
发号模式
-
Sequence.snowflake(dataCenterId, machineId):本地雪花算法,不依赖外部存储。 -
DbRangeManager:基于数据库分段取号,内置 MySQL、PostgreSQL、H2。 -
RedisRangeManager:基于 RedisINCRBY分段取号,可使用 Lettuce 或 Spring Data Redis 连接。
SequenceBuilder 支持设置:
-
bizName:业务名称,必填,不同业务使用不同号段。 -
step:每次申请的号段步长,默认1000。 -
stepStart:初始值,默认0,真实发号从stepStart + 1开始。
@Configuration
public class Config{
@Bean
public Sequence dbSequence(DataSource dataSource) {
Sequence sequence = SequenceBuilder.builder(new DbRangeManager(dataSource)).bizName("test-sequence").build();
return sequence;
}
@Bean
public Sequence lettuceSequence(RedisClient redisClient) {
LettuceSequenceRedisHelper helper = new LettuceSequenceRedisHelper(redisClient);
Sequence sequence = SequenceBuilder.builder(new RedisRangeManager(helper)).bizName("test-sequence").build();
return sequence;
}
@Bean
public Sequence redisSequence(RedisConnectionFactory factory) {
SpringSequenceRedisHelper helper = new SpringSequenceRedisHelper(factory);
Sequence sequence = SequenceBuilder.builder(new RedisRangeManager(helper)).bizName("test-sequence").build();
return sequence;
}
@Bean
public Sequence snowflakeSequence() {
Sequence sequence = Sequence.snowflake(1, 2);
return sequence;
}
}
3.4.14. useragent
提供 User-Agent 解析能力,支持 Spring MVC 与 Spring WebFlux。
通过 @UserAgentInfo 注入解析后的 UserAgent,也可以从上下文持有器中获取当前请求的结果。
MVC 使用
@RestController
@RequestMapping("user-agent")
public class UserAgentController {
@GetMapping
public HttpEntity<Map<String, UserAgent>> get(@UserAgentInfo UserAgent userAgent) {
Map<String, UserAgent> map = Map.of("argument", userAgent,
"context", UserAgentContextHolder.getUserAgentContext());
return ResponseEntity.ok(map);
}
}
WebFlux 使用
@RestController
@RequestMapping("user-agent")
public class ReactiveUserAgentController {
@GetMapping
public Mono<UserAgent> get(@UserAgentInfo Mono<UserAgent> userAgent) {
return userAgent;
}
}
3.5. spring testcontainers support容器拓展
提供自定义testcontainers与spring boot的支持
<dependency>
<groupId>io.github.livk-cloud</groupId>
<artifactId>spring-testcontainers-support</artifactId>
<version>${version}</version>
<scope>test</scope>
</dependency>
testImplementation 'io.github.livk-cloud:spring-testcontainers-support:${version}'
testImplementation("io.github.livk-cloud:spring-testcontainers-support:${version}")
3.5.1. DockerImageNames
提供常用容器镜像名称构造方法:
-
DockerImageNames.mysql() -
DockerImageNames.postgres() -
DockerImageNames.redis() -
DockerImageNames.redisStack() -
DockerImageNames.zookeeper()
每个方法都提供默认 latest 标签,也可以传入指定 tag。
4. 问题
4.1. 常见问题
4.1.1. 为什么 starter 引入后没有生效
请先确认已经引入对应 starter,而不是只引入了 spring-extension-context。
spring-extension-context 提供基础 API,starter 才会引入对应自动配置模块并注册 Spring
Bean。
4.1.2. 自动配置文件没有生成
spring-auto-service 是编译期注解处理器,需要同时加入 compileOnly 和 annotationProcessor。
如果使用 Kotlin,需要按项目的 Kotlin KAPT/KSP 配置接入注解处理器。
4.1.3. Redisson、Curator、Redisearch 连接失败
先检查配置前缀是否正确:
-
Redisson:
spring.redisson -
Curator:
spring.zookeeper.curator -
RediSearch:
spring.redisearch
再确认中间件地址、端口、认证信息与本地网络环境。使用 Testcontainers 时,可优先使用 @ServiceConnection
或测试配置注入连接信息。