本文首先介绍了如何自定义一个springboot starter, 并在项目中使用。然后对springboot 自动配置原理进行了简要的说明。
本文基于SpringBoot 2.2.4.RELEASE 版本
自定义一个starter的步骤
可参考:www.pdai.tech/md/spring/s…
等有时间了再整理
starter的自动配置原理
先抛出结论:
通过@EnableAutoConfiguration 上导入的 AutoConfigurationImportSelector,会其中selectImports()方法通过SpringFactoriesLoader扫描所有具有META-INF/spring.factories的jar包。这个spring.factories文件是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔。
在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。
在每一个starter中都有一个xxxxAutoConfiguration的类,该类会被加载到容器,该类上有一个注解 @EnableConfigurationProperties 会将这个starter的配置类也同样加载到容器中,这样就能读取application.yml中的配置项,并赋值到这个Properties类中。
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。
注解 @SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
这个注解放在启动类上,而且包含了多个注解,接下来我们一个个看。
注解 @SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
...
}
事实上只是 @Configuration 的另一种声明,标识是SpringBoot的配置类。同时该类上面也加了 @Configuration 注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
...
}
注解 @EnableAutoConfiguration
根据字面意思,启动自动配置,这个看名字应该是重点,先看源码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
*/
String[] excludeName() default {};
}
根据属性可以看到,如果某些自动配置类不需要用到,可以在这里手动排除掉。接下来看注解 @AutoConfigurationPackage。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
这个类的说明如下,表示了需要注册的注解类所在的包路径,暂且这么理解,继续往下看。这里使用了另外一个注解 @Import。
Indicates that the package containing the annotated class should be registered with
先看看这个注解有什么用, 根据文档描述,它的功能等效spring中的<import/>标签,表示需要导入的组件类,作用是给容器中导入一个组件。上面的意思就是需要加载 AutoConfigurationPackages.Registrar.class 到容器,Registrar翻译为登记员,暂且翻译为“自动配置包名登记员”。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
启动时断点可知,其实就是把启动类所在的包,注册为组件所在的根包名,也就是说,只有在spring-boot所在主类(@SpringBootApplication标注的类)所在包下的组件,才有可能加载到容器中。
注解 AutoConfigurationImportSelector
里面有个process方法
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
这里重点关注 getAutoConfigurationEntry 方法,进一步查看,会调用以下这个loadSpringFactories方法。
这里有个常量:public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
看到这里,大概就明白了,每个starter在META-INF/spring.factories定义好需要加载的类,然后springboot会自动读取里面的内容,将对应的AutoConfiguration配置类加载到容器中,从而实现了自动配置。