一.前言
hello,everyone。好久不见,使用springboot的同学对springboot提供的各种各样的starter都不陌生。那么日常工作中如果我们想开发一个starter供其他同事来使用,我们该怎么做呢?
二.概念讲解
在springboot项目的pom文件中会发现很多带starter的pom包,这也是springboot的一个典型特点,starter是什么?怎么用的?
开发springboot最常见的starter就是:
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
引入了这个maven依赖,基本就满足了日常的web接口开发,而不使用springboot时,需要引入spring-web、spring-webmvc、spring-aop等等来支持项目开发。
对于其他一些starter,比如要使用redis、jpa等等,就不仅仅是引入依赖了,还需要实现一些初始的配置,比如常使用@Configuration,这个注解就会在springboot启动时去实例化被其修饰的类,前提是springboot配置了@EnableAutoConfiguration。而springboot启动类默认的**@SpringBootApplication中默认包含了该注解,所以不用再显示引入,最后需要在starter项目中META-INF/spring.factories**中添加:
代码语言:javascript复制org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.xxx.Xxx
这样在springboot启动时,才能正确加载自定义starter的配置。
所以说,starter中简单来讲就是引入了一些相关依赖和一些初始化的配置。
为什么加了**@Configuration注解还是要配置META-INF/spring.factories呢?因为springboot项目默认只会扫描本项目下的带@Configuration注解的类,如果自定义starter,不在本工程中,是无法加载的,所以要配置META-INF/spring.factories**配置文件。
为什么配置了META-INF/spring.factories配置文件就可以加载?这里才是springboot实现starter的关键点,springboot的这种配置加载方式是一种类SPI(Service Provider Interface)的方式,SPI可以在META-INF/services配置接口扩展的实现类,springboot中原理类似,只是名称换成了spring.factories而已。
关于springboot如何来实现的spi机制,强烈建议先阅读博主的另一篇博客:一文吃透@SpringbootApplication的前世与今生,对springboot中如何花式加载bean有一个了解。
三.配置类定义
根据第二点的概念我们得知,如果我们要编写一个提供被springboot管理的自动配置的stater,首先我们要做的是编写配置类。
本文为了大家能够更加清晰的明白statrer的使用方式,这里以博主在github上开源的公有微服务框架的common-interaction模块为例
项目地址:https://github.com/louyanfeng25/common-frame
相信大家日常工作中都需要对返回给前端或者前端给到后端的日期数据做一定的格式化处理,展示成yyyy-MM-dd HH:mm:ss的格式。常规的在springboot中解析出入参时间格式可以使用**@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")。这样前端传入的yyyy-MM-dd HH:mm:ss字符串,就可以映射到后端的LocalDateTime**类型上。
但是这样麻烦的一个地方是,我对日期格式化输入输出的地方很多,如果每个地方我都要单独处理,那么实体类上面标注的注解就会很多。基于springboot的约定高于配置的原则,我们肯定是前端约定,对于日期的交互,我们就是以yyyy-MM-dd HH:mm:ss格式,那么如何进行全局处理?
增加全局的jackson序列化配置【springboot默认使用jackson来进行序列化与反序列化】
代码语言:javascript复制/**
* 全局时间格式化
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.simpleDateFormat(dateTimeFormat);
//日期序列化
builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ofPattern(timeFormat)));
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
//日期反序列化
builder.deserializers(new LocalTimeDeserializer(DateTimeFormatter.ofPattern(timeFormat)));
builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
};
}
ok,到这里,在当前项目中已经对日期的格式化输入输出统一配置处理了。那如果其他工程也想使用这个功能,显然每个工程内部定义这样一个bean是一件很蠢的事情。把这个配置做到统一的基础配置starter中是最优的选择。
定义了bean还不能被springboot给加载,需要**@Configuration**来配合使用。
代码语言:javascript复制@Configuration
public class CommonJacksonConfig {
public static final String timeFormat = "HH:mm:ss";
public static final String dateFormat = "yyyy-MM-dd";
public static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
/**
* 全局时间格式化
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.simpleDateFormat(dateTimeFormat);
//日期序列化
builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ofPattern(timeFormat)));
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
//日期反序列化
builder.deserializers(new LocalTimeDeserializer(DateTimeFormatter.ofPattern(timeFormat)));
builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
};
}
}
四.配置spring.factories
定义完配置类后,需要将这个bean暴露出来。这么做的原因也是因为被**@SpringbootApplication标注的类,除非定义了扫描的包路径,不然默认是扫描被@SpringbootApplication标注的类**所在的包路径。这也是为什么官方建议启动类放置在最外层的包,是为了保证当前项目中定义的bean定义的bean都能被springboot扫描并加载。
新建resources目录下新建**/META-INF/spring.factories**文件,增加配置
代码语言:javascript复制org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.baiyan.common.interaction.config.CommonJacksonConfig
五.选择性加载与额外配置
经过上述步骤之后,自动配置类就已经做好了。但是这里还有两个问题。
5.1.选择性加载
jar包是统一提供的,jar包内所有的配置都会被加载,但是对于我来说有些配置是无效的,我不想开启,比如本文的全局格式处理。我不想让他加载进来,除了直接把这个类再启动类的地方排除掉,我们还可以在配置类的上面这在注解配置
代码语言:javascript复制@ConditionalOnProperty(value = "common.config.jackson.enable", havingValue = "true")
这样只有配置文件中配置了common.config.jackson.enable为true时候这个配置类才会被加载,默认无配置或者配置未false时不加载。
5.2.额外配置
如果我们定义的start中有特别多的bean。但是其中有大部分的bean都是互相依赖的:
- 例如A依赖B,B依赖C
- 例如A的生效与否依赖与项目中是否存在B这个类或者C这个bean
- ... 这个时候可以是用@Import注解来加载关联的配置类,是用**@ConditionalOnBean**来决定是否加载对应的配置。
六.推送
到第五步,starter已经制作完成,可以是用maven把你定义的starter推送至公司的私仓,这样其他项目直接引入你推送的maven配置。如果未做条件性判断加载bean的情况,直接启动项目就可以在加载配置。否则添加对应的配置即可。
七.参考与引用
SpringBoot中starter原理简介