starter背景
在第一章中,已经简单的介绍了java spi以及spi的用法以及优缺点。那spring中是否有用到spi机制呢?我们如何自定义一个starter呢?
我们先简单的定义一个springboot版 starter
在springboot starter官方命名推荐中,有两种形式
官方命名
spring-boot-starter-模块名
eg:spring-boot-starter-web、spring-boot-starter-jdbc、spring-boot-starter-thymeleaf
自定义命名
模块名xxx-spring-boot-starter
eg:mybatis-spring-boot-start
在定义starter的时候,也有相应的规则
启动器模块是一个空 JAR 文件,仅提供辅助性依赖管理,这些依赖可能用于自动 装配或者其他类库
启动器只用来做依赖导入
专门来写一个自动配置模块;
启动器依赖自动配置模块,项目中引入相应的starter就会引入启动器的所有传递依赖
starter示例
我们按照这个规范定义一个demo
1.先定义一个starter项目
1 | demo-spring-boot-starter |
再在starter下定义一个autoconfigure模块
1 | hello-starter-autoconfigure |
starter pom文件如下
1 | <?xml version="1.0" encoding="UTF-8"?> |
hello-starter-autoconfigure pom文件如下
1 | <?xml version="1.0" encoding="UTF-8"?> |
只需要引入最基础的spring-boot-starter即可,注意需要在pom文件中去掉
1 | <build> |
否则没有main方法主类无法打包
- 在hello-starter-autoconfigure模块下建立所需要的文件,在这里最简单化
包含HelloService和实现类,HelloAutoConfiguration配置类,具体如下
1 | public interface HelloService { |
1 | public class HelloServiceImpl implements HelloService { |
1 | @Configuration |
最重要的一个步骤,在resources下建立META-INF文件夹,然后建立spring.factories文件,在spring.factories文件中对需要扫描的配置类进行配置,在这里是
##Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.example.hellostarterautoconfigure.autoconfigure.HelloAutoConfiguration
然后对hello-starter-autoconfigure打包,再对demo-spring-boot-starter进行打包,因为demo-spring-boot-starter包含hello-starter-autoconfigure,所以顺序不能反。
这样一个可用的简单版的starter就搭建好了,我们在其他项目中导入测试一下
我们导入starter的坐标
1 | <dependency> |
建立一个HelloController测试类
1 | @RestController |
开启测试
返回shuaizx,starter测试完成
starter原理
想要知道starter是怎么运行的,首先要知道springboot是怎么运行的,starter中的配置加载只是springboot加载中的一个过程。
我们直接找到main方法的@SpringBootApplication注解,在观察@EnableAutoConfiguration注解,这个注解的意思就是启动自动配置的能力意思,我们刚才在starter spring.factories文件也配置了这个注解的key,value形式,那自动配置又是怎么做到的呢?
1 | @Target(ElementType.TYPE) |
我们看到在EnableAutoConfiguration上有
@Import(AutoConfigurationImportSelector.class)
的自动引入字样
@Import 可以配置三种不同的class
- 普通bean或者有@Configuration 的bean
- 实现ImportSelector 接口进行动态注入
- 实现ImportBeanDefinitionRegistrar 接口进行动态注入
AutoConfigurationImportSelector
通过名字可以猜到它是基于第二种情况实现bean 的加载功能
关键就在这里
AutoConfigurationImportSelector这个类实现了DeferredImportSelector接口,DeferredImportSelector接口继承了ImportSelector接口
1 | public interface DeferredImportSelector extends ImportSelector |
ImportSelector接口是Spring导入外部配置的核心接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)中起到了决定性的作用。当在@Configuration标注的Class上使用@Import引入了一个 ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。
DeferredImportSelector接口继承自ImportSelector,它和ImportSelector的区别在于装载bean的时机上,DeferredImportSelector需要等所有的@Configuration都执行完毕后才会进行装载。
AutoConfigurationImportSelector类中的selectImports方法实现如下
1 | @Override |
String[]返回的字符串会全部被注入到bean容器中,我们看getAutoConfigurationEntry这里面的实现。在这个实现中对获取到了的类进行了去重加过滤等,最后过滤出来的configurations截图如下,有我们在前面设置的com.example.hellostarterautoconfigure.autoconfigure.HelloAutoConfiguration,
1 | protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, |
configurations类路径字符串又是从getCandidateConfigurations方法中获取的,再继续进入getCandidateConfigurations方法查看里面的关键实现
1 | protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { |
最终调用了SpringFactoriesLoader.loadFactoryNames这个方法,getSpringFactoriesLoaderFactoryClass()方法返回的是EnableAutoConfiguration.class,,待会会通过这个类对获取到的map进行过滤
1 | protected Class<?> getSpringFactoriesLoaderFactoryClass() { |
1 | public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { |
loadFactoryNames方法调用了loadSpringFactories方法,然后获取到的map在进行过滤,通过刚才传入的EnableAutoConfiguration.class,没有则返回空数组
1 | private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { |
SpringFactoriesLoader这个类中的属性FACTORIES_RESOURCE_LOCATION 就是我们熟悉的META-INF/spring.factories路径
1 | /** |
loadSpringFactories方法在springboot初始化中就调用过,在初始化时会对这个地方会进行缓存,后续调用这个方法直接通过cache获取到map,然后过滤。
1 | Enumeration<URL> urls = (classLoader != null ? |
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) 就是对META-INF/spring.factories下的资源进行获取。后面处理的时候会返回类的全路径然后过滤。最终返回我们设置的starter配置的类路径。
总结一下就是
- AutoConfigurationImportSelector实现了ImportSelector接口,实现selectImports方法,返回的字符串会定义为bean
- getCandidateConfigurations方法调用了SpringFactoriesLoader.loadFactoryNames方法,传入了EnableAutoConfiguration.class进行过滤,返回所有被EnableAutoConfiguration修饰的类路径
- 对getCandidateConfigurations返回的路径再次过滤,得到我们想要的类路径,进行后续操作
这就是自动装配的原理,也是我们定义一个starter必备的过程。