《【源码解析】凭什么?spring boot 一个 jar 就能开发 web 项目》 中有读者反应: > 部署后运维很不方便,比较修改一个 IP 配置,须要从新打包。html
这一点我是深有体会,17 年自学,并很大胆的直接在生产环境用的时候,我都是让产品经理(此时他充当咱们的运维,嘿嘿)用压缩软件打开 jar,而后复制出配置,修改完以后再替换回去。为何我这么大胆,由于当时才入行一年,并且以为有架构师兜底,我就奔放了。你是不知道,当时负责这个项目的开发(c#开发)一开始不想用 SpringBoot 的。java
不过现在看到这个问题,我有点震惊,都 9102 年了,居然还担忧这样的问题。我想说,哥们,这真的不是事儿。SpringBoot 早就提供了方法来解决这个问题。git
SpringBoot 有不少生产特性,能够在生产环境中使用时更加方便。其中外部化配置基本都会用到。web
> Spring Boot 容许外部化配置,以便相同的应用在不一样的环境中工做。 > 属性值能够在 Spring 环境中使用 @Value 或 @ConfigurationProperties 使用。spring
这次参考的版本是 SpringBoot-2.2.0.RELEASE
json
外部化配置的优先级顺序以下:c#
$HOME/.config/spring-boot
@TestPropertySource
properties
属性:在 @SpringBootTest 和 用来测试特定片断的测试注解SPRING_APPLICATION_JSON
中的属性:内嵌在环境变量或系统属性中的 JSONServletConfig
初始化参数ServletContext
初始化参数java:comp/env
中的 JNDI 属性System.getProperties()
RandomValuePropertySource
):random.*
属性application-{profile}.properties
application-{profile}.properties
application.properties
application.properties
@PropertySource
注解:用于 @Configuration
类上SpringApplication.setDefaultProperties
指定注意:以上用 properties
文件的地方也可用 yml
文件数组
my.uuid=${random.uuid}
java -jar -Ddemo=vm demo.jar --demo=arg
System#getProperty
获取Environment#getProperty
获取,若经过此方法获取不到,会获取 vm 同名参数public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(ArgApplication.class, args); LOGGER.info("----------------"); /* 打印 arg 参数 */ Arrays.stream(args) .forEach( arg -> { LOGGER.info("arg:{}", arg); }); /* 命令行传参 demo */ LOGGER.info("System#getProperty:{}", System.getProperty("demo")); LOGGER.info("Environment#getProperty:{}", context.getEnvironment().getProperty("demo")); }
输入命令安全
java -jar -Ddemo=vm arg-0.0.1-SNAPSHOT.jar aaa bbb ccc --demo=arg
效果以下:bash
---------------- arg:aaa arg:bbb arg:ccc arg:--demo=arg System#getProperty:vm Environment#getProperty:arg
而若是执行命令是:
java -jar -Ddemo=vm arg-0.0.1-SNAPSHOT.jar aaa bbb ccc
结果以下:
arg:aaa arg:bbb arg:ccc System#getProperty:vm Environment#getProperty:vm
若是执行命令是:
java -jar arg-0.0.1-SNAPSHOT.jar aaa bbb ccc --demo=arg
结果以下:
arg:aaa arg:bbb arg:ccc arg:--demo=arg System#getProperty:null Environment#getProperty:arg
优先级:
若是定义了 spring.config.location
,如:classpath:/custom-config/,file:./customr-config/
,优先级以下:
若是指定了 spring.config.additional-location
,会先加载 additional 配置 如:spring.config.additional-location=classpath:/custom-config/,file:./customr-config/
,优先级以下:
默认的 profile 是 default
,当没有指定spring.profiles.active
属性时,默认会加载application-default.properties
文件。指定 profiles 文件的加载顺序与上述不指定 profiles 文件的加载一致。指定 profile 文件的属性始终覆盖未指定文件的属性
。如:spring.profiles.active=dev
,则 application-dev.properties
文件内的属性会覆盖 application.properties
内的同名属性。
> 注意:若是在 spring.config.location
属性中指定了 文件
,则此文件对应的特定 profiles 类文件不起做用。若是想要起做用,在 spring.config.location
中使用 文件夹
。
配置文件中能够引用以前定义的值,以下:
app.name=MyApp app.description=${app.name} is a Spring Boot application.
能够用此特性建立一些已存在的 Spring Boot 配置的较短、易于使用的变量。以下:
# nacos 配置示例 spring: cloud: nacos: config: server-addr: 127.0.0.1:8848 namespace: d9a39d78-xxxxxxxx-ea4f282e9d99 discovery: server-addr: 127.0.0.1:8848 namespace: d9a39d78-xxxxxxxx-ea4f282e9d99 # Discovery 配置示例 nacos: plugin: namespace: d9a39d78-xxxxxxxx-ea4f282e9d99
可改成以下配置
spring: cloud: nacos: config: server-addr: ${app.server-addr} namespace: ${app.namespace} discovery: server-addr: ${app.server-addr} namespace: ${app.namespace} # Discovery 配置示例 nacos: plugin: namespace: ${app.namespace} app: server-addr: 127.0.0.1:8848 namespace: d9a39d78-xxxxxxxx-ea4f282e9d99
而后在命令行能够直接经过 -Dapp.namespace
或 --app.namespace
来传参,会方便不少。特别是在多个地方用到同一个属性的时候。
Spring Boot 不支持属性加密,但提供钩子节点修改配置属性。EnvironmentPostProcessor
接口容许在应用启动前操做 Environment
。
yaml 文件使用的时候很是直观、方便。并且在 Spring Boot 中作了处理,获取 yaml 和 properties 文件中的属性基本是同样的操做。
经过 spring.profiles
指示什么时候使用对应的配置,使用 ---
进行配置分隔
# application.yml server: address: 192.168.1.100 --- spring: profiles: development server: address: 127.0.0.1 --- spring: profiles: production & eu-central server: address: 192.168.1.120
用 @PropertySource
不能加载 yaml 文件,这种状况下只能使用 properties 文件。
在特定 profile 的 yaml 文件中使用多 profile 配置,会有意料以外的状况:
# application-dev.yml server: port: 8000 --- spring: profiles: "!test" security: user: password: "secret"
当运行时指定 --spring.profiles.active=dev
,启用 dev profile,其它的 profile 会忽略。也就是此例中 spring.security.user.password
属性会失效。
所以,不要在指定 profile 的 yaml 文件中使用多种 profile 配置。
经过 @ConfigurationProperties
注解将属性(properties、yml 文件、环境变量等)绑定到类对象中。与自动配置类相似。
@ConfigurationProperties("acme") public class AcmeProperties{ private boolean enabled; private InetAddress remoteAddress; private final Security security = new Security(); // getter and setter public static class Security{ private String username; private String password; private List<string> roles = new ArrayList<>(Collections.singleton("USER")); // getter and setter } }
> 这种安排依赖于默认的无参构造器,getter 和 setter 一般是必需的,由于绑定就像 Spring MVC 同样是经过标准的 Java Beans 属性描述符进行的。在下列状况下,可省略 setter: > * Maps:只要被初始化后,getter 必须而 setter 没必要须,binder 能够对它们进行修改 > * Collections 和 数组:能够经过索引或逗号分隔的值来设定属性。后者必须有 setter 方法。建议对于这种状况一直加上 setter。若是初始化了一个 Collection,确保它不是不可变类型。 > * 若是初始化了嵌套的 POJO 属性(如上例中的 Security),setter 不是必须的。若是须要 binder 经过其默认构造器动态建立实例,则须要 setter
> 注意:若是使用 Lombok 生成 getter 和 setter,确保不会生成任何特定的构造器,否则容器会自动使用它来实例化对象。 > 最后,只有标准 Java Bean 属性能够这样绑定属性,静态属性不支持。
上述示例能够改为以下:
@ConstructorBinding @ConfigurationProperties("acme") public class AcmeProperties{ private final boolean enabled; private final InetAddress remoteAddress; private final Security security; public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security){ this.enabled = enabled; this.remoteAddress = remoteAddress; this.security = security; } // getter and setter public static class Security{ private final String username; private final String password; private final List<string> roles; public Security(String username, String password, @DefaultValue("USER") List<string> roles){ this.username = username; this.password = password; this.roles = roles; } // getter and setter } }
@ConstructorBinding
注解表示使用构造函数绑定属性值。这意味着 binder
将指望找到一个包含待绑定参数的构造器。 @ConstructorBinding
类的嵌套成员也将经过构造函数绑定属性值。
可使用 @DefaultValue
指定默认值,转换服务将字符串值强转为缺乏属性的目标类型。
> 要使用构造绑定,类必须容许使用 @EnableConfigurationProperties
或 配置属性扫描方式。不能对由常规 Spring 机制建立的 bean 使用构造函数绑定。如:@Component Bean、经过@Bean 方法建立的 Bean 或使用@Import 加载的 Bean
> 若是类中有多个构造器,能够直接将 @ConstructorBinding
注解使用在要绑定的构造器上。
@ConfigurationProperties
注解类型> Spring Boot 提供了一个基础设施来绑定这些类型并将它们自动注册为 bean。 若是应用程序中使用 @SpringBootsApplication
,用 @ConfigurationProperties
注解的类将被自动扫描并注册为 bean。默认状况下,将从声明此注解的类的包中进行扫描。若是要扫描特定的包,能够对 ·@SpringBootsApplication
注解的类显式使用 @ConfigurationPropertiescan
注解,以下例所示:
@SpringBootApplication @ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" }) public class MyApplication { }
> 有时,用 @ConfigurationProperties
注释的类可能不适合扫描,例如,若是正在开发本身的自动配置,在这些状况下,能够在任何@Configuration 类上指定要处理的类型列表,以下例所示:
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(AcmeProperties.class) public class MyConfiguration { }
> 注意:当使用配置属性扫描或经过@EnableConfigurationProperties 注册@ConfigurationProperties bean 时,bean 有一个常规名称:<prefix>-<fqn>
,其中 <prefix>
是 @ConfigurationProperties
注解中指定的环境 key 前缀,<fqn>
是 bean 的彻底限定名。若是注解没有提供任何前缀,则只使用 bean 的彻底限定名。 > 上例中 bean name 是 acme-com.example.AcmeProperties
。
@ConfigurationProperties
注解类型这种类型的配置在 SpringApplication 外部 YAML 配置中特别适用,以下例所示:
# application.yml acme: remote-address: 192.168.1.1 security: username: admin roles: - USER - ADMIN
@ConfigurationProperties
bean 能够像其它 bean 同样注入使用。以下:
@Service public class MyService{ private final AcmeProperties properties; @Autowired public MyService(AcmeProperties properties){ this.properties = properties; } // ... }
> 使用 @ConfigurationProperties
还能够生成元数据文件,IDE 可使用这些文件提供代码自动完成功能。
除了能够在 类
上使用 @ConfigurationProperties
注解,还能够在 public @Bean 方法
上使用它。若是要将属性绑定到不在控制范围内的第三方组件,那么这样作特别有用。
要从 Environment
属性配置 bean,将 @ConfigurationProperties
添加到其 bean 注册中,以下例所示:
@ConfigurationProperties(prefix = "another") @Bean public AnotherComponent anotherComponent() { //... }
> 用 another
前缀定义的任何 JavaBean 属性都映射到 AnotherComponent
bean 上,映射方式相似于前面的 AcmeProperties 示例。
> Spring Boot 使用一些宽松的规则将 Environment
属性绑定到@ConfigurationProperties
bean,所以环境属性名和 bean 属性名之间不须要彻底匹配。常见的包括短划线分隔的环境属性(例如,context-path
绑定到 contextPath
)和大写的环境属性(例如,PORT
绑定到 port
)。
@ConfigurationProperties(prefix="acme.my-project.person") public class OwnerProperties { private String firstName; public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } }
对于以上 Java Bean,可使用如下属性
> 注意:注解的前缀值必须是短横线 (小写,用-分隔,如:acme.my-project.person)。
> 建议:若是可能的话,将属性存储为小写的短横线格式,例如:my.property-name=acme。
在绑定到 Map
属性时,若是 key
包含除小写字母-数字字符或 -
以外的任何内容,则须要使用括号符号,以便保留原始值。若是 key
没有被[]
包围,则删除任何不是字母数字或 -
的字符。
acme: map: "[/key1]": value1 "[/key2]": value2 /key3: value3
上面的属性将绑定到 Map
的这些 key
中:/key1
、/key2
、key3
当在多个位置配置 list 时,经过替换(而非添加)整个 list 来覆盖。
@ConfigurationProperties("acme") public class AcmeProperties { private final List<mypojo> list = new ArrayList<>(); public List<mypojo> getList() { return this.list; } }
acme: list: - name: my name description: my description --- spring: profiles: dev acme: list: - name: my another name
当启用 dev
配置时,AcmeProperties.list
中值包含一个 MyPojo
对象(name 为my another name
),不是添加操做,而是覆盖操做。
> 当一个 List
在多个 profiles 中定义时,最高优先级的被使用。
对于 Map
属性,能够使用从多个属性源获取属性值进行绑定。可是,对于多个源中的同一属性,将使用优先级最高的属性。
@ConfigurationProperties("acme") public class AcmeProperties { private final Map<string, mypojo> map = new HashMap<>(); public Map<string, mypojo> getMap() { return this.map; } }
acme: map: key1: name: my name 1 description: my description 1 --- spring: profiles: dev acme: map: key1: name: dev name 1 key2: name: dev name 2 description: dev description 2
当 dev 配置启用时,AcmeProperties.map
中包含两个键值对。key1
中 pojo name 为 dev name 1,description 为 my description 1;key2
中 pojo name 为 dev name 2,description 为 dev description 2。
不一样属性源的配置进行了合并 > 以上合并规则适用于全部的属性源
> Spring Boot 试图在绑定到 @ConfigurationProperties
bean 时将外部应用程序属性强转为正确的类型。若是须要自定义类型转换,能够提供 ConversionService
bean(带有名为 ConversionService
的 bean)或自定义属性编辑器(经过 CustomEditorConfigurer
bean)或自定义 Converters
(使用 bean 定义注解 @ConfigurationPropertiesBinding
)。
> 注意:因为此 bean 在应用程序生命周期的早期被请求,请确保限制 ConversionService
正在使用的依赖项。一般,须要的任何依赖项在建立时均可能未彻底初始化。若是自定义的 ConversionService
不须要配置 keys 强转,而且仅依赖于使用 @ConfigurationPropertiesBinding
限定的自定义转换器,则可能须要将它重命名。
SpringBoot 对表示持续时间有专门的支持。若是暴露 java.time.Duration
属性,则能够用如下格式:
long
表示(除非指定了 @DurationUnit
,不然使用毫秒做为默认单位)java.time.Duration
使用的标准 ISO-8601 格式@ConfigurationProperties("app.system") public class AppSystemProperties { @DurationUnit(ChronoUnit.SECONDS) private Duration sessionTimeout = Duration.ofSeconds(30); private Duration readTimeout = Duration.ofMillis(1000); public Duration getSessionTimeout() { return this.sessionTimeout; } public void setSessionTimeout(Duration sessionTimeout) { this.sessionTimeout = sessionTimeout; } public Duration getReadTimeout() { return this.readTimeout; } public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; } }
要指定 30 秒的 sessionTimeout,30、PT30S 和 30s 都是等效的。500ms 的 readTimeout 能够用如下任何形式指定:500、PT0.5S 和 500ms。 也可使用如下任何支持的单位:
ns
:纳秒us
:微妙ms
:毫秒s
:秒m
:分h
:时d
:天> 默认的单位是毫秒,可使用 @DurationUnit
指定
Spring 框架有一个 DataSize
类型,以字节表示大小。若是暴露一个 DataSize
属性,则能够用如下格式:
long
表示(除非指定了 @DataSizeUnit
,不然使用字节做为默认单位)java.time.Duration
使用的标准 ISO-8601 格式10MB
表示 10 兆字节)。@ConfigurationProperties("app.io") public class AppIoProperties { @DataSizeUnit(DataUnit.MEGABYTES) private DataSize bufferSize = DataSize.ofMegabytes(2); private DataSize sizeThreshold = DataSize.ofBytes(512); public DataSize getBufferSize() { return this.bufferSize; } public void setBufferSize(DataSize bufferSize) { this.bufferSize = bufferSize; } public DataSize getSizeThreshold() { return this.sizeThreshold; } public void setSizeThreshold(DataSize sizeThreshold) { this.sizeThreshold = sizeThreshold; } }
要指定 10 兆字节的 bufferSize
,10
和 10MB
是等效的。256 字节的 sizeThreshold
能够指定为 256
或 256B
。 也可使用如下任何支持的单位: B
:字节 KB
:千字节 MB
:兆字节 GB
:千兆字节 TB
:兆兆字节 > 默认的单位是字节,可使用 @DataSizeUnit
指定
每当对 @ConfigurationProperties
类使用 Spring 的@Validated
注解时,Spring Boot 就会验证它们。能够直接在配置类上使用 JSR-303 javax.validation
约束注解。必须确保类路径上有一个兼容的 JSR-303 实现(如:hibernate-validator),而后将约束注解添加到字段中。
@ConfigurationProperties(prefix="acme") @Validated public class AcmeProperties { @NotNull private InetAddress remoteAddress; // ... getters and setters }
> 注意:还能够经过注解@Bean 方法来触发验证,该方法使用@Validated 建立配置属性。
> 尽管嵌套属性在绑定时也将被验证,但最好对关联字段使用 @Valid
。这确保即便找不到嵌套属性,也会触发验证。
@ConfigurationProperties(prefix="acme") @Validated public class AcmeProperties { @NotNull private InetAddress remoteAddress; @Valid private final Security security = new Security(); // ... getters and setters public static class Security { @NotEmpty public String username; // ... getters and setters } }
> 还能够经过建立ConfigurationPropertiesValidator
bean 来添加自定义 Spring Validator
。@Bean
方法应该声明为 static
。配置属性验证器是在应用程序生命周期的早期建立的,将@Bean 方法声明为 static 能够建立 Bean,而无需实例化@configuration 类。这样作能够避免任何可能由早期实例化引发的问题。
> 注意:spring-boot-actuator
模块包含一个端点,该端点暴露全部 @ConfigurationProperties
bean。访问 /actuator/configprops
可得到相关信息。
@Value
注解是一个核心容器特性,它不提供与 @ConfigurationProperties
相同的特性。
> 若是须要为组件定义了一组配置键,建议将它们配置到一个 @ConfigurationProperties
注解的 POJO 中。因为 @Value
不支持松绑定,若是须要使用环境变量提供值,则它不是一个好的选项。 > 虽然能够在 @Value
中编写 SpEL
表达式,但此类表达式不会从 properties 文件中处理。
若是项目比较大的话,分红了好几个 SpringBoot 工程,可使用某些 SpringCloud 组件,好比:配置中心。配置中心支持一个地方管理全部的配置,有些还能够支持修改配置实时生效而不用重启应用,真的是很棒棒呢。推荐使用 nacos
。若是项目比较小,你用 git
或者指定文件夹
来做为配置存放的地方也能够。
怎么样?有了这些用法的支持,你还会以为 Springboot 打成一个 jar
会在部署的时候很不方便吗?
官方文档 公众号:逸飞兮(专一于 Java 领域知识的深刻学习,从源码到原理,系统有序的学习)
</string,></string,></mypojo></mypojo></fqn></prefix></fqn></prefix></string></string></string>