提供spring boot扩展包,包含自动装配、starter、一些工具类等。

1. 关于

1.1. 简介

spring-boot-extension是一个拓展Spring Boot的库,内置一些SpringBoot未包含的Starter包
同时也补充的各种工具类,用于简化开发、提升开发效率

项目按职责拆分为几个部分:

  • spring-auto-service:编译期注解处理器,用于生成 Spring Boot 自动装配文件、spring.factoriesaot.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-commonsspring-extension-context 中的基础 API。

2. 安装

2.1. 依赖相关

Maven
<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>
Gradle-Groovy
implementation platform('io.github.livk-cloud:spring-extension-dependencies:$version')
Groovy-Kotlin
implementation(platform("io.github.livk-cloud:spring-extension-dependencies:${version}"))

最小BOM依赖

Maven
<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>
Gradle-Groovy
implementation platform('io.github.livk-cloud:spring-extension-bom:$version')
Groovy-Kotlin
implementation(platform("io.github.livk-cloud:spring-extension-bom:${version}"))

2.2. 支持的Java版本

version

支持情况

Jdk21以下

Jdk21-Jdk26

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 约定位置的资源文件。

Maven
<dependency>
    <groupId>io.github.livk-cloud</groupId>
    <artifactId>spring-auto-service</artifactId>
    <version>${version}</version>
    <scope>provided</scope>
</dependency>
Gradle-Groovy
compileOnly 'io.github.livk-cloud:spring-auto-service:${version}'
annotationProcessor 'io.github.livk-cloud:spring-auto-service:${version}'
Groovy-Kotlin
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 文件,适合配合 @AutoImportSpringAbstractImportSelector 使用。

@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 项目需要同时配置 compileOnlyannotationProcessor,否则编译期无法执行处理器。

当前类如果仅仅只有一个接口,可以不指定,自动生成

@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 客户端、CuratorTemplate、Actuator 健康检查

需要可访问的 Zookeeper

disruptor-spring-boot-starter

Disruptor 事件队列扫描、生产者与消费者集成

需要声明 @DisruptorScan

dynamic-datasource-spring-boot-starter

动态数据源、数据源切换 AOP

需要配置 spring.dynamic.primary 与多个 datasource

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 转换器发现、缓存和 MapstructService

需要 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. 依赖示例

Maven
<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>
Gradle-Groovy
implementation 'io.github.livk-cloud:redisson-spring-boot-starter'
implementation 'io.github.livk-cloud:distributed-lock-spring-boot-starter'
Groovy-Kotlin
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-starteryauaa-spring-boot-starter:二选一接入 User-Agent 解析。

不建议一次性引入所有 starter。按功能引入可以减少无关第三方依赖,也能避免多个同类 Bean 同时存在导致自动配置选择不符合预期。

3.3. spring通用工具拓展

提供一些通用、工具类方便开发

该模块不强制绑定具体业务场景,适合在 starter、自定义自动配置和普通业务代码中复用。
其中 annotationaopexpression 更偏扩展开发;httpjacksonwebutil 更偏日常业务工具。

Maven
<dependency>
    <groupId>io.github.livk-cloud</groupId>
    <artifactId>spring-extension-commons</artifactId>
    <version>${version}</version>
</dependency>
Gradle-Groovy
implementation 'io.github.livk-cloud:spring-extension-commons:${version}'
Groovy-Kotlin
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

文件内容为需要导入的配置类全限定名,每行一个。

AnnotationBasePackageSupport

用于快速获取basePackage
使用注解方式获取,注解需包含basePackages和basePackageClasses

示例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DisruptorScan {

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

}
AnnotationBeanDefinitionScanner

用于快速扫描bean,使用注解方式获取

通常和 AnnotationBasePackageSupport 配合使用:先从注解中解析扫描包,再扫描指定注解或接口的候选 BeanDefinition。
适合 Disruptor、MapStruct 等需要“按包扫描并注册基础设施 Bean”的组件。

3.3.2. aop

aop 包封装了基于注解的 Advisor 模板,减少重复编写 PointcutMethodInterceptor 和注解解析逻辑。

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

提供多种可选方案

  1. forType基于类级别的拦截等价于(@Around(@within(Annotation)))

  2. forMethod基于方法级别的拦截等价于(@Around(@annotation(Annotation)))

  3. forTypeOrMethod基于类或方法级别的拦截等价于(@Around(@annotation(Annotation)||@within(Annotation)))

  4. forTarget根据Annotation Target推断(如果仅有TYPE、则为TYPE级别。如果仅有METHOD、则为METHOD级别。如果同时都有则为TYPE_OR_METHOD级别。以上情况都无法出现则抛出异常)

使用建议

自定义拦截器时只需要关注两个点:

  • 注解应该拦截类、方法还是两者都拦截。

  • invoke 中如何根据注解属性包装原方法调用。

如果注解只允许标记方法,优先选择 AnnotationPointcut.forMethod;如果注解同时允许标记类和方法,使用 forTypeOrMethodforTarget 更省心。

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
  1. SpringExpressionResolver → 根据SpringEL表达式进行解析

  2. AviatorExpressionResolver → 根据Aviator表达式进行解析(需要重新引入jar)

  3. FreeMarkerExpressionResolver → 根据FreeMarker表达式进行解析(需要重新引入jar)

  4. JexlExpressionResolver → 根据Apache Commons Jexl3表达式进行解析(需要重新引入jar)

  5. MvelExpressionResolver → 根据Mvel 2表达式进行解析(需要重新引入jar)

Context

ContextFactory 用于把运行时信息转换成表达式上下文。常见输入包括:

  • Map<String, Object>:直接作为变量集合。

  • Method 与参数数组:按参数名、参数索引等方式暴露变量。

  • Spring Environment:支持读取配置属性。

选择建议

Spring 项目优先使用 SpringExpressionResolver。如果业务已有 Aviator、FreeMarker、Jexl3 或 Mvel2 规则资产,可以引入对应依赖后使用相应 resolver。
需要高频解析时,优先继承 CacheExpressionResolver,避免每次都重新编译表达式。

3.3.4. http

http 包提供 HTTP 客户端自动导入注解,可按需创建 WebClientRestClient 或 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作为快捷方式

HttpClientType

EnableHttpClientvalue 支持多个客户端类型:

  • WEB_CLIENT:注册响应式 WebClient

  • REST_CLIENT:注册 Spring RestClient

  • OKHTTP:注册 OkHttp 相关配置。

只需要单个客户端时,推荐使用快捷注解:

@EnableWebClient
@Configuration
public class WebClientConfig {

}

如果业务已经注册同类型 Bean,可以通过 Spring Bean 覆盖默认配置。

3.3.5. jackson

jackson 包提供 JSON 序列化、反序列化、JsonNode 操作和类型构造的便捷封装。

JacksonSupport

Jackson便捷开发工具,通过包装ObjectMapper实现各种JSON转换

使用示例:

public static void main(String[] args){
  JacksonSupport json = new JacksonSupport(new JsonMapper());
}
@NumberJsonFormat

Number类型数据Jackson序列化处理注解

各种工具类

com.livk.commons.jackson.JsonMapperUtils 用于JsonMapper默认配置进行快速序列化和反序列化

com.livk.commons.jackson.JsonNodeUtils 用于JsonNode快速操作

com.livk.commons.jackson.TypeFactoryUtils 用于获取Jackson类型工具

数字序列化

@NumberJsonFormat 可用于 Number 类型字段的序列化格式处理,底层由 NumberJsonSerializer 完成。
适合金额、比例、统计值等需要统一格式输出的接口字段。

使用建议
  • JacksonSupport 适合需要持有自定义 ObjectMapperJsonMapper 的场景。

  • JsonMapperUtils 适合静态工具式快速读写 JSON。

  • TypeFactoryUtils 适合构造泛型集合、Map 等复杂 Jackson 类型。

3.3.6. util

util 包收纳通用工具类,可以按用途大致分为:

  • 注解与反射:AnnotationMetadataResolverAnnotationValueExtractorReflectionUtilsClassUtils

  • Bean 与泛型:BeanLambdaBeanUtilsGenericWrapperGenericsByteBuddy

  • Web 辅助:HttpServletUtilsHttpReactiveUtilsMultiValueMapSplitter

  • 数据结构与 ID:PairTreeNodeSnowflakeIdGenerator

  • 配置与上下文:YamlUtilsContextSnapshots

AnnotationMetadataResolver

根据包名查找含有某些注解的类

使用示例:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main{
    public static void main(String[] args){
      AnnotationMetadataResolver resolver = new AnnotationMetadataResolver();
      resolver.find(MyAnnotation.class,"com.livk.resolver");
    }
}
BeanLambda

根据lambda解析Field和Method

使用示例:

import java.lang.reflect.Field;
import java.lang.reflect.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行为传播

使用示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public static void main(String[] args){
  ExecutorService service = Executors.newCachedThreadPool();
  ExecutorService wrap = ContextSnapshots.wrap(service);
  wrap.submit(()->{});
}
GenericWrapper

进行类包装

使用示例:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

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"

使用示例:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

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一样的适配

使用示例:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main{
    public static void main(String[] args){
      Pair<String, String> pair = Pair.of("username", "password");
    }
}
SnowflakeIdGenerator

雪花算法ID生成器

使用示例:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main{
    public static void main(String[] args){
      SnowflakeIdGenerator generator = new SnowflakeIdGenerator(1, 2);
      long id = generator.nextId();
    }
}
TreeNode

树形节点定义通用类

使用建议

工具类优先用于基础设施代码或边界适配代码中。业务代码中如果只是简单调用,建议保持局部使用,避免把业务模型过度包装成通用结构。

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")));
    }
}
注意事项

RequestWrapper 修改 body 后,下游组件读取到的是包装后的内容。
ResponseWrapper 替换响应体后,需要确保过滤器链结束前将包装结果写回真实响应,避免客户端拿不到修改后的内容。

3.4. spring 组件拓展

兼容Spring的基础包
提供一些第三方包与spring整合的拓展,包括一些自定义拓展

如果只需要基础 API、自定义 Bean 或手动装配,可以引入 spring-extension-context
如果希望交给 Spring Boot 自动配置,业务项目通常直接引入对应 starter。

Maven
<dependency>
    <groupId>io.github.livk-cloud</groupId>
    <artifactId>spring-extension-context</artifactId>
    <version>${version}</version>
</dependency>
Gradle-Groovy
implementation 'io.github.livk-cloud:spring-extension-context:${version}'
Groovy-Kotlin
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 解析器

Maven
<dependency>
    <groupId>io.github.livk-cloud</groupId>
    <artifactId>spring-boot-extension-autoconfigure</artifactId>
    <version>${version}</version>
</dependency>
Gradle-Groovy
implementation 'io.github.livk-cloud:spring-boot-extension-autoconfigure:${version}'
Groovy-Kotlin
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 后可直接注入 CuratorFrameworkCuratorTemplate 使用;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());
    }

}
disruptor-spring-boot-starter

artifactId: disruptor-spring-boot-starter

引入 starter 后,扫描到的事件会注册对应 SpringDisruptor Bean。
业务代码可以注入 SpringDisruptor<T> 创建 DisruptorEventProducer<T>,也可以把生产者封装为领域服务。

使用建议

Disruptor 适合高吞吐、低延迟的内存事件处理场景。消费者执行逻辑应尽量短小,耗时 IO 建议转交给独立线程池或下游队列。

3.4.4. dynamic

动态数据源组件基于 DynamicDatasource 和 AOP 拦截器完成数据源切换。
手动模式可以直接注册 DynamicDatasource,Spring Boot 项目推荐使用 dynamic-datasource-boot-starter@EnableDynamicDatasource

DynamicSource

标记Service方法上,动态数据源切换

使用示例

@DynamicSource("mysql")
public class UserService{

}
DataSourceInterceptor

动态数据源切换拦截器,注册为Spring Bean即可

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 名称。

文件名和后缀

@ResponseExcel 支持指定 fileNamesuffix。如果使用 @ExcelController 默认配置,文件名为 out,后缀为 xlsm

fesod-spring-boot-starter

artifactId: fesod-spring-boot-starter

根据Servlet环境自动注册 ExcelMethodArgumentResolverExcelMethodReturnValueHandler

或Reactive环境自动注册 ReactiveExcelMethodReturnValueHandlerReactiveExcelMethodArgumentResolver

注意事项

导入大文件时建议使用流式解析器,并在 listener 中控制内存占用。
导出多 sheet 时建议返回 Map<String, List<?>>,便于明确 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:触发限流后的自定义处理器。

LimitExecutor

限流执行器基类,实现注册为Spring Bean即可 提供RedissonLimitExecutor作为实现,需要Redisson依赖

自定义限流存储时,实现 LimitExecutor 并注册为 Spring Bean 即可。
如果使用 Redisson 实现,需要先引入并配置 redisson-spring-boot-starter

LimitInterceptor

限流拦截器,注册为Spring Bean即,使用AOP实现

limit-spring-boot-starter

artifactId: limit-spring-boot-starter

引入 starter 后会自动注册限流拦截器。业务只需要在需要保护的方法上添加 @Limit

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:锁类型,支持 LOCKFAIRREADWRITE

  • leaseTime:锁租约时间,默认 10

  • waitTime:等待获取锁时间,默认 3

  • async:是否使用异步锁能力。

DistributedLock

分布式锁基类,实现注册为Spring Bean即可 提供RedissonLock作为实现,需要Redisson依赖 提供CuratorLock作为实现,需要Curator依赖

使用 Redisson 锁时需要配置 RedissonClient;使用 Curator 锁时需要配置 CuratorFramework
如果两种实现同时存在,建议业务显式保留一个 DistributedLock Bean,避免锁实现选择不符合预期。

DistributedLockInterceptor

分布式锁拦截器,注册为Spring Bean即,使用AOP实现

distributed-lock-boot-starter

artifactId: distributed-lock-boot-starter

starter 会注册拦截器和可用的锁实现。方法执行完成或异常退出时都会释放锁。

3.4.8. mapstruct

MapStruct 组件提供两类转换方式:

  • 自定义双向转换器:继承 com.livk.context.mapstruct.converter.Converter,通过 MapstructService 使用。

  • Spring 单向转换器:继承 org.springframework.core.convert.converter.Converter,通过 Spring ConversionService 使用。

使用示例
自定义转换器
继承 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)));
    }

}
mapstruct-spring-boot-starter

artifactId: mapstruct-spring-boot-starter

starter 会注册 MapstructServiceConverterRepositoryMapstructLocator
MapstructService 会优先从 Spring 容器查找匹配的 Converter<S, T>,找到后缓存到仓库中。

集合转换

MapstructService 内置集合转换方法:

  • convert(Collection<S>, Class<T>):返回 Stream<T>

  • convertList(Collection<S>, Class<T>):返回 List<T>

  • convertSet(Collection<S>, Class<T>):返回 Set<T>

如果没有找到匹配转换器,会抛出 ConverterNotFoundException

3.4.9. mybatis

MyBatis 扩展提供 SQL 字段自动注入、JSON 类型处理器和 SQL 执行耗时监控。

内置SQL数据注入工具
import javax.sql.DataSource;

@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

mybatis sql监控

添加com.livk.context.mybatis.MybatisSqlMonitor至MyBatis

并且配置timeOut和unit至Properties

Spring Boot starter 中配置前缀为 mybatis.log.monitor

mybatis:
  log:
    monitor:
      time-out: 1s
mybatis-extension-boot-starter

artifactId: mybatis-extension-boot-starter

starter 会自动注册 SqlDataInjection 和 SQL 监控相关配置。JSON TypeHandler 仍需要在 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 以便在代码中动态设置尺寸和颜色。

qrcode-spring-boot-starter

artifactId: qrcode-spring-boot-starter

引入 starter 后会注册二维码参数解析器和返回值处理器。

3.4.11. redisearch

封装 RediSearchTemplateStringRediSearchTemplate 来操作 RediSearch。
自动配置基于 lettuce-mod 创建 RediSearchConnectionFactory,并在存在 Jackson 时提供 JSON 序列化的模板。

配置

配置前缀为 spring.redisearch,默认连接 localhost:63790 号库。

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;
    }

}
redisearch-spring-boot-starter

artifactId: redisearch-spring-boot-starter

3.4.12. redisson-spring-boot-starter

artifactId: redisson-spring-boot-starter

适配 Spring Boot 的 yamlproperties 配置文件,使用 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:基于 Redis INCRBY 分段取号,可使用 Lettuce 或 Spring Data Redis 连接。

SequenceBuilder 支持设置:

  • bizName:业务名称,必填,不同业务使用不同号段。

  • step:每次申请的号段步长,默认 1000

  • stepStart:初始值,默认 0,真实发号从 stepStart + 1 开始。

import javax.sql.DataSource;

@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;
   }
}
注意事项

数据库模式会自动创建 sequence_range 表,并通过乐观更新获取下一段区间。
Redis 模式会使用 x_sequence_ 前缀生成 key。部署多实例时,所有实例必须连接同一套数据库或 Redis,才能保证全局递增。

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;
    }

}
解析器选择

starter 根据类路径自动注册解析器:

  • 引入 browscap-spring-boot-starter 时,使用 BrowsCap 的 UserAgentParser

  • 引入 yauaa-spring-boot-starter 时,使用 YAUAA 的 UserAgentAnalyzer

如果项目中同时存在多个 UserAgentConverterUserAgentHelper 会按 Spring Bean 顺序依次尝试转换,返回第一个非空结果。

browscap-spring-boot-starter

artifactId: browscap-spring-boot-starter

yauaa-spring-boot-starter

artifactId: yauaa-spring-boot-starter

3.5. spring testcontainers support容器拓展

提供自定义testcontainers与spring boot的支持

Maven
<dependency>
    <groupId>io.github.livk-cloud</groupId>
    <artifactId>spring-testcontainers-support</artifactId>
    <version>${version}</version>
    <scope>test</scope>
</dependency>
Gradle-Groovy
testImplementation 'io.github.livk-cloud:spring-testcontainers-support:${version}'
Groovy-Kotlin
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。

3.5.2. ZookeeperContainer

封装 Zookeeper Testcontainers 容器,默认暴露 2181 端口。

@TestConfiguration(proxyBeanMethods = false)
class ContainerConfiguration {

    @Bean
    @ServiceConnection
    ZookeeperContainer zookeeperContainer() {
        return new ZookeeperContainer();
    }

}

3.5.3. ServiceConnection

spring-testcontainers-support 会通过 ConnectionDetailsFactoryZookeeperContainer 生成连接信息。
在 Spring Boot 测试中使用 @ServiceConnection 后,可以自动获得 Zookeeper 的 host:mappedPort 连接串。

4. 问题

4.1. 常见问题

4.1.1. 为什么 starter 引入后没有生效

请先确认已经引入对应 starter,而不是只引入了 spring-extension-context
spring-extension-context 提供基础 API,starter 才会引入对应自动配置模块并注册 Spring Bean。

4.1.2. 自动配置文件没有生成

spring-auto-service 是编译期注解处理器,需要同时加入 compileOnlyannotationProcessor
如果使用 Kotlin,需要按项目的 Kotlin KAPT/KSP 配置接入注解处理器。

4.1.3. Redisson、Curator、Redisearch 连接失败

先检查配置前缀是否正确:

  • Redisson:spring.redisson

  • Curator:spring.zookeeper.curator

  • RediSearch:spring.redisearch

再确认中间件地址、端口、认证信息与本地网络环境。使用 Testcontainers 时,可优先使用 @ServiceConnection 或测试配置注入连接信息。

4.1.4. WebMVC 与 WebFlux 能力如何选择

大部分 Web 能力会根据类路径自动适配。Servlet 项目使用 Spring MVC 类型,响应式项目使用 WebFlux 类型。
如果同一个工程同时存在两套 Web 依赖,请明确使用对应的 starter 和控制器参数类型,避免解析器匹配不符合预期。

4.1.5. 表达式解析器如何选择

默认可以使用 Spring EL。Aviator、FreeMarker、Jexl3、Mvel2 等解析器依赖对应第三方库,业务项目需要自行引入相应依赖。