SpringBoot之浅析配置项解析(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 在我们的开发工作总是离不了配置项相关的配置工作,SpringBoot也为我们提供了@ConfigurationProperties注解来进行配置项信息的配置工作,同时也提供了几个配置文件的默认加载位置,如:classpath:application.

在我们的开发工作总是离不了配置项相关的配置工作,SpringBoot也为我们提供了@ConfigurationProperties注解来进行配置项信息的配置工作,同时也提供了几个配置文件的默认加载位置,如:classpath:application.properties、classpath:application.yml、classpath:application.yaml、classpath:/config/application.properties、classpath:/config/application.yml、classpath:/config/application.yaml等。另外我们还可以在命令行中、系统属性中、虚拟机参数中、Servlet上下文中进行配置项的配置,既然有这么多的配置位置,程序在加载配置项的时候总得有一个先后顺序吧,要不然系统不就乱套了。在SpringBoot中大概有这样的一个先后加载顺序(优先级高的会覆盖优先级低的配置):

  1. 命令行参数。
  2. Servlet初始参数
  3. ServletContext初始化参数
  4. JVM系统属性
  5. 操作系统环境变量
  6. 随机生成的带random.*前缀的属性
  7. 应用程序以外的application.yml或者appliaction.properties文件
  8. classpath:/config/application.yml或者classpath:/config/application.properties
  9. 通过@PropertySource标注的属性源
  10. 默认属性

那么SpringBoot是怎么创建这样的一个优先顺序的呢?默认的application.properties是怎么被加载的呢?在本章中我将把其中奥秘慢慢道出:
我在之前的SpringBoot启动流程简析的文章中说过,SpringBoot在启动的过程中会从spring.factories中加载一些ApplicationListener,在这些ApplicationListener中其中就有一个我们今天要说的ConfigFileApplicationListener;我们之前也说过在启动过程中会创建ConfigurableEnvironment,也会进行命令行参数的解析工作。在org.springframework.boot.SpringApplication#prepareEnvironment这个方法中有这样的一段代码:
prepareEnvironment
先创建应用可配置的环境变量,为命令行进行环境变量配置工作:

    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
        //将命令行参数转换为org.springframework.core.env.PropertySource
        configurePropertySources(environment, args);
        //Profile的配置,这里先不说明
        configureProfiles(environment, args);
    }
    protected void configurePropertySources(ConfigurableEnvironment environment,
            String[] args) {
        //从上面创建的ConfigurableEnvironment实例中获取MutablePropertySources实例
        MutablePropertySources sources = environment.getPropertySources();
        //如果有defaultProperties属性的话,则把默认属性添加为最后一个元素
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(
                    new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        //这里addCommandLineProperties默认为true 如果有命令行参数的数
        if (this.addCommandLineProperties && args.length > 0) {
            //name为:commandLineArgs
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            //如果之前的MutablePropertySources中有name为commandLineArgs的PropertySource的话,则把当前命令行参数转换为CompositePropertySource类型,和原来的PropertySource进行合并,替换原来的PropertySource
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(new SimpleCommandLinePropertySource(
                        name + "-" + args.hashCode(), args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                //如果之前没有name为commandLineArgs的PropertySource的话,则将其添加为MutablePropertySources中的第一个元素,注意了这里讲命令行参数添加为ConfigurableEnvironment中MutablePropertySources实例的第一个元素,且永远是第一个元素
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

从上面的代码中我们可以看到,SpringBoot把命令行参数转换为PropertySource,并添加为环境变量中的第一个元素!这里简单的提一下MutablePropertySources 这个类,它的UML如下所示:
MutablePropertySources
从上面的UML中我们可以看到,MutablePropertySources实现了Iterable接口,是一个可迭代的类,在这个类中有这样的一个属性:

private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();

一个类型为PropertySource的CopyOnWriteArrayList,这里用的是CopyOnWriteArrayList,而不是ArrayList、LinkedList,大家可以想一下这里为什么用了CopyOnWriteArrayList。
propertySourceList
MutablePropertySources中的这些方法都是通过CopyOnWriteArrayList中的方法来实现的。我们在之前的文章中说明,SpringBoot创建的ConfigurableEnvironment实例是StandardServletEnvironment,其UML类图如下:
StandardServletEnvironment,其在实例化的过程中,会调用父类的构造函数先实例化父类,其父类StandardEnvironment为默认无参构造函数,AbstractEnvironment中的无参构造函数如下:

    public AbstractEnvironment() {
    //调用customizePropertySources方法进行定制PropertySource
    customizePropertySources(this.propertySources);
    }

在StandardServletEnvironment和StandardEnvironment分别重写了这个方法,其调用为StandardServletEnvironment中的customizePropertySources方法,其源码如下:

    protected void customizePropertySources(MutablePropertySources propertySources) {
    //SERVLET_CONFIG_PROPERTY_SOURCE_NAME 为 servletConfigInitParams 添加servletConfigInitParams 的PropertySource
        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    //SERVLET_CONTEXT_PROPERTY_SOURCE_NAME为servletContextInitParams 添加servletContextInitParams 的PropertySource
        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    //如果有JNDI
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
    //JNDI_PROPERTY_SOURCE_NAME 为 jndiProperties 添加jndiProperties  的PropertySource
            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
        }
        //调用父类的StandardEnvironment中的customizePropertySources
        super.customizePropertySources(propertySources);
    }
    protected void customizePropertySources(MutablePropertySources propertySources) {
    //SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME 为 systemProperties 添加systemProperties 的PropertySource
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    //SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME 为 systemEnvironment 添加systemEnvironment的PropertySource
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

从上面的分析中我们可以看到在创建StandardServletEnvironment的实例的时候,会向org.springframework.core.env.AbstractEnvironment#propertySources中按顺序添加:name分别为:servletConfigInitParams、servletContextInitParams、jndiProperties 、systemProperties、systemEnvironment 的PropertySource,再按照我们前面的分析将name为commandLineArgs的PropertySource放到第一位,则org.springframework.core.env.AbstractEnvironment#propertySources的顺序到现在为:commandLineArgs、servletConfigInitParams 、servletContextInitParams 、jndiProperties 、systemProperties 、systemEnvironment,是不是和我们前面说的对照起来了?我们一直在说PropertySource,也一直在说PropertySource中的name,对于PropertySource我们可以理解为带name的、存放 name/value 的property pairs;那么其中的name我们应该如何理解呢?在org.springframework.core.env.MutablePropertySources中有这样一个方法:addAfter,其作用是将某个PropertySource的实例添加到某个name的PropertySource的后面,其源码如下所示:

    public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
        //确定所传入的relativePropertySourceName和所传入的propertySource的name不相同
        assertLegalRelativeAddition(relativePropertySourceName, propertySource);
        //如果之前添加过此PropertySource 则移除
        removeIfPresent(propertySource);
        //获取所传入的relativePropertySourceName的位置
        int index = assertPresentAndGetIndex(relativePropertySourceName);
        //将传入的propertySource添加到相应的位置
        addAtIndex(index + 1, propertySource);
    }

在上面的代码中有assertPresentAndGetIndex这样的一段代码比较重要:

    int index = assertPresentAndGetIndex(relativePropertySourceName);

    private int assertPresentAndGetIndex(String name) {
        //获取name为某个值的PropertySource的位置,
        int index = this.propertySourceList.indexOf(PropertySource.named(name));
        if (index == -1) {
            throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist");
        }
        return index;
    }

在上面的代码中获取PropertySource的元素的位置的时候,是调用List中的indexOf方法来进行查找的,但是其参数为PropertySource.named(name)产生的对象,我们之前往propertySourceList中放入的明明是PropertySource类型的对象,这里在查找的时候为什么要用PropertySource.named(name)产生的对象来进行索引位置的查找呢?PropertySource.named(name)产生的对象又是什么呢?

    public static PropertySource<?> named(String name) {
        return new ComparisonPropertySource(name);
    }

PropertySource.named(name)产生的对象是ComparisonPropertySource的实例,它也是PropertySource的一个子类,那么为什么用它也能查找到之前放入到propertySourceList中的元素的位置呢?通过翻开indexOf这个方法的源码我们知道,它是通过调用元素的equals方法来判断是否是同一个元素的,而凑巧的是在PropertySource中重写了equals这个方法:

    public boolean equals(Object obj) {
        return (this == obj || (obj instanceof PropertySource &&
                ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name)));
    }

到这里就很明显了,PropertySource中的name属性是用来判断是否是同一个元素的,即是否是同一个PropertySource的实例!我们在创建PropertySource类型的子类的时候都会传入一个name,直接用我们创建的PropertySource来进行位置的查找不就可以了吗?为什么还要创建出来一个ComparisonPropertySource类呢?通过翻看ComparisonPropertySource这个类的源码我们可以发现,在这个类中调用getSource、containsProperty、getProperty方法都会抛出异常,并且除了这三个方法之外没有多余的方法,如果直接用我们创建的PropertySource的话,保不齐你会重写它的equals方法,是不是?用ComparisonPropertySource的话,即使你在别的PropertySource实现类重写了PropertySource方法,在查找其顺序是也要按照Spring定义的规则来,并且ComparisonPropertySource只能做干查找元素位置这一件事,其他的事它什么也干不了,这又是不是设计模式中的某一个原则的体现呢?

相关文章
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
172 0
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
175 2
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
430 37
|
2月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
41 1
|
1月前
|
前端开发 JavaScript Java
【SpringBoot系列】视图解析器的搭建与开发
【SpringBoot系列】视图解析器的搭建与开发
29 0
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
157 1
|
24天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
97 62
|
22天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
40 2
|
25天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的大学竞赛报名管理系统
基于Java+Springboot+Vue开发的大学竞赛报名管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的大学竞赛报名管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
223 3
基于Java+Springboot+Vue开发的大学竞赛报名管理系统

推荐镜像

更多
下一篇
无影云桌面