zoukankan      html  css  js  c++  java
  • 2.SpringBoot学习(二)——Spring Boot ConfigurationProperties

    1.简介

    1.1 概述

    Annotation for externalized configuration. Add this to a class definition or a @Bean method in a @Configuration class if you want to bind and validate some external Properties (e.g. from a .properties file).

    Binding is either performed by calling setters on the annotated class or, if @ConstructorBinding is in use, by binding to the constructor parameters.

    Note that contrary to @Value, SpEL expressions are not evaluated since property values are externalized.

    一个外部化配置的注解。如果您要绑定和验证某些外部属性(例如,来自.properties文件),则将其添加到类定义或 @Configuration 类中的 @Bean 方法中。

    绑定可以通过在带注释的类上调用setter来执行,或者,如果正在使用 @ConstructorBinding,则可以通过绑定到构造函数参数来执行。

    请注意,与@Value相反,由于属性值是外部化的,因此不评估SpEL表达式。

    1.2 特点

    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ConfigurationProperties {
    
        String value() default "";
    
        String prefix() default "";
    
        boolean ignoreInvalidFields() default false;
    
        boolean ignoreUnknownFields() default true;
    }
    

    1.3 对比 @Value

    @Configuration @Value
    功能 批量注入配置文件中的属性 一个个指定
    松散绑定(松散语法) 支持 不支持
    SPEL语法 不支持 支持
    JSR303数据校验 支持 不支持
    复杂类型封装 支持 不支持

    2.环境

    1. JDK 1.8.0_201
    2. Spring Boot 2.2.0.RELEASE
    3. 构建工具(apache maven 3.6.3)
    4. 开发工具(IntelliJ IDEA )

    3.代码

    3.1 代码结构

    image-20200711201606906

    3.2 maven 依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    

    3.3 配置文件

    application.properties

    user.prop.name=zhangsan
    user.prop.age=20
    

    3.4 java代码

    UserProperties.java

    @Component
    @Validated // JSR303数据校验
    @ConfigurationProperties(prefix = "user.prop")
    public class UserProperties {
    
        @NotBlank
        private String name;
    
        @Range(min = 1, max = 200)
        private Integer age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    }
    

    UserProps.java

    @Component
    public class UserProps {
        @Value("${user.prop.name}")
        private String name;
    
        // SPEL 表达式
        @Value("#{10 * 2}")
        private Integer age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    }
    

    UserController.java

    @RestController
    public class UserController {
    
        @Autowired
        private UserProperties userProperties;
    
        @Autowired
        private UserProps userProps;
    
        @GetMapping("/user/get/1")
        public String getUser1() {
            return userProperties.getName() + "'s age is " + userProperties.getAge();
        }
    
        @GetMapping("/user/get/2")
        public String getUser2() {
            return userProps.getName() + "'s age is " + userProps.getAge();
        }
    }
    

    3.5 git 地址

    spring-boot/spring-boot-02-config

    4.结果

    启动 SpringBoot02ConfigApplication.main 方法,在 spring-boot-02-config.http 访问如下两个地址,输出 “zhangsan's age is 20” 表示请求成功

    image-20200711202711465

    image-20200711202749942

    5.源码分析

    5.1 @ConfigurationProperties 原理分析

    image-20200711210302871

    @SpringBootApplication 注解是一个复合注解,它里面包含一个 @ConfigurationPropertiesScan,这个里面又有一个 @EnableConfigurationProperties,@ConfigurationProperties 的作用与它有关。

    @ConfigurationProperties 中通过 @Import 引入一个 EnableConfigurationPropertiesRegistrar,它里面有一个 registerBeanDefinitions 方法

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerInfrastructureBeans(registry);
        ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
        getTypes(metadata).forEach(beanRegistrar::register);
    }
    

    registerBeanDefinitions 调用一个 registerInfrastructureBeans ,这个方法将 属性绑定后置处理器、bean 校验器、元数据注入到 registry 中,这里的 registry 保存了所有 bean 信息。

    static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
        ConfigurationPropertiesBindingPostProcessor.register(registry);
        ConfigurationPropertiesBeanDefinitionValidator.register(registry);
        ConfigurationBeanFactoryMetadata.register(registry);
    }
    

    通过查看类图可以知道,ConfigurationPropertiesBindingPostProcessor 是 BeanPostProcessor 的一个实现类

    image-20200711215341386

    它在 bean 实例化的时候发生作用,BeanPostProcessor 提供了 postProcessBeforeInitialization 和

    postProcessAfterInitialization 两个方法

    public interface BeanPostProcessor {
    
        @Nullable
        default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Nullable
        default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    }
    

    在 ConfigurationPropertiesBindingPostProcessor 的 postProcessBeforeInitialization 方法中提供了对于属性值的注入

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 属性绑定
        bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
        return bean;
    }
    

    在 bind 方法中,通过 ConfigurationPropertiesBinder 来绑定 ConfigurationProperties 中属性

    BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
        Bindable<?> target = propertiesBean.asBindTarget();
        // 获取目标 bean 上的 @ConfigurationProperties 注解
        ConfigurationProperties annotation = propertiesBean.getAnnotation();
        // 获取 BindHandler
        BindHandler bindHandler = getBindHandler(target, annotation);
        // 通过配置的 prefix 和 BindHandler 进行属性绑定
        return getBinder().bind(annotation.prefix(), target, bindHandler);
    }
    

    到这里已经比较清晰了,后面的就是从 应用上下文中获取属性值,然后转换成对应的类型,再将属性值设置给目标对象。

    5.2 @Value 原理分析

    image-20200711214716859

    这个流程中,doCreateBean 前面的流程实际上是 spirng bean 的初始化流程,在初始化过程中,会对 bean 的依赖和字段进行填充;BeanPostProcessor 也是在这个阶段发生作用

    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) {
                if (filteredPds == null) {
                    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                }
                pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                if (pvsToUse == null) {
                    return;
                }
            }
            pvs = pvsToUse;
        }
    }
    

    使用注解进行 bean 注入的时候,会有一个 AutowiredAnnotationBeanPostProcessor 的处理类,它里面有一个 postProcessProperties 方法

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
        InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
        try {
            metadata.inject(bean, beanName, pvs);
        }
        catch (BeanCreationException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
        }
        return pvs;
    }
    

    InjectionMetadata 是类的注入元数据,这里通过它来对 bean 中的属性进行注入,它里面提供了多种注入元件,而 ConfigurationProperties 主要通过字段属性进行注入

    image-20200711221221448

    AutowiredFieldElement 的 inject 方法实现如下

    @Override
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        Field field = (Field) this.member;
        Object value;
        // 判断是否已缓存,如果缓存了,直接获取
        if (this.cached) {
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
        }
        else {
            // 如果没有缓存,需要从 beanFactory 中获取具体值,然后缓存起来
            DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
            desc.setContainingClass(bean.getClass());
            Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
            Assert.state(beanFactory != null, "No BeanFactory available");
            TypeConverter typeConverter = beanFactory.getTypeConverter();
            try {
                value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
            }
            catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
            }
            synchronized (this) {
                if (!this.cached) {
                    if (value != null || this.required) {
                        this.cachedFieldValue = desc;
                        registerDependentBeans(beanName, autowiredBeanNames);
                        if (autowiredBeanNames.size() == 1) {
                            String autowiredBeanName = autowiredBeanNames.iterator().next();
                            if (beanFactory.containsBean(autowiredBeanName) &&
                                beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                                // 将获取到的值缓存起来
                                this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                    desc, autowiredBeanName, field.getType());
                            }
                        }
                    }
                    else {
                        this.cachedFieldValue = null;
                    }
                    // 修改标记
                    this.cached = true;
                }
            }
        }
        if (value != null) {
            // 最终将获取到的值,通过反射进行注入
            ReflectionUtils.makeAccessible(field);
            field.set(bean, value);
        }
    }
    

    接下来调用流程是 resolveDependency -> doResolveDependency -> resolveEmbeddedValue

    @Override
    @Nullable
    public String resolveEmbeddedValue(@Nullable String value) {
        if (value == null) {
            return null;
        }
        String result = value;
        for (StringValueResolver resolver : this.embeddedValueResolvers) {
            result = resolver.resolveStringValue(result);
            if (result == null) {
                return null;
            }
        }
        return result;
    }
    

    最后调用到 PropertyPlaceholderConfigurer,通过解析配置文件获取到最终值

    @Override
    @Nullable
    public String resolveStringValue(String strVal) throws BeansException {
        String resolved = this.helper.replacePlaceholders(strVal, this.resolver);
        if (trimValues) {
            resolved = resolved.trim();
        }
        return (resolved.equals(nullValue) ? null : resolved);
    }
    

    6.参考

    1. @ConfigurationProperties与@Value的区别
    2. springboot中@Value的工作原理
  • 相关阅读:
    React生命周期函数
    云效创建项目应用以及流水线的说明文档
    前端工作规范
    阮一峰 前端系列教程
    js对时间戳的处理 获取时间,昨天,今天,明天,时间不同格式
    当天时间小案例--时间戳,获取年月日星期时分秒
    React中构造函数constractor,为什么要用super(props)
    Java8新特性——Optional类的使用(有效的避免空指针异常)
    Java8新特性——新一套时间API的使用
    Java8新特性——StreamAPI 的使用
  • 原文地址:https://www.cnblogs.com/col-smile/p/13285850.html
Copyright © 2011-2022 走看看