`
shinesuo
  • 浏览: 156313 次
  • 性别: Icon_minigender_1
  • 来自: 宇宙
社区版块
存档分类
最新评论

Spring Boot 源码分析 —— @ConfigurationProperties

 
阅读更多

1. 概述

本文我们来分享 @ConfigurationProperties 注解,如何将配置文件自动设置到被注解的类。代码如下:

 

// ConfigurationProperties.java

/**
* Annotation for externalized configuration. Add this to a class definition or a
* {@code @Bean} method in a {@code @Configuration} class if you want to bind and validate
* some external Properties (e.g. from a .properties file).
* <p>
* Note that contrary to {@code @Value}, SpEL expressions are not evaluated since property
* values are externalized.
*
* @author Dave Syer
* @see ConfigurationPropertiesBindingPostProcessor
* @see EnableConfigurationProperties
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {

/**
* The name prefix of the properties that are valid to bind to this object. Synonym
* for {@link #prefix()}. A valid prefix is defined by one or more words separated
* with dots (e.g. {@code "acme.system.feature"}).
*
* @return the name prefix of the properties to bind
*/
@AliasFor("prefix")
String value() default "";

/**
* The name prefix of the properties that are valid to bind to this object. Synonym
* for {@link #value()}. A valid prefix is defined by one or more words separated with
* dots (e.g. {@code "acme.system.feature"}).
*
* @return the name prefix of the properties to bind
*/
@AliasFor("value")
String prefix() default "";

/**
* Flag to indicate that when binding to this object invalid fields should be ignored.
* Invalid means invalid according to the binder that is used, and usually this means
* fields of the wrong type (or that cannot be coerced into the correct type).
*
* @return the flag value (default false)
*/
boolean ignoreInvalidFields() default false;

/**
* Flag to indicate that when binding to this object unknown fields should be ignored.
* An unknown field could be a sign of a mistake in the Properties.
*
* @return the flag value (default true)
*/
boolean ignoreUnknownFields() default true;

}

 

@ConfigurationProperties 注解有两种使用方法,可见 《关与 @EnableConfigurationProperties 注解》 文章。总结来说:

  • 第一种,@Component + @ConfigurationProperties 
  • 第二种,@EnableConfigurationProperties + ConfigurationProperties 

实际情况下,更多的是使用第一种。当然,第二种的 @EnableConfigurationProperties 的效果,也是将指定的类,实现和 @Component 被注解的类是一样的,创建成 Bean 对象。
这样,@ConfigurationProperties 就可以将配置文件自动设置到该 Bean 对象咧。

2. @EnableConfigurationProperties

org.springframework.boot.context.properties.@EnableConfigurationProperties 注解,可以将指定带有 @ConfigurationProperties 的类,注册成 BeanDefinition ,从而创建成 Bean 对象。代码如下:

 

// EnableConfigurationProperties.java

/**
* Enable support for {@link ConfigurationProperties} annotated beans.
* {@link ConfigurationProperties} beans can be registered in the standard way (for
* example using {@link Bean @Bean} methods) or, for convenience, can be specified
* directly on this annotation.
*
* @author Dave Syer
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {

/**
* 指定的类们
*
* Convenient way to quickly register {@link ConfigurationProperties} annotated beans
* with Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@link ConfigurationProperties} annotated beans to register
*/
Class<?>[] value() default {};

}

 

2.1 ConfigurationPropertiesAutoConfiguration

默认情况下,@EnableConfigurationProperties 会通过 org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration 类,进行开启。代码如下:

 

// ConfigurationPropertiesAutoConfiguration.java

/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link ConfigurationProperties}
* beans. Automatically binds and validates any bean annotated with
* {@code @ConfigurationProperties}.
*
* @author Stephane Nicoll
* @since 1.3.0
* @see EnableConfigurationProperties
* @see ConfigurationProperties
*/
@Configuration
@EnableConfigurationProperties // <X>
public class ConfigurationPropertiesAutoConfiguration {
}

 

  • 看,看看,看看看,<X> 哟~

2.2 EnableConfigurationPropertiesImportSelector

org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector ,实现 ImportSelector 接口,处理 @EnableConfigurationProperties 注解。代码如下:

 

// EnableConfigurationPropertiesImportSelector.java

private static final String[] IMPORTS = {
ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

@Override
public String[] selectImports(AnnotationMetadata metadata) {
return IMPORTS;
}

 

2.3 ConfigurationPropertiesBeanRegistrar

ConfigurationPropertiesBeanRegistrar ,是 EnableConfigurationPropertiesImportSelector 的内部静态类,实现 ImportBeanDefinitionRegistrar 接口,将 @EnableConfigurationProperties 注解指定的类,逐个注册成对应的 BeanDefinition 对象。代码如下:

 

// EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.java

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
getTypes(metadata) // <1>
.forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type)); // <2>
}

 

  • <1> 处,调用 #getTypes(AnnotationMetadata metadata) 方法,获得 @EnableConfigurationProperties 注解指定的类的数组。代码如下:

    // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.java

    private List<Class<?>> getTypes(AnnotationMetadata metadata) {
    // 获得 @EnableConfigurationProperties 注解
    MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
    // 获得 value 属性
    return collectClasses((attributes != null) ? attributes.get("value")
    : Collections.emptyList());
    }

    private List<Class<?>> collectClasses(List<?> values) {
    return values.stream().flatMap((value) -> Arrays.stream((Object[]) value))
    .map((o) -> (Class<?>) o).filter((type) -> void.class != type)
    .collect(Collectors.toList());
    }

     

    • ~
  • <2> 处,遍历,逐个调用 #register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) 方法,注册每个类对应的 BeanDefinition 对象。代码如下:

     

    // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.java

    private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) {
    // <2.1> 通过 @ConfigurationProperties 注解,获得最后要生成的 BeanDefinition 的名字。格式为 prefix-类全名 or 类全名
    String name = getName(type);
    // <2.2> 判断是否已经有该名字的 BeanDefinition 的名字。没有,才进行注册
    if (!containsBeanDefinition(beanFactory, name)) {
    registerBeanDefinition(registry, name, type); // <2.3>
    }
    }

     

    • <2.1> 处,调用 #getName(Class<?> type) 方法,通过 @ConfigurationProperties 注解,获得最后要生成的 BeanDefinition 的名字。代码如下:

      // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.java

      private String getName(Class<?> type) {
      ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class);
      String prefix = (annotation != null) ? annotation.prefix() : "";
      return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
      }

       

      • 格式为 prefix-类全名 or 类全名。
    • <2.2> 处,调用 #containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) 方法,判断是否已经有该名字的 BeanDefinition 的名字。代码如下:

      // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.java

      private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) {
      // 判断是否存在 BeanDefinition 。如果有,则返回 true
      if (beanFactory.containsBeanDefinition(name)) {
      return true;
      }
      // 获得父容器,判断是否存在
      BeanFactory parent = beanFactory.getParentBeanFactory();
      if (parent instanceof ConfigurableListableBeanFactory) {
      return containsBeanDefinition((ConfigurableListableBeanFactory) parent, name);
      }
      // 返回 false ,说明不存在
      return false;
      }

       

      • 如果不存在,才执行后续的注册 BeanDefinition 逻辑。
    • <2.3> 处,调用 #registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) 方法,注册 BeanDefinition 。代码如下:

      // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.java

      private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) {
      // 断言,判断该类有 @ConfigurationProperties 注解
      assertHasAnnotation(type);
      // 创建 GenericBeanDefinition 对象
      GenericBeanDefinition definition = new GenericBeanDefinition();
      definition.setBeanClass(type);
      // 注册到 BeanDefinitionRegistry 中
      registry.registerBeanDefinition(name, definition);
      }

      private void assertHasAnnotation(Class<?> type) {
      Assert.notNull(
      AnnotationUtils.findAnnotation(type, ConfigurationProperties.class),
      () -> "No " + ConfigurationProperties.class.getSimpleName()
      + " annotation found on '" + type.getName() + "'.");
      }

       

      • ~

2.4 ConfigurationPropertiesBindingPostProcessorRegistrar

org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar ,实现 ImportBeanDefinitionRegistrar 接口,代码如下:

 

// ConfigurationPropertiesBindingPostProcessorRegistrar.java

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
// <1> 注册 ConfigurationPropertiesBindingPostProcessor BeanDefinition
registerConfigurationPropertiesBindingPostProcessor(registry);
// <2> 注册 ConfigurationBeanFactoryMetadata BeanDefinition
registerConfigurationBeanFactoryMetadata(registry);
}
}

 

  • <1> 处,调用 #registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) 方法,注册 ConfigurationPropertiesBindingPostProcessor BeanDefinition 。代码如下:

    // ConfigurationPropertiesBindingPostProcessorRegistrar.java

    private void registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) {
    // 创建 GenericBeanDefinition 对象
    GenericBeanDefinition definition = new GenericBeanDefinition();
    definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
    definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    // 注册
    registry.registerBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);
    }

     

  • <2> 处,调用 #registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) 方法,注册 ConfigurationBeanFactoryMetadata BeanDefinition 。代码如下:

    // ConfigurationPropertiesBindingPostProcessorRegistrar.java

    private void registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) {
    // 创建 GenericBeanDefinition 对象
    GenericBeanDefinition definition = new GenericBeanDefinition();
    definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
    definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    // 注册
    registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition);
    }

     

3. ConfigurationBeanFactoryMetadata

org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata ,初始化配置类创建 Bean 的每个方法的元数据。

3.1 postProcessBeanFactory

实现 #postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 方法,代码如下:

 

// ConfigurationBeanFactoryMetadata.java

private ConfigurableListableBeanFactory beanFactory;

/**
* FactoryMetadata 的映射
*
* KEY :Bean 的名字
*/
private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// <1> 初始化 beanFactory 属性
this.beanFactory = beanFactory;
// <2> 遍历所有的 BeanDefinition 的名字们
for (String name : beanFactory.getBeanDefinitionNames()) {
// <2.1> 获得 BeanDefinition 对象
BeanDefinition definition = beanFactory.getBeanDefinition(name);
// <2.2> 获得 method、bean 属性
String method = definition.getFactoryMethodName();
String bean = definition.getFactoryBeanName();
// <2.3> 添加到 beansFactoryMetadata 中
if (method != null && bean != null) {
this.beansFactoryMetadata.put(name, new FactoryMetadata(bean, method));
}
}
}

 

  • <1> 处,初始化 beanFactory 属性。

  • <2> 处,遍历所有的 BeanDefinition 的名字们,初始化 beansFactoryMetadata 属性。

  • <2.1> 处,获得 BeanDefinition 对象。

  • <2.2> 处,获得 BeanDefinition 的 factoryMethodNamefactoryBeanName 属性。

    • factoryBeanName 属性,是创建该 Bean 的工厂 Bean 的名字。

    • factoryMethodName 属性,是创建 Bean 的工厂 Bean 的方法名。

    • 以如下的 Configuration 类,举个例子:

      @Configuration
      public class TestConfiguration {

      @Bean
      public Object testObject() {
      return new Object();
      }

      }

       

      • 每个 @Bean 注解的方法,都是一个 factoryBeanName + factoryMethodName 
      • factoryBeanName 属性,为 "testConfiguration" 
      • factoryMethodName 属性,为 "testObject" 
  • <2.3> 处,都非空的情况下,添加到 beansFactoryMetadata 中。

  • FactoryMetadata 是 ConfigurationBeanFactoryMetadata 的内部静态类。代码如下:

    // ConfigurationBeanFactoryMetadata#FactoryMetadata.java

    private static class FactoryMetadata {

    /**
    * Bean 的名字
    */
    private final String bean;
    /**
    * Bean 的方法名
    */
    private final String method;

    // ... 省略 setting / getting 方法

    }

     

3.2 findFactoryMethod

#findFactoryMethod(String beanName) 方法,获得指定 Bean 的创建方法。代码如下:

 

// ConfigurationBeanFactoryMetadata.java

public Method findFactoryMethod(String beanName) {
// 如果不存在,则返回 null
if (!this.beansFactoryMetadata.containsKey(beanName)) {
return null;
}
AtomicReference<Method> found = new AtomicReference<>(null);
// 获得 beanName 对应的 FactoryMetadata 对象
FactoryMetadata metadata = this.beansFactoryMetadata.get(beanName);
// 获得对应的工厂类
Class<?> factoryType = this.beanFactory.getType(metadata.getBean());
if (ClassUtils.isCglibProxyClass(factoryType)) {
factoryType = factoryType.getSuperclass();
}
// 获得对应的工厂类的方法
String factoryMethod = metadata.getMethod();
ReflectionUtils.doWithMethods(factoryType, (method) -> {
if (method.getName().equals(factoryMethod)) {
found.compareAndSet(null, method);
}
});
return found.get();
}

 

3.3 findFactoryAnnotation

#findFactoryAnnotation(String beanName, Class<A> type) 方法,获得指定 Bean 的创建方法上的注解。代码如下:

 

// ConfigurationBeanFactoryMetadata.java

public <A extends Annotation> A findFactoryAnnotation(String beanName, Class<A> type) {
// 获得方法
Method method = findFactoryMethod(beanName);
// 获得注解
return (method != null) ? AnnotationUtils.findAnnotation(method, type) : null;
}

 

3.4 getBeansWithFactoryAnnotation

#getBeansWithFactoryAnnotation(Class<A> type) 方法,获得 beansFactoryMetadata 中的每个 Bean 的方法上的指定注解。代码如下:

 

// ConfigurationBeanFactoryMetadata.java

public <A extends Annotation> Map<String, Object> getBeansWithFactoryAnnotation(Class<A> type) {
Map<String, Object> result = new HashMap<>();
// 遍历 beansFactoryMetadata
for (String name : this.beansFactoryMetadata.keySet()) {
// 获得每个 Bean 的创建方法上的注解
if (findFactoryAnnotation(name, type) != null) {
result.put(name, this.beanFactory.getBean(name));
}
}
return result;
}

 

分享到:
评论

相关推荐

    Spring Boot技术知识点:如何理解@ConfigurationProperties注解

    在Spring Boot框架中,`@ConfigurationProperties`注解是一个核心特性,它允许我们将配置属性从外部化配置文件(如application.properties或application.yml)绑定到Java对象的字段上,从而简化了属性管理。...

    SpringBoot @ConfigurationProperties使用详解(源代码)

    在Spring Boot中注解@ConfigurationProperties有三种使用场景,而通常情况下我们使用的最多的只是其中的一种场景。本篇文章带大家了解一下三种场景的使用情况。 1.2 场景一 使用@ConfigurationProperties和@...

    @ConfigurationProperties注解使用方法(源代码)

    jpa的依赖 (spring-boot-starter-data-jpa) 3.1.2.4 JavaBean对象 3.1.2.5 application.properties配置文件 3.1.2.6 配置类 3.1.2.7 测试类 3.1.3问题与解答 问题: 解答与分析: 案例实操 3.2 作用于Class类及其...

    Spring Boot2.0 @ConfigurationProperties使用详解

    "Spring Boot 2.0 @ConfigurationProperties 使用详解" 概述 {@ConfigurationProperties} 是 Spring Boot 2.0 中的一个核心功能,用于简化外部化配置方式的使用。通过使用 {@ConfigurationProperties},可以轻松地...

    spring boot 使用 ConfigurationProperties.docx

    在Spring Boot中,`@ConfigurationProperties` 是一个强大的特性,用于将配置文件(如application.properties或application.yml)中的键值对映射到Java对象的属性上。这使得开发者能够更方便地管理和使用配置信息,...

    Spring Boot系列四 Spring @Value 属性注入使用总结一

    Spring Boot系列四 Spring @Value 属性注入使用总结一

    @ConfigurationProperties绑定配置信息至Array、List、Map、Bean的实现

    @ConfigurationProperties是Spring Boot提供的一种强大的特性,用于将配置文件(如application.properties或application.yml)中的键值对映射到Java对象的属性上。这使得我们可以更方便地管理和使用配置信息,尤其是...

    spring boot整合JPA——demo

    本示例“spring boot整合JPA——demo”将演示如何在Spring Boot项目中配置和使用JPA。 首先,我们需要理解Spring Boot与JPA的关系。Spring Boot是基于Spring框架的快速开发工具,它通过自动化配置减少了常规设置...

    软件框架技术-使用@Component@ConfigurationProperties等方法实现将配置文件的注入,并在控制台显示

    接下来,`@ConfigurationProperties`是Spring Boot提供的一个高级特性,它允许我们将配置文件(如application.properties或application.yml)中的键值对映射到一个Java类的字段上。这大大简化了处理配置的工作,使...

    SpringBoot @ConfigurationProperties使用详解

    在Spring Boot中,`@ConfigurationProperties`是用于将配置文件中的属性绑定到Java Bean的注解,极大地简化了属性的管理,使得配置更加灵活和类型安全。以下是对`@ConfigurationProperties`的详细解析。 **1. 添加...

    Spring Boot课件1 —— 创建和运行Spring Boot项目

    **Spring Boot创建与运行项目详解** Spring Boot是Java开发领域中的一个热门框架,它通过简化配置和自动装配,使得创建和运行Spring应用变得更加容易。在本篇内容中,我们将深入探讨如何利用Spring Boot来创建和...

    spring boot源码分析

    深入学习spring boot 懂得各个标签,注解的用途和原理

    Testing_Configuration_Properties:一个简单的设置来测试Spring Boot的@ConfigurationProperties

    在Spring Boot中测试@ConfigurationProperties 该项目显示了如何在Spring Boot中测试@ConfigurationProperties。 您可以在以下位置找到有关此内容的博客文章:

    第三节-springboot源码解析-王炸篇.pdf

    通过源码分析,开发者可以更好地理解Spring Boot的自动装配、启动流程以及如何自定义启动器。Spring Boot的自动装配原理涉及到Spring Boot的核心特性,它简化了基于Spring的应用开发,通过自动配置减少了大量的配置...

    spring-boot-2.7.0.zip源码

    通过深入阅读和分析Spring Boot 2.7.0的源码,我们可以了解到Spring Boot是如何实现其核心特性的,以及如何利用Spring Framework进行扩展和定制。同时,这也有助于我们更好地利用Spring Boot进行微服务开发,提高...

    果子学院Spring boot源码解析

    《果子学院Spring Boot源码解析》是一套深入学习Spring Boot源码的教程,旨在帮助开发者深入了解这个流行的Java开发框架的内部工作机制。Spring Boot简化了Java应用的初始搭建以及开发过程,它集成了大量常用的第三...

    三、Spring源码分析——ApplicationContext

    《Spring源码分析——ApplicationContext》 在Java世界中,Spring框架是不可或缺的一部分,它以其强大的IoC(Inversion of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)特性,极大地...

    Spring Boot源码(spring-boot-2.6.2.zip)

    在这个版本中,我们将深入探讨Spring Boot的核心特性、工作原理以及如何通过源码来理解其内部机制。 首先,Spring Boot的核心理念是“约定优于配置”,它通过预设许多默认配置,减少了开发者需要手动配置的繁琐工作...

    spring boot资料以及项目

    Spring Boot的配置文件(application.properties或application.yml)的使用方法,以及如何通过@ConfigurationProperties将配置绑定到Java对象,也是学习的重点。 Spring Boot对于数据库的支持非常全面,包括JDBC、...

    Spring Boot实战派(源码)

    《Spring Boot实战派》源码提供了丰富的学习材料,旨在帮助开发者深入理解并...通过分析《Spring Boot实战派》源码,读者不仅可以了解上述技术点,还能学习到如何在实际项目中应用这些技术,提升开发效率和代码质量。

Global site tag (gtag.js) - Google Analytics