Spring boot | 属性配置

主要分析 application.x 、@ConfigurationProperties 实现原理。

application.x,表示:x 表示文件后缀名,可以是 properties,yaml,xml,yml

application 配置文件
1、Spring bean 创建流程回顾

AbstractAutowireCapableBeanFactory

1、函数doCreateBean->createBeanInstance(beanName, mbd, args)

1、依据mbd 定义创建 bean instance,
2、将instance 封装到BeanWrapperImpl 中,为后续对象属性设值做准备。
3、这步只创建了实例,没有对实例的属性值绑定值。

2、doCreateBean->populateBean(beanName, mbd, instanceWrapper);

1、从mbd 中获取属性配置,PropertyValues
pvs=mbd.getPropertyValues()
2、依据,autowireByName,autowireByType 为 pvs 添加新的属性值。
3、applyPropertyValues(),beanWapper 将pvs 设值为对象的属性值。
4、这步以及获取到配置信息给bean 对象的属性设置值。

PlaceholderConfigurerSupport implements BeanFactoryPostProcessor
0、BeanFactoryPostProcessor 是bean 配置信息读取完后,首先执行的方法,可以对注册的bean 信息进行修改。

1、PlaceholderConfigurerSupport 实现了对bean 定义中 PropertyValues 进行扩展,属性值来源有两个方面。

1、属性的值来源与环境变量,private Environment environment;
2、属性的值来源于配置文件

doProcessProperties(): 属性值与BeanDefinition绑定

  BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);    
             String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
        BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);

// 将属性值设置到BeanDefinition 中
visitor.visitBeanDefinition(bd);

PlaceholderConfigurerSupport类图参考:


PlaceholderConfigurerSupport.png
2、Spring boot 为PlaceholderConfigurerSupport 准备环境变量

1、SpringApplication#run
省略不相关代码

......
SpringApplicationRunListeners listeners = getRunListeners(args);

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
.....
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
.....
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        listeners.environmentPrepared(environment);
        return environment;
...
    }

看到listener,应该是观察者模式了。
SpringApplicationRunListeners 来源META-INF /spring.fatories:
在创建SpringApplication 对象时候读取,run方法之前。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

2、listeners.environmentPrepared(environment)
基于SimpleApplicationEventMulticaster对SpringApplication里面所有ApplicationListener 进行相同的广播通知,通知的事件不一样。

public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster
                .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    }

3、ApplicationListener来源于META-INF /spring.fatories
初始化读取

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

4、ConfigFileApplicationListener
配置文件有一个很重要的Listener,ConfigFileApplicationListener。这个类进行了application 属性文件的读取,Profile 判断,以及转换成ConfigurableEnvironment,提供给PlaceholderConfigurerSupport 使用。
ConfigFileApplicationListener 接受到事件通知

public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
        }
    }

EnvironmentPostProcessor 来源于META-INF /spring.factories 文件
,ConfigFileApplicationListener 本身也是一个EnvironmentPostProcessor,也是最核心的一个PostProcessor。postProcessors.add(this);

org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

ConfigFileApplicationListener#addPropertySources
进行application 文件解析。

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        RandomValuePropertySource.addToEnvironment(environment);
        new Loader(environment, resourceLoader).load();
    }

new Loader()

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
.....
            this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
                    getClass().getClassLoader());
        }

propertySourceLoaders解析xml,properties,yaml 格式,解析器来自META-INF /spring.factories

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
3、Spring boot 配置PlaceholderConfigurerSupport 注册

autoconfigure 项目文件META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
...autoconfigure.context.PropertyPlaceholderAutoConfiguration
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

}
小结:

ConfigFileApplicationListener 对application.x 文件进行读取,并且转换成环境变量,其中有很多地方对象都有来自于META-INF /spring.factories文件,比如,EnvironmentPostProcessor,PropertySourceLoader。autoconfigure 通过配置PropertySourcesPlaceholderConfigurer bean ,将boot 项目和spring 项目连通。环境变量信息一部分来来自于本身,另一部分来自于属性配置文件。

ConfigurationProperties 注解配置

入口:
autoconfigure 项目文件META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
autoconfigure.context.ConfigurationPropertiesAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration {

}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
    String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
    Class<?>[] value() default {};
}

我们可以看到springboot 中只要注解是@EnableXXXXXX,一般都是用import 注解来导入配置信息的。

EnableConfigurationPropertiesRegistrar 类主要代码:

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerInfrastructureBeans(registry);
        ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
        getTypes(metadata).forEach(beanRegistrar::register);
    }
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
        ConfigurationPropertiesBindingPostProcessor.register(registry);
        BoundConfigurationProperties.register(registry);
        ConfigurationPropertiesBeanDefinitionValidator.register(registry);
        ConfigurationBeanFactoryMetadata.register(registry);
    }

EnableConfigurationPropertiesRegistrar主要注册了两个信息

1、注册ConfigurationPropertiesBindingPostProcessor ,对被@ConfigurationProperties注解的类,进行属性值绑定。

2、@EnableConfigurationProperties(arg),注册arg 的类当成一个普通bean。并且arg类被@ConfigurationProperties注解。因为@ConfigurationProperties注解的类要配合其他注解(@Configuration)才能注册到bean定义中,才能生效,如果使用@EnableConfigurationProperties(arg) ,EnableConfigurationPropertiesRegistrar 会将arg添加到bean 定义中,这样才能被ConfigurationPropertiesBindingPostProcessor 处理。

ConfigurationPropertiesBindingPostProcessor绑定值:

BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
    Bindable<?> target = propertiesBean.asBindTarget();
    ConfigurationProperties annotation = propertiesBean.getAnnotation();
    BindHandler bindHandler = getBindHandler(target, annotation);
    return getBinder().bind(annotation.prefix(), target, bindHandler);
}

总结:

Spring boot 加强了默认属性的配置,并且还约定了配置文件application.x 名称。再一次明确约定大于配置的思想。spring boot通过扩展BeanPostProcessor ,如ConfigurationPropertiesBindingPostProcessor ,添加了@ConfigurationProperties注解的支持。还通过对application.x 文件解析,扩展了环境变量的值。明白属性值设置的原理,可以扩展如何将bean 对象的值,设置成自定义的值。也将有助于理解spring cloud 中属性值的配置 如bootstrap.x。

Spring 专题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354