springboot为什么通过一个全局配置文件(application.properties | | application.yml)就能配置整个项目的各种属性呢?原因就在于springboot为我们做好了自动配置,那么springboot是怎样做到自动配置的呢?接下来我们就来讲解其原理。
springboot自动配置的关键注解就是@EnableAutoConfiguration,我们通常使用在启动类上的@SpringbootApplication就是继承了该注解才可以成功自动配置。
当我们使用springboot的时候,所包含的jar包中会有个名为spring-boot-autoconfigure,该jar包包含了spring-boot-starter-web所需要自动配置的类信息,若有依赖其他spring-boot-starter-xxx,那么也将会有其类似spring-boot-starter-web的spring-boot-autoconfigure jar包,届时,所有的自动配置的类信息都会被扫描到。
@Import(AutoConfigurationImportSelector.class),该注解是将AutoConfigurationImportSelector.class这个类注入到spring容器里来(关于@Import是如何实现注入的可以看我发的相关分析文章),通过分析这个类发现其实现了ImportSelector接口,那么直接看其重写的selectImports方法。
【在当前项目下所有具备META-INF/spring.factories和META-INF/spring-autoconfigure-metadata.properties的jar包都会被扫描到】
在selectImports方法中会先加载META-INF/spring-autoconfigure-metadata.properties文件里的类,
再通过getCandidateConfigurations方法里的SpringFactoriesLoader.loadFactoryNames方法中的loadSpringFactories方法扫描META-INF/spring.factories里的所有自动配置类,而令其自动配置类生效所必须具备的条件就是上面先加载的类(这里所说的条件就是后面要说的条件注解)。
【在SpringFactoriesLoader类中就已定义了】
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
这也就是为什么先扫描加载META-INF/spring-autoconfigure-metadata.properties,而后扫描META-INF/spring.factories。
条件注解
在spring.factories里的每一个xxxxAutoConfiguration自动配置类都是需要在某些特定的条件下才会生效的,这些条件注解定义在org.springframework.boot.autoconfigure.condition包下
@ConditionalOnBean(XXX):当前容器存在指定的bean时才会加载。
@ConditionalOnMissingBean(XXX):当前容器不存在该指定bean时才会加载。
@ConditionalOnClass(XXX):当前类路径下存在指定类时才会加载。
@ConditionalOnMissingClass(XXX):当前类路径下不存在指定类时才会加载。
所以这就能解释为什么在springboot中如果自己配置了个继承WebMvcConfigurationSupport的配置类或者是注解了@EnableWebMvc(因为该注解@import了DelegatingWebMvcConfiguration类,而这个类是实现了WebMvcConfigurationSupport的)的时候,WebMvcAutoConfiguration这个自动配置类会失效。
全局配置文件信息绑定到类的属性
在自动配置中经常会碰到这个注解@ConfigurationProperties,这个注解的作用就是将全局配置文件中有关的信息注入到被@ConfigurationProperties所注解的类的属性上。
prefix = “server” :表示在全局配置文件里以server为前缀的配置信息来绑定
ignoreUnknownFields = true:告诉Spring Boot即使有属性不能匹配到声明的域的时候也不会抛出异常;若为false,则会抛出异常。
locations: 表示所要绑定的全局配置文件的位置。
该注解只负责将配置文件中的内容绑定到类的属性,而不会将其实例化成一个bean对象,若想要将其变为一个bean对象,有两个办法。
- 在类上加上@Component
- 通过@EnableConfigurationProperties(xxxx.class【该xxxx.class为注解了 @ConfigurationProperties的类】)来将xxxx.class注入到spring的容器中。
@EnableConfigurationProperties的使用后期会在《springboot如何内嵌tomcat》文章中一并将其讲解。
@EnableConfigurationProperties的原理
@EnableConfigurationProperties通过@Import(EnableConfigurationPropertiesImportSelector.class),将EnableConfigurationPropertiesImportSelector注入到了spring容器,该类实现了ImportSelector接口,所以在spring的解析过程中,会将其实例化并执行重写的selectImports方法,其返回的数组类名将会被放入到bdmap里。
IMPORTS = {ConfigurationPropertiesBeanRegistrar.class.getName(),ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };而这些返回的类名,会再被实例化并判断其继承或实现了哪些接口进而执行其相应重写的方法,最后将其放入到spring容器中。
private static final String[] IMPORTS = {
ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return IMPORTS;
}
public static class ConfigurationPropertiesBeanRegistrar
implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
getTypes(metadata).forEach((type) -> register(registry,
(ConfigurableListableBeanFactory) registry, type));
}
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
EnableConfigurationProperties.class.getName(), false);
return collectClasses((attributes != null) ? attributes.get("value")
: Collections.emptyList());
}