码迷,mamicode.com
首页 > 编程语言 > 详细

springboot情操陶冶-@ConfigurationProperties注解解析

时间:2018-08-13 18:01:27      阅读:429      评论:0      收藏:0      [点我收藏+]

标签:tar   htm   很多   demo1   amp   tap   外部   enable   gpo   

承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述@ConfigurationProperties注解的使用

@ConfigurationProperties

此注解多用于对配置文件的加载以及映射对应值至相应的java属性中,样例如下


1.配置属性指定application.properties

# user custom
user.custom.username=demo_jing
user.custom.nickname=nanco
user.custom.email=questionsky1211@gmail.com
user.custom.password=demo1234
user.custom.job=programmer

2.属性映射类(使用@ConfigurationProperties注解)UserProperty.java

package com.example.demo.bootbase;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author nanco
 * @create 2018/8/13
 **/
@ConfigurationProperties(prefix = "user.custom")
public class UserProperty {

    private String username;

    private String nickname;

    private String email;

    private String password;

    private String job;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    @Override
    public String toString() {
        return "UserProperty{" +
                "username='" + username + '\'' +
                ", nickname='" + nickname + '\'' +
                ", email='" + email + '\'' +
                ", password='" + password + '\'' +
                ", job='" + job + '\'' +
                '}';
    }
}

3.属性映射开启(使用@EnableConfigurationProperties注解)UserPropertiesAutoConfiguration.java

package com.example.demo.bootbase;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author nanco
 * @create 2018/8/13
 **/
@Configuration
@EnableConfigurationProperties(value = UserProperty.class)
public class UserPropertiesAutoConfiguration {
}

4.上述注解生效入口META-INF\spring.fatories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.bootbase.UserPropertiesAutoConfiguration

5.结果检验

package com.example.demo;

import com.example.demo.bootbase.UserProperty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class DemoSpringbootApplication {

    public static void main(String[] args) {
        ApplicationContext demoApplicationContext = SpringApplication.run(DemoSpringbootApplication.class, args);

        UserProperty userProperty = demoApplicationContext.getBean(UserProperty.class) ;

        System.out.println(userProperty);
    }
}

输出结果如下

...
...
2018-08-13 14:37:05.537  INFO 17384 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 2147483647
2018-08-13 14:37:05.546  INFO 17384 --- [           main] c.e.demo.DemoSpringbootApplication       : Started DemoSpringbootApplication in 2.611 seconds (JVM running for 3.756)
UserProperty{username='demo_jing', nickname='nanco', email='questionsky1211@gmail.com', password='demo1234', job='programmer'}

出现的结果与我们预知的一样,属性都进行了相应的赋予。下面笔者开展下源码层的解析以解开其中的绑定小谜团

@EnableConfigurationProperties

要想上述的结果运行成功,必须在相应的启动类使用@EnableConfigurationProperties注解,我们先看下其源码

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

}

其中的value属性便是扫描的属性class,并且会将指定的class属性注册至bean工厂。通过前文分析可得,最终的解析是通过@Import引入的EnableConfigurationPropertiesImportSelector.class来实现的

EnableConfigurationPropertiesImportSelector

直接查看其复写的selectImports()方法

    private static final String[] IMPORTS = {
            // 属性类注册
            ConfigurationPropertiesBeanRegistrar.class.getName(),
            // 属性类绑定处理
            ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

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

将导入两个类去处理属性的关系,我们按照顺序来分析


ConfigurationPropertiesBeanRegistrar.class
直接去看其复写的方法

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            // 读取被注解类上的EnableConfigurationProperties上的value属性并进行注入至bean工厂
            getTypes(metadata).forEach((type) -> register(registry,
                    (ConfigurableListableBeanFactory) registry, type));
        }

具体的代码就不贴出来了,笔者此处作下小的总结

  1. 优先读取被注解类上@EnableConfigurationPropertiesvalue属性

  2. 在将上述的value属性指定的class集合注入至bean工厂前,优先判断是否被@ConfigurationProperties注解修饰过,没有则会报错

  3. 将指定的class集合注册至bean工厂


ConfigurationPropertiesBindingPostProcessorRegistrar
直接查看复写的方法

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

主要注册了两个beanDefinition,分别是ConfigurationPropertiesBindingPostProcessor类和ConfigurationBeanFactoryMetadata类。
看来具体的属性绑定就是这两个类来处理了,笔者继续往下分析

ConfigurationBeanFactoryMetadata

根据spring的bean的生命周期,实现BeanFactoryPostProcessor接口类中的postProcessBeanFactory()方法优先执行

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
        this.beanFactory = beanFactory;
        for (String name : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition definition = beanFactory.getBeanDefinition(name);
            String method = definition.getFactoryMethodName();
            String bean = definition.getFactoryBeanName();
            if (method != null && bean != null) {
                this.beansFactoryMetadata.put(name, new FactoryMetadata(bean, method));
            }
        }
    }

其主要用于获取bean工厂上实现FactoryBean接口的beanDefinitions并保存至beansFactoryMetadata属性中

ConfigurationPropertiesBindingPostProcessor

根据spring的bean的生命周期,首先我们看下其afterPropertiesSet()实现方法

    @Override
    public void afterPropertiesSet() throws Exception {
        // 获取ConfigurationBeanFactoryMetadata实体类
        this.beanFactoryMetadata = this.applicationContext.getBean(
                ConfigurationBeanFactoryMetadata.BEAN_NAME,
                ConfigurationBeanFactoryMetadata.class);
        // 创建属性绑定类
        this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(
                this.applicationContext, VALIDATOR_BEAN_NAME);
    }

再而我们看下其postProcessBeforeInitialization()方法

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        // 查找是否bean上对应的class上含有ConfigurationProperties注解
        ConfigurationProperties annotation = getAnnotation(bean, beanName,
                ConfigurationProperties.class);
        if (annotation != null) {
            // 开始绑定操作
            bind(bean, beanName, annotation);
        }
        return bean;
    }

Ok,我们继续往下追踪bind()方法

    private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
        ResolvableType type = getBeanType(bean, beanName);
        // 查看类上是否含有@Validated注解
        Validated validated = getAnnotation(bean, beanName, Validated.class);
        Annotation[] annotations = (validated != null
                ? new Annotation[] { annotation, validated }
                : new Annotation[] { annotation });
        // class/实例/注解 三个绑定在一起供下述调用
        Bindable<?> target = Bindable.of(type).withExistingValue(bean)
                .withAnnotations(annotations);
        try {
            this.configurationPropertiesBinder.bind(target);
        }
        catch (Exception ex) {
            throw new ConfigurationPropertiesBindException(beanName, bean, annotation,
                    ex);
        }
    }

最终的如何绑定本文就不讲解了,涉及的代码很多,有兴趣的读者可自行分析。
很明显其会读取springboot的统一配置比如application.properties\application.yml应用于@ConfigurationProperties注解

小结

如果想在springboot中使用外部源的属性值有两个方法

  1. @PropertySource注解加载外部源,然后通过@Value注解来进行注入即可

  2. @ConfigurationProperties注解修饰javabean,其prefix属性是必须配置的,javabean的内部属性与配置文件指定的属性须一致,并且setter方法必须配置,这样可确保属性设置能成功;
    并且必须使用@EnableConfigurationProperties注解,通过其value属性关联上述的javabean类

springboot情操陶冶-@ConfigurationProperties注解解析

标签:tar   htm   很多   demo1   amp   tap   外部   enable   gpo   

原文地址:https://www.cnblogs.com/question-sky/p/9468021.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!