Spring Boot 配置的优先级

本文主要参考Externalized Configuration
为了能让应用在不同的环境下运行,Spring Boot允许自定义配置文件,如properties文件、yaml文件、系统环境变量参数、命令行参数。配置文件的覆盖优先级如下

  1. Devtools全局配置
  2. @TestPropertySource注解
  3. @SpringBootTest的properties属性
  4. 命令行参数
  5. SPRING_APPLICATION_JSON
  6. ServletConfig、ServletContext
  7. JNDI
  8. Java System properties (System.getProperties())
  9. 系统环境变量
  10. 随机数配置
  11. 自定义配置文件
  12. application.properties
  13. @PropertySource
  14. Spring Boot内部默认的配置参数

Devtools全局配置

Developer Tools提供了一些开发帮助工具,在build.gradle添加依赖后启用。

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
    testRuntimeClasspath {
        extendsFrom developmentOnly
    }
}
dependencies {
    developmentOnly("org.springframework.boot:spring-boot-devtools")
}

Spring Boot会读取在计算机用户的home目录下的.spring-boot-devtools.properties文件里的配置参数到该计算级的所有Spring Boot应用中作为顶层配置,如Linux环境下root用户下~/.spring-boot-devtools.properties文件。开发过程中,可以将一些个人参数记录在这个配置文件中,例如ip地址,机器uuid,datasource参数等。在该配置文件中的定义的配置环境并不会影响到应用配置的读取,官方原话是:

Profiles activated in .spring-boot-devtools.properties will not affect the loading of profile-specific configuration files.

但要注意,该配置优先级最高,设置的时候需要做好记录否则会出现"原因不明的bug",不过应该很少人会用到这个功能。分析下源码,就是加了一个配置切面,并把其设置为顶层配置:

public class DevToolsHomePropertiesPostProcessor implements EnvironmentPostProcessor {
    private static final String FILE_NAME = ".spring-boot-devtools.properties";
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        File home = getHomeFolder();//System.getProperty("user.home")获取用户目录
        File propertyFile = (home != null ? new File(home, FILE_NAME) : null);
        if (propertyFile != null && propertyFile.exists() && propertyFile.isFile()) {
            FileSystemResource resource = new FileSystemResource(propertyFile);
            Properties properties;
            try {
                properties = PropertiesLoaderUtils.loadProperties(resource);
                //成功加载后作为顶层配置
                environment.getPropertySources().addFirst(
                        new PropertiesPropertySource("devtools-local", properties));
            }
            catch (IOException ex) {
                throw new IllegalStateException("Unable to load " + FILE_NAME, ex);
            }
        }
    }

@TestPropertySource注解

在测试的时候,可能会使用另一套测试专用的配置,该套配置的优先级高于系统环境变量、java系统参数、程序内部参数,@TestPropertySource注解就是用来指定这一类配置的。该注解一共有5个参数可以设置:

  • inheritLocations: boolean
    是否从父类继承locations,默认为true
  • inheritProperties: boolean
    是否从父类继承properties
  • locations\value: String[]
    配置文件的路径,路径格式支持类路径("classpath:/com/spring/test/DemoTest.properties"),或者文件路径("file:/dir/DemoTest.properties"),路径名支持SpEL但不支持通配符。配置文件格式支持xml和properties,不支持yaml。
  • properties: String[]
    参数键值对支持3种语法"key=value", "key:value","key value",优先级高于从locations读取的参数。

如果使用注解的时候没有任何参数,那么会从标注了注解的测试类的包中尝试读取配置文件,例如测试类com.spring.test.DemoTest,那么相应的默认配置文件为com.spring.test.DemoTest.properties,如果没有找到默认的配置文件则抛出非法状态异常。
AbstractTestContextBootstrapper在初始化上下文的时候会调用一个读取、合并配置的方法buildMergedContextConfiguration,该方法通过工具类TestPropertySourceUtils读取类的注解信息。TestPropertySourceUtils从类的注解解析配置信息后返回一个可合并的配置源。

static MergedTestPropertySources buildMergedTestPropertySources(Class<?> testClass) {
        Class<TestPropertySource> annotationType = TestPropertySource.class;
        AnnotationDescriptor<TestPropertySource> descriptor = findAnnotationDescriptor(testClass, annotationType);
        if (descriptor == null) {
            return new MergedTestPropertySources();
        }

        List<TestPropertySourceAttributes> attributesList = resolveTestPropertySourceAttributes(testClass);
        String[] locations = mergeLocations(attributesList);
        String[] properties = mergeProperties(attributesList);
        return new MergedTestPropertySources(locations, properties);
    }

@SpringBootTest的properties属性

@SpringBootTest的value\properties属性用于注入一些自定义的注解,语法要求和@TestPropertySource的properties一样,这里就不详细展开了。

命令行参数

用命令行方式启动Spring Boot应用程序的时候,可以注入一些配置参数,参数的格式是--key=name。举个简单的例子,程序直接输出一个参数,然后打成jar包后运行。

@SpringBootApplication
@Slf4j
public class App implements CommandLineRunner {

    @Value("${cl.name:undefined}")
    private String commandLineName;

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        log.info("Command line arguments: {}", Arrays.toString(args));
        log.info("commandLineName: {}", commandLineName);
    }
}

运行:
java -jar .\springbootconfiguraiton.jar --cl.name="Spring Boot Arguments"
从输出的结果中可以看到可以读取到命令行中的配置。


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.2.RELEASE)

2019-05-25 17:41:36.763  INFO 14416 --- [           main] demo.App                                 : Starting App on DESKTOP-HTJRA28 with PID 14416 (G:\spring boot configuration\build\libs\springbootconfiguraiton.jar started by 66439 in G:\spring boot configuration\build\libs)
2019-05-25 17:41:36.769  INFO 14416 --- [           main] demo.App                                 : No active profile set, falling back to default profiles: default
2019-05-25 17:41:36.859  INFO 14416 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@555590: startup date [Sat May 25 17:41:36 CST 2019]; root of context hierarchy
2019-05-25 17:41:39.662  INFO 14416 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2019-05-25 17:41:39.705  INFO 14416 --- [           main] demo.App                                 : Started App in 3.534 seconds (JVM running for 4.473)
2019-05-25 17:41:39.708  INFO 14416 --- [           main] demo.App                                 : Command line arguments: [--cl.name=Spring Boot Arguments]
2019-05-25 17:41:39.709  INFO 14416 --- [           main] demo.App                                 : commandLineName: Spring Boot Arguments
2019-05-25 17:41:39.714  INFO 14416 --- [       Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@555590: startup date [Sat May 25 17:41:36 CST 2019]; root of context hierarchy
2019-05-25 17:41:39.730  INFO 14416 --- [       Thread-2] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

SPRING_APPLICATION_JSON

可以在环境变量中定义一个key为SPRING_APPLICATION_JSON的参数,值为json字符串,Spring Boot会解析该json字符串作为参数注入到系统中。SPRING_APPLICATION_JSON可以定义在环境变量、系统配置中,命令行也是可以的,例如命令行参数中用到的demo,执行以下的命令也应该能得到相同的参数结果。
java -jar .\springbootconfiguraiton.jar SPRING_APPLICATION_JSON='{"cl":{"name"="Spring Boot Arguments"}}'
结果输出是undefined,不知道原因,这个配置方式用的应该也很少,放弃研究。。。

ServletConfig、ServletContext

优先级是ServletConfig > ServletContext,可以在application.yml中设置:

server:
  servlet:
    context-parameters:
      scp: 'scpV'

随机数配置

随机数配置大多用于测试,支持的类型如下:

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}

其中long\int可以限制数据范围,[]是闭区间,()是开区间。

自定义配置文件

这个应该是我们用的最多的。首先说优先级,文件可以放在以下4个位置,相同文件从上到下覆盖。外部指的是启动应用程序的目录,例如gradle用application插件打包后,运行的脚本目录就是./

  1. file:./config,在外部的config目录下
  2. file:./,在外部的目录下
  3. classpath:/config/resources/config
  4. classpath:/resources

文件的命名为application-[当前激活的环境名].[yml/properties],当前激活的配置可以用spring.profile.active=[当前激活的环境名]定义,多个环境名用逗号分隔,未设置时用default标识。关于如果修改默认的加载路径和文件名,后面会继续讨论。

application.properties

Spring Boot系统启动时默认会读取的配置文件,支持properties\yml格式。也就是说,会先加载application.properties,根据spring.profile.active的设置加载相应的application-XX.properties配置,然后按优先级合并配置文件。
不同文件目录下application.properties的优先级和自定义配置文件的顺序是一样的。

@PropertySource

类似@TestPropertySource注解,在项目中可以方便的注入自定义的配置文件,注解一共有5个参数:

  • value: String[]
    配置文件的路径,路径格式支持类路径("classpath:/com/spring/test/DemoTest.properties"),或者文件路径("file:/dir/DemoTest.properties"),路径名支持SpEL但不支持通配符。配置文件格式支持xml和properties,不支持yaml。
  • encoding: String
    字符集,默认为空,使用系统的字符集。
  • ignoreResourceNotFound: boolean
    当找不到资源文件时,是否忽略,默认为false,找不到资源的时候会报错。
  • name: String
    每一套PropertySource都有一个唯一的资源限定名,可以通过propertySource.getName()获取,默认为空,Spring根据资源类型(ClassPathResouce\FileSystemResource)生成一个资源名。
  • factory: subClass of PropertySourceFactory
    解析资源的工厂类,一般用默认的即可。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容