SpringBoot之@EnableConfigurationProperties分析

我们在用SpringBoot进行项目开发的时候,基本上都使用过@ConfigurationProperties这个注解,我们在之前的文章中也说过ConfigurationPropertiesBindingPostProcessor会对标注@ConfigurationProperties注解的Bean进行属性值的配置,但是我们之前没有说ConfigurationPropertiesBindingPostProcessor这个Bean是什么时候注入到Spring容器中的。在Spring容器中如果没有这个Bean存在的话,那么通过@ConfigurationProperties注解对Bean属性值的配置行为将无从谈起。我们在本篇文章中将会说明SpringBoot在什么时机会将ConfigurationPropertiesBindingPostProcessor注入到SpringBoot容器中,但它不是我们这篇文章的主角,我们的主角是EnableConfigurationProperties注解。下面我们来请EnableConfigurationProperties登场。
关于EnableConfigurationProperties,在SpringBoot的注释中是这样说明的:为带有@ConfigurationProperties注解的Bean提供有效的支持。这个注解可以提供一种方便的方式来将带有@ConfigurationProperties注解的类注入为Spring容器的Bean。你之前可能听说过类似这样的说法@ConfigurationProperties注解可以生效是因为在SpringBoot中有一个类叫ConfigurationPropertiesAutoConfiguration,它为@ConfigurationProperties注解的解析提供了支持的工作,这种说法更准确一点的说法是在这个类上还存在了@Configuration和@EnableConfigurationProperties这两个注解!我们去ConfigurationPropertiesAutoConfiguration这个类中看一下这个类中的内容:

@Configuration
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration {

}

我们会发现这个类没有任何内容,它其实只是一个标识性的类,用来标识ConfigurationProperties自动配置,重要的就是@Configuration和@EnableConfigurationProperties。这个类在SpringBoot中唯一被引用到的位置是在spring.factories中,
ConfigurationPropertiesAutoConfiguration
关于@EnableAutoConfiguration的内容我们先不展开,你现在先记着由于在@EnableAutoConfiguration中存在

@Import(EnableAutoConfigurationImportSelector.class)

Import这个注解,这个注解中又引入了EnableAutoConfigurationImportSelector这个类,而这个类会在某一个时机某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被创建并调用它的selectImports方法,在它的selectImports方法中会获取到ConfigurationPropertiesAutoConfiguration这个类,
EnableAutoConfigurationImportSelector
而在ConfigurationPropertiesAutoConfiguration这个类上存在@EnableConfigurationProperties这个注解,在EnableConfigurationProperties这个注解中又存在@Import这个注解,在这个注解中又引入了EnableConfigurationPropertiesImportSelector.class这个类,所以这个类同样会在某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被实例化,然后调用它的selectImports方法。关于@Import这个注解中所引入的类可以分为三种类型,第一种是实现了ImportSelector接口的类,ImportSelector接口的实现类又可以分为两种,一种是直接实现ImportSelector接口的类,另一种是实现了DeferredImportSelector接口的类,DeferredImportSelector是ImportSelector接口的子类,也只有直接实现了ImportSelector接口的类,才会在ConfigurationClassParser#processImports中被调用它的selectImports方法;第二种是实现了ImportBeanDefinitionRegistrar接口的类,实现了ImportBeanDefinitionRegistrar接口的类将会调用它的registerBeanDefinitions方法,向Spring容器中注册Bean定义;最后一种是只要不属于上面那两种的都是第三种。

@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 {};
}

我们在上面说了会调用EnableConfigurationPropertiesImportSelector这个类的selectImports方法,我们去看看这个方法的内容:

class EnableConfigurationPropertiesImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
                EnableConfigurationProperties.class.getName(), false);
        Object[] type = attributes == null ? null
                : (Object[]) attributes.getFirst("value");
        if (type == null || type.length == 0) {
            return new String[] {
                    ConfigurationPropertiesBindingPostProcessorRegistrar.class
                            .getName() };
        }
        return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(),
                ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
    }
.........

在selectImports中会先通过

//false的意思是 不将 获取到的注解中的值转换为String
metadata.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);

来获取类上的EnableConfigurationProperties注解中的value值,注意这里返回的值MultiValueMap

//如果前面没有获取到EnableConfigurationProperties注解的话 则type为null,获取到EnableConfigurationProperties注解的则取value这个元数据的值
Object[] type = attributes == null ? null: (Object[]) attributes.getFirst("value");
//如果上一步获取到的type为null或者是一个空数组的话则返回ConfigurationPropertiesBindingPostProcessorRegistrar的全限定类名
if (type == null || type.length == 0) {
    return new String[] {
        ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() 
    };
}
//如果在上一步中获取到了EnableConfigurationProperties注解中的value元数据的值,则返回ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个类的全限定类名
return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(),
                ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

如果我们的类上只是标注了EnableConfigurationProperties注解,如ConfigurationPropertiesAutoConfiguration这个类的用法。那么就会返回ConfigurationPropertiesBindingPostProcessorRegistrar的全限定类名。按照我们之前说的,这个类将会在某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被实例化,

public class ConfigurationPropertiesBindingPostProcessorRegistrar
        implements ImportBeanDefinitionRegistrar 

请注意这个类是一个实现了ImportBeanDefinitionRegistrar接口的类,所以将会在某一个地方(org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions)调用它的registerBeanDefinitions方法。我们先去看下这个类中registerBeanDefinitions方法的内容:

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
        //  BINDER_BEAN_NAME 的值是:' ConfigurationPropertiesBindingPostProcessor.class.getName()
        //如果在Spring Bean注册器中=不存在beanBame为org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor的Bean定义
        if (!registry.containsBeanDefinition(BINDER_BEAN_NAME)) {
            BeanDefinitionBuilder meta = BeanDefinitionBuilder
                    .genericBeanDefinition(ConfigurationBeanFactoryMetaData.class);
            //ConfigurationPropertiesBindingPostProcessor bean定义的构造器
            BeanDefinitionBuilder bean = BeanDefinitionBuilder.genericBeanDefinition(
                    ConfigurationPropertiesBindingPostProcessor.class);
            bean.addPropertyReference("beanMetaDataStore", METADATA_BEAN_NAME);
            //将beanName为org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor的bean定义放入到Springbean注册器
            //如果之前看过Spring的源码的话,看到这里你就会会心一笑了。
            //在这里就将我们开篇说的ConfigurationPropertiesBindingPostProcessor注入到Spring容器中了
            registry.registerBeanDefinition(BINDER_BEAN_NAME, bean.getBeanDefinition());
            registry.registerBeanDefinition(METADATA_BEAN_NAME, meta.getBeanDefinition());
        }
    }

我们再看如果我们的类上标注了@EnableConfigurationProperties注解,并在注解中value赋值的话,它会返回ConfigurationPropertiesBeanRegistrar 和ConfigurationPropertiesBindingPostProcessorRegistrar这两个类的全限定名,关于ConfigurationPropertiesBindingPostProcessorRegistrar我们已经说过了,下面说ConfigurationPropertiesBeanRegistrar 这个类。首先EnableConfigurationPropertiesImportSelector这个类是EnableConfigurationPropertiesImportSelector中的一个静态内部类,同样的它也实现了ImportBeanDefinitionRegistrar这个接口(ImportBeanDefinitionRegistrar这个接口注意是对注解的Bean定义进行处理)。

public static class ConfigurationPropertiesBeanRegistrar
            implements ImportBeanDefinitionRegistrar
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            //这里再次从metadata中获取EnableConfigurationProperties注解相关的东西
            //这里的metadata即是我们带有EnableConfigurationProperties类的Bean的元数据
            MultiValueMap<String, Object> attributes = metadata
                    .getAllAnnotationAttributes(
                            EnableConfigurationProperties.class.getName(), false);
            //这个是用的attributes.get("value")获取value的值 而前面是用的attributes.getFirst("value")
            //将获取到的value转换为List
            List<Class<?>> types = collectClasses(attributes.get("value"));
            for (Class<?> type : types) {
                //这里是获取EnableConfigurationProperties注解中value中的类的ConfigurationProperties的prefix
                //说的太绕了。。。。
                String prefix = extractPrefix(type);
                //如果ConfigurationProperties中的prefix有值的话,则将beanName拼接为prefix+"-"+类的全限定名
                String name = (StringUtils.hasText(prefix) ? prefix + "-" + type.getName()
                        : type.getName());
                //如果Spring bean注册器中不存在这个beanName的bean定义的话,则注入到Spring bean注册器中
                if (!registry.containsBeanDefinition(name)) {
                    registerBeanDefinition(registry, type, name);
                }
            }
        }
        private void registerBeanDefinition(BeanDefinitionRegistry registry,
                Class<?> type, String name) {
            //bean定义的构造器
            BeanDefinitionBuilder builder = BeanDefinitionBuilder
                    .genericBeanDefinition(type);
            //获取bean定义
            AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
            //注入到Spring bean注册器中
            registry.registerBeanDefinition(name, beanDefinition);
            //获取EnableConfigurationProperties value引入的类上的ConfigurationProperties注解
            ConfigurationProperties properties = AnnotationUtils.findAnnotation(type,
                    ConfigurationProperties.class);
            //如果EnableConfigurationProperties value引入的类上没有ConfigurationProperties注解的话则抛出异常
            //这个检测为什么不放到方法的开头处呢????
            Assert.notNull(properties,
                    "No " + ConfigurationProperties.class.getSimpleName()
                            + " annotation found on  '" + type.getName() + "'.");
        }

通过这篇文章你应该了解了ConfigurationPropertiesAutoConfiguration这个类什么会使ConfigurationProperties注解生效了(因为这个类放到了spring.factories中,作为org.springframework.boot.autoconfigure.EnableAutoConfiguration的一个value值,在SpringBoot启动的时候会先被加载,确保能获取到ConfigurationPropertiesAutoConfiguration这个类,并解析它上面的注解!),也应该明白了@EnableConfigurationProperties这个注解的作用,同时也应该了解了ConfigurationPropertiesBindingPostProcessorRegistrar这个类是怎么被注入到Spring容器中的。下面以一个小例子结束这篇文章:

import com.zkn.springboot.analysis.domain.EnableConfigurationPropertiesDomain;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 *
 * @author zkn
 * @date 2018/1/28
 */
@Component
@EnableConfigurationProperties(value = EnableConfigurationPropertiesDomain.class)
public class EnableConfigurationPropertiesBean {

}
/**
 * @author zkn
 * @date 2018/1/28
 */
//这里只有ConfigurationProperties注解
@ConfigurationProperties(prefix = "configuration.properties")
public class EnableConfigurationPropertiesDomain implements Serializable {

    private static final long serialVersionUID = -3485524455817230192L;

    private String name;
    //省略getter setter
    @Override
    public String toString() {
        return "EnableConfigurationPropertiesDomain{" +
                "name='" + name + '\'' +
                '}';
    }
}
configuration:
  properties:
    name: This is EnableConfigurationProperties!
    @Autowired
    private EnableConfigurationPropertiesDomain propertiesDomain;

    @RequestMapping("index")
    public String index() {
        System.out.println(propertiesDomain);
        return "success";
    }

我们通过浏览器访问一下,输出结果如下:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/zknxx/article/details/79183698