tips:本系列文章的代码,均为 maven 构建, SpringBoot 版本 2.x。 (3.x 不再以 spring.factories 作为导入全路径名, Spring Boot 2.7 已经废弃,在 Spring Boot 3.0 彻底移除)
本文将自定义一个 Starter,所谓麻雀虽小五脏俱全,感受一下 Starter 的五脏六腑。
本项目源码地址: gitee.com/uzongn/uzon...
一、定义一个 Starter 需要哪些步骤
- 创建一个新的 Maven 项目作为 Starter 项目,主要是一些业务实现类
- 创建一个或多个配置类,使用 @Configuration 注解
- 创建 spring.factories 文件,位置:src/main/resources/META-INF/spring.factories ,在文件中添加对自动配置类的引用
- 使用 @ConfigurationProperties 注解创建一个属性类,以支持从 application.properties 或 application.yml 文件中读取配置。(可选)
- 将 Starter 项目打包并可能发布到 Maven 中央仓库或私有仓库
- 引用和使用
那么接下来将围绕这几个步骤实现一个 Starter。
二、Starter 应用场景
每一个 Starter 都应该解决一个场景。本案例选择一个简单的缓存场景,开发环境使用本地缓存,预发环境使用 redis。
在实际应用过程中,会根据环境装配不同的实现。
作为案例,由于保持简单,因此只提供了三个接口。put、get、delete。
csharp
/**
* 缓存服务接口定义,提供基本的缓存操作方法。
*/
public interface CacheService<K, V> {
/**
* 将键值对存储到缓存中。
*/
void put(K key, V value);
/**
* 根据键从缓存中获取值。
*/
V get(K key);
/**
* 从缓存中删除指定的键和对应的值。
*/
void delete(K key);
}
下面是实现的类图, 一个是基于 redis,另外一个基于 ConcurrentHashMap。
三、Starter 快速案例
注意:生成环境,使用缓存需要小心,比如缓存失效时间、考虑复杂数据结构等。
3.1 Starter 的结构
maven 项目的结构如下所示:
- auto-configuration
- spring.factories
- service..
3.2 xxAutoConfiguration 类
auto-configuration 主要负责 bean 的加载,同时会根据条件注解,进行选择加载。比如本案例中还会出现一些条件注解:
-
ConditionalOnClass: 依赖场景,比如本案例中 RedisCacheService 依赖 RedisTemplate
-
ConditionalOnMissingBean 如果不存在特定的bean,则会加载,用于互斥场景
-
ConditionalOnProperty 条件装配,只有属性 cache.type 为 redis 才加载
less
@Configuration
public class SimpleCacheAutoConfiguration {
/**
* 1. ConditionalOnClass 依赖 RedisTemplate
* 2. ConditionalOnProperty 条件装配
*
*/
@Bean
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(name = "cache.type", havingValue = "redis")
public <K, V> SimpleCacheService<K, V> redisTemplateService(RedisTemplate redisTemplate) {
return new RedisCacheService<K, V>(redisTemplate);
}
/**
* 条件装配,ConditionalOnMissingBean 如果不存 SimpleCacheService 在则加载
*/
@Bean
@ConditionalOnMissingBean
public <K, V> SimpleCacheService<K, V> inMemoryCacheService() {
return new InMemoryCacheService<K, V>();
}
}
注:条件装配的注解远不止上面几个。
3.3 xxAutoConfiguration注入 bean
留意一个细节,我们常常只会在 xxAutoConfiguration 中注入 bean,而基本不使用包扫描;避免不必要的类加载到容器; 另外使用路径扫描,可能会将相同路径下的其他包,扫描到容器中。 侧面也警醒我们需要将包路径尽量命名唯一。
实现类,不添加 @Service 等注解, Bean 的注入,由 xxAutoConfiguration 控制;实现统一、灵活的 bean 加载。
3.4 配置spring.factories
文件名和路径名要求严格一致。文件中的可以值,必须是 org.springframework.boot.autoconfigure.EnableAutoConfiguration
; value可以用逗号分隔,可以有多个 xxAutoConfiguration。
3.5 ConfigurationProperties 可选
ConfigurationProperties 是为了增加一定的灵活性而存在;不是必须,可以直接使用 ConditionalOnProperty
等替代。本项目略过。
@ConditionalOnProperty(name = "cache.type", havingValue = "redis")
3.6 maven 依赖
关于 maven 保持最小依赖,保持干净。
注意 maven 中两个细节,分别是:
<scope>provided</scope>
<optional>true</optional>
这个内容,我们将在实战篇中详细说明。
特别注意: packaging 要求 为 jar
3.7 测试打包
可以先打包到本地,然后测试使用,没有问题后,再上传到公司的私服。
maven 中的 packaging 为 jar,而不是 pom,否则上传不成功。
3.8 使用
引入 maven 依赖,即可使用,可以通过条件装配使用不同的实现。
xml
<dependency>
<groupId>com.uzong</groupId>
<artifactId>simplecache-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
引用接口; 实现类可以通过条件装配,加载不同的实现类。
在 application.propterties 中选择不同的实现。
3.9 验证阶段
启动应用程序,并访问地址。
http://localhost:8080/api/cache/test?key=1&value=a
到这里,自定义 Starter 已经完成。可以下载代码进行体验。
四、其他注意事项(补充阅读)
4.1 Bean 定义顺序
在同一个 configuration 中的 Bean, 其加载是按照从上到下的顺序进行加载; 所以在使用条件装配的时候,务必要注意顺序。
比如下面情况会,在配置了 cache.type=redis
因为顺序加载解析 ConditionalOnMissingBean(不存在则加载),所以会先加载 InMemoryCacheService,然后又加载 InMemoryCacheService,出现两个实现类。所以在实现的时候,需要控制好条件注解和bean加载的顺序。
4.2 Starter 没有生效排查
如果发现自己的 Starter 没有生效。比如找不到实现类
A component required a bean of type 'com.uzong.simple.cache.SimpleCacheService' that could not be found.
其一、org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
断点排查,查看是否引入 auto-configuration 类
这里的内容,我们先不深入,后面我们会深入源码进入分析。
其二、检查条件配置等,是否配置错误。当然可能还会有其他问题,需要一一对症下药。
4.3 autoconfigure 模块与 Starter 模块
官网建议将自动装配相关类放在 autoconfigure 模块中,Starter 模块依赖引用这个模块,并且引入其他需要的依赖, 这不是必须的。而我们自定义 Starter, 常常会将这两者合并到一个模块中。不过 mybatis-spring-boot-starter 则使用这种方式。
五、最后小结
本章快速定义了一个 Starter,并提供了源码实现,可以下载源码进行体验。这一章可以作为实现 Starter 的参考范本。
接下来,以 mybatis-spring-boot-starter 为例,看看 Mybatis 是如何使用 Starter 的,并逐步揭开 Starter 底层原理的面纱。