SpringBoot starter自动配置原理

461 阅读4分钟

本文首先介绍了如何自定义一个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配置类加载到容器中,从而实现了自动配置。

参考