SpringBoot的配置属性值是如何绑定的? SpringBoot源码(五)


SpringBoot的配置属性值是如何绑定的? SpringBoot源码(五)

注:该源码分析对应SpringBoot版本为2.1.0.RELEASE

1 前言
本篇接 SpringBoot是如何实现自动配置的?–SpringBoot源码(四)

温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了SpringBoot的自动配置的相关源码,自动配置相关源码主要有以下几个重要的步骤:

  1. 从spring.factories配置文件中加载自动配置类;
  2. 加载的自动配置类中排除掉@EnableAutoConfiguration注解的exclude属性指定的自动配置类;
  3. 然后再用AutoConfigurationImportFilter接口去过滤自动配置类是否符合其标注注解(若有标注的话)@ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication的条件,若都符合的话则返回匹配结果;
  4. 然后触发AutoConfigurationImportEvent事件,告诉ConditionEvaluationReport条件评估报告器对象来分别记录符合条件和exclude的自动配置类。
  5. 最后spring再将最后筛选后的自动配置类导入IOC容器中

本篇继续来分析SpringBoot的自动配置的相关源码,我们来分析下@EnableConfigurationProperties和@EnableConfigurationProperties这两个注解,来探究下外部配置属性值是如何被绑定到@ConfigurationProperties注解的类属性中的?

举个栗子:以配置web项目的服务器端口为例,若我们要将服务器端口配置为8081,那么我们会在application.properties配置文件中配置server.port=8081,此时该配置值8081就将会绑定到被@ConfigurationProperties注解的类ServerProperties的属性port上,从而使得配置生效。

2 @EnableConfigurationProperties

我们接着前面的设置服务器端口的栗子来分析,我们先直接来看看ServerProperties的源码,应该能找到源码的入口:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
   
  /**
	 * Server HTTP port.
	 */
  private Integer port;
  // ...省略非关键代码
}

可以看到,ServerProperties类上标注了@ConfigurationProperties这个注解,服务器属性配置前缀为server,是否忽略未知的配置值(ignoreUnknownFields)设置为true。

那么我们再来看下@ConfigurationProperties这个注解的源码:

@Target({
    ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
   

  // 前缀别名
  @AliasFor("prefix")
  String value() default "";

  // 前缀
  @AliasFor("value")
  String prefix() default "";

  // 忽略无效的配置属性
  boolean ignoreInvalidFields() default false;

  // 忽略未知的配置属性
  boolean ignoreUnknownFields() default true;
}

@ConfigurationProperties这个注解的作用就是将外部配置的配置值绑定到其注解的类的属性上,可以作用于配置类或配置类的方法上。可以看到@ConfigurationProperties注解除了有设置前缀,是否忽略一些不存在或无效的配置等属性等外,这个注解没有其他任何的处理逻辑,可以看到@ConfigurationProperties是一个标志性的注解,源码入口不在这里。

这里讲的是服务器的自动配置,自然而然的,我们来看下自动配置类ServletWebServerFactoryAutoConfiguration的源码:

@Configuration
@EnableConfigurationProperties(ServerProperties.class)
// ...省略非关键注解
public class ServletWebServerFactoryAutoConfiguration {
   
  // ...省略非关键代码
}

为了突出重点,我已经把ServletWebServerFactoryAutoConfiguration的非关键代码和非关键注解省略掉了。可以看到,ServletWebServerFactoryAutoConfiguration自动配置类中有一个@EnableConfigurationProperties注解,且注解值是前面讲的ServerProperties.class,因此@EnableConfigurationProperties注解肯定就是我们关注的重点了。

同样,再来看下@EnableConfigurationProperties注解的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
   
  // 这个值指定的类就是@ConfigurationProperties注解标注的类,其将会被注册到spring容器中
  Class<?>[] value() default {
   };
}

@EnableConfigurationProperties注解的主要作用就是为@ConfigurationProperties注解标注的类提供支持,即对将外部配置属性值(比如application.properties配置值)绑定到@ConfigurationProperties标注的类的属性中。

注意:SpringBoot源码中还存在了ConfigurationPropertiesAutoConfiguration这个自动配置类,同时spring.factories配置文件中的EnableAutoConfiguration接口也配置了ConfigurationPropertiesAutoConfiguration,这个自动配置类上也有@EnableConfigurationProperties这个注解,堆属性绑定进行了默认开启。

那么,@EnableConfigurationProperties这个注解对属性绑定提供怎样的支持呢?

可以看到@EnableConfigurationProperties这个注解上还标注了@Import(EnableConfigurationPropertiesImportSelector.class),其导入了EnableConfigurationPropertiesImportSelector,因此可以肯定的是@EnableConfigurationProperties这个注解对属性绑定提供的支持必定跟EnableConfigurationPropertiesImportSelector有关。

到了这里,EnableConfigurationPropertiesImportSelector这个哥们是我们接下来要分析的对象,那么我们下面继续来分析EnableConfigurationPropertiesImportSelector是如何承担将外部配置属性值绑定到@ConfigurationProperties标注的类的属性中的。

3 EnableConfigurationPropertiesImportSelector

EnableConfigurationPropertiesImportSelector类的作用主要用来处理外部属性绑定的相关逻辑,其实现了ImportSelector接口,我们都知道,实现ImportSelector接口的selectImports方法可以向容器中注册bean。

那么,我们来看下EnableConfigurationPropertiesImportSelector覆写的selectImports方法:

// EnableConfigurationPropertiesImportSelector.java

class EnableConfigurationPropertiesImportSelector implements ImportSelector {
   
  // IMPORTS数组即是要向spring容器中注册的bean
  private static final String[] IMPORTS = {
   
    ConfigurationPropertiesBeanRegistrar.class.getName(),
    ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

  @Override
  public String[] selectImports(AnnotationMetadata metadata) {
   
    // 返回ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar的全限定名
    // 即上面两个类将会被注册到Spring容器中
    return IMPORTS;
  }

}

可以看到EnableConfigurationPropertiesImportSelector类中的selectImports方法中返回的是IMPORTS数组,而这个IMPORTS是一个常量数组,值是ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar。即EnableConfigurationPropertiesImportSelector的作用是向Spring容器中注册了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个bean。

我们在EnableConfigurationPropertiesImportSelector类中没看到处理外部属性绑定的相关逻辑,其只是注册了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个bean,接下来我们再看下注册的这两个bean类。

4 ConfigurationPropertiesBeanRegistrar

我们先来看下ConfigurationPropertiesBeanRegistrar这个类。

ConfigurationPropertiesBeanRegistrar是EnableConfigurationPropertiesImportSelector的内部类,其实现了ImportBeanDefinitionRegistrar接口,覆写了registerBeanDefinitions方法。可见,ConfigurationPropertiesBeanRegistrar又是用来注册一些bean definition的,即也是向Spring容器中注册一些bean。

先看下ConfigurationPropertiesBeanRegistrar的源码:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java

public static class ConfigurationPropertiesBeanRegistrar
  implements ImportBeanDefinitionRegistrar {
   
  @Override
  public void registerBeanDefinitions(AnnotationMetadata metadata,  // metadata是AnnotationMetadataReadingVisitor对象,存储了某个配置类的元数据
                                      BeanDefinitionRegistry registry) {
   
    // (1)得到@EnableConfigurationProperties注解的所有属性值,
    // 比如@EnableConfigurationProperties(ServerProperties.class),那么得到的值是ServerProperties.class
    // (2)然后再将得到的@EnableConfigurationProperties注解的所有属性值注册到容器中
    getTypes(metadata).forEach((type) -> register(
      registry,
      (ConfigurableListableBeanFactory) registry, type));
  }
}

在ConfigurationPropertiesBeanRegistrar实现的registerBeanDefinitions中,可以看到主要做了两件事:

  1. 调用getTypes方法获取@EnableConfigurationProperties注解的属性值XxxProperties;
  2. 调用register方法将获取的属性值XxxProperties注册到Spring容器中,用于以后和外部属性绑定时使用。

我们来看下getTypes方法的源码:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java

private List<Class<?>> getTypes(AnnotationMetadata metadata) {
   
  // 得到@EnableConfigurationProperties注解的所有属性值,
  // 比如@EnableConfigurationProperties(ServerProperties.class),那么得到的值是ServerProperties.class
  MultiValueMap<String, Object> attributes = metadata
    .getAllAnnotationAttributes(
    EnableConfigurationProperties.class.getName(), false);
  // 将属性值取出装进List集合并返回
  return collectClasses((attributes != null) ? attributes.get("value")
                        : Collections.emptyList());
}

getTypes方法里面的逻辑很简单即将@EnableConfigurationProperties注解里面的属性值XxxProperties(比如ServerProperties.class)取出并装进List集合并返回。

由getTypes方法拿到@EnableConfigurationProperties注解里面的属性值XxxProperties(比如ServerProperties.class)后,此时再遍历将XxxProperties逐个注册进Spring容器中,我们来看下register方法:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java
private void register(BeanDefinitionRegistry registry,
                      ConfigurableListableBeanFactory beanFactory, Class<?> type) {
   
  // 得到type的名字,一般用类的全限定名作为bean name
  String name = getName(type);
  // 根据bean name判断beanFactory容器中是否包含该bean
  if (!containsBeanDefinition(beanFactory, name)) {
   
    // 若不包含,那么注册bean definition
    registerBeanDefinition(registry, name, type);
  }
}

我们再来看下由EnableConfigurationPropertiesImportSelector导入的另一个类ConfigurationPropertiesBindingPostProcessorRegistrar又是干嘛的呢?

5 ConfigurationPropertiesBindingPostProcessorRegistrar

可以看到ConfigurationPropertiesBindingPostProcessorRegistrar类名字又是以Registrar单词为结尾,说明其肯定又是导入一些bean definition的。直接看源码:

// ConfigurationPropertiesBindingPostProcessorRegistrar.java

public class ConfigurationPropertiesBindingPostProcessorRegistrar
  implements ImportBeanDefinitionRegistrar 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值