提供spring boot扩展包,包含自动装配、starter、一些工具类等。
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}"))
3. 使用指导
3.1. spring boot装配文件自动生成
根据代码定义生成spring boot的自动装配文件和spring.factories、aot.factories
<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使用示例
@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 使用示例
@SpringFactories支持生成aot.factories原理基本同下,只需指定属性aot=true
指定接口为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.2. spring通用工具拓展
提供一些通用、工具类方便开发
<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.2.1. aop
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();
}
}
AnnotationPointcutType
提供四种可选方案
-
TYPE基于类级别的拦截等价于(@Around(@within(Annotation)))
-
METHOD基于方法级别的拦截等价于(@Around(@annotation(Annotation)))
-
TYPE_OR_METHOD基于类或方法级别的拦截等价于(@Around(@annotation(Annotation)||@within(Annotation)))
-
AUTO根据Annotation Target推断(如果仅有TYPE、则为TYPE级别。如果仅有METHOD、则为METHOD级别。如果同时都有则为TYPE_OR_METHOD级别。以上情况都无法出现则抛出异常)
3.2.2. expression
AbstractExpressionResolver
ExpressionResolver抽象实现
将Map或者Method等信息转成Context
使用示例:
public class MyExpressionResolver extends AbstractExpressionResolver {
@Override
public <T> T evaluate(String value, Context context, Class<T> returnType) {
//TODO:解析表达式
}
//重写此方法用于调整Context的解析
@Override
protected ContextFactory getContextFactory() {
return super.getContextFactory();
}
}
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;
}
}
ConverterExpressionResolver
用于适配不同的解析工具
将Context转成相对于的上下文环境
public class MyExpressionResolver extends ConverterExpressionResolver<EvaluationContext, Expression> {
//将上下文转成组件的上下文
@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.2.3. http
EnableHttpClient
EnableHttpClient会根据value值自动导入对应的http客户端
使用示例:
@Slf4j
@EnableHttpClient({
HttpClientType.REST_TEMPLATE,
HttpClientType.WEB_CLIENT,
HttpClientType.REST_CLIENT
})
public class App {
@Bean
public ApplicationRunner applicationRunner(WebClient webClient,
RestTemplate restTemplate,
RestClient restClient) {
return args -> {
log.info("restTemplate:{}", restTemplate);
log.info("webClient:{}", webClient);
log.info("restClient:{}", restClient);
};
}
}
同时提供了@EnableRestClient、@EnableRestTemplate和@EnableWebClient作为快捷方式
3.2.4. jackson
3.2.5. util
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);
}
}
GenericWrapper
进行类包装
使用示例:
public class Main{
public static void main(String[] args){
GenericWrapper<?> wrapper = GenericWrapper.of("123");
wrapper.unwrap()
}
}
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);
}
}
3.2.6. web
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.3. spring 组件拓展
兼容Spring的基础包
提供一些第三方包与spring整合的拓展,包括一些自定义拓展
<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的自动装配特性,自定义配置文件来覆盖官方的配置
<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.3.1. curator
3.3.2. disruptor
DisruptorScan
类似于MyBatisScan用与扫描DisruptorEvent标记的实体
根据实体和DisruptorEvent生成SpringDisruptor<DisruptorEventWrapper<T>>队列
使用示例
@DisruptorScan("com.livk.disruptor")
public class Config{
}
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 Consumerimplements 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.3.3. dynamic
DynamicDatasource
动态数据源,使用DataSourceContextHolder进行数据源切换
使用示例
@Configuration
public class Config {
@Bean
public DynamicDatasource dynamicDatasource() {
DynamicDatasource dynamicDatasource = new DynamicDatasource();
dynamicDatasource.setTargetDataSources(datasourceMap);
dynamicDatasource.setDefaultTargetDataSource(datasourcePrimary);
return dynamicDatasource;
}
}
3.3.4. fastexcel
Excel导入
使用注解 @ExcelImport
解析Excel(支持Spring Webflux)
fileName指定文件名称
parse使用自定义封装FastExcel的解析器
com.livk.excel.mvc.listener.InfoExcelListener
paramName指定需要传递至那个参数
@RestController
public class InfoController {
@ExcelImport(parse = InfoExcelListener.class, paramName = "dataExcels")
@PostMapping("uploadList")
public HttpEntity<List<Info>> uploadList(List<Info> dataExcels) {
return ResponseEntity.ok(dataExcels);
}
@ExcelImport(parse = InfoExcelListener.class, paramName = "dataExcels")
@PostMapping("upload")
public HttpEntity<List<Info>> upload(List<Info> dataExcels) {
return ResponseEntity.ok(dataExcels);
}
@ExcelImport(parse = InfoExcelListener.class, paramName = "dataExcels")
@PostMapping("uploadMono")
public Mono<HttpEntity<List<Info>>> uploadMono(Mono<List<Info>> dataExcels) {
return dataExcels.map(ResponseEntity::ok);
}
}
Excel导出
使用注解 @ExcelReturn
或者 @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 {
@ExcelReturn(fileName = "outFile")
@ExcelImport(parse = InfoExcelListener.class, paramName = "dataExcels")
@PostMapping("uploadDownLoad")
public List<Info> uploadDownLoadMono(List<Info> dataExcels) {
return dataExcels;
}
@ExcelReturn(fileName = "outFile")
@ExcelImport(parse = InfoExcelListener.class, paramName = "dataExcels")
@PostMapping("uploadDownLoadMono")
public Mono<List<Info>> uploadDownLoadMono(Mono<List<Info>> dataExcels) {
return dataExcels;
}
@ExcelReturn(fileName = "outFile")
@ExcelImport(parse = InfoExcelListener.class, paramName = "dataExcels")
@PostMapping("uploadDownLoadFlux")
public Flux<Info> uploadDownLoadFlux(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)));
}
}
3.3.5. http-spring-boot-starter
artifactId: http-spring-boot-starter
使用示例,在接口上添加 @Provider
或者 @HttpExchange
兼容reactor Mono
Flux
使用方式类似于Feign, 被注解标准的接口需要在Spring包扫描下
@Provider(url = "https://spring.io")
public interface RemoteService {
@GetExchange()
String get();
}
@Provider(url = "https://spring.io")
@Slf4j
@RestController
@RequiredArgsConstructor
public class HttpController {
private final RemoteService service;
@PostConstruct
public void init() {
log.info("get length:{}", service.get().trim().length());
}
@GetMapping("get")
public HttpEntity<String> get() {
return ResponseEntity.ok(service.get());
}
}
3.3.6. limit
3.3.7. lock
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) {
}
}
3.3.8. mapstruct
使用示例
自定义转换器
继承 com.livk.autoconfigure.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)));
}
}