当前栏目
重学SpringBoot系列之配置管理
重学SpringBoot系列之配置管理
Bean自动装载的核心问题
我们之前为大家介绍了,Spring Boot里面的各种Bean(类对象)能够实现自动装载,自动的装载帮我们减少了XML的配置,和手动编码进行Bean的加载工作。从而极大程度上帮我们减少了配置量和代码量
要实现Bean的自动装载,需要解决两个问题
- 如何保证Bean自动装载的灵活性?这个问题通过配置文件来解决,在配置A情况下去装载BeanY;在配置B情况下去装载BeanZ。(通常情况下配置A和B会有默认值,来决定默认的装载行为,这样就不需要我们配置了,进一步减少配置量)
- 如何保证Bean装载的顺序性?当BeanA装载完成之后再去装载BeanY,BeanY装载完成之后才去装载BeanX。这个装载顺序问题由@ConditionOnXXXXXXX注解来解决。
全局配置文件
SpringBoot使用一个全局的配置文件,配置文件名是固定的;
- application.properties
- application.yml
全局配置文件的作用:修改SpringBoot自动配置的默认值,通过配置来影响SpringBoot自动加载行为。
配置加载原理源码解析
所有的Spring Boot应用程序都是以SpringApplication.run()作为应用程序入口的。下面我们来一步一步跟踪一下这个函数。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
run方法传入了SpringApplication
对象和一些运行期参数。继续向前跟进,我们发现一个类叫做SpringFactoriesLoader
,这里面体现了Spring Boot加载配置文件的核心逻辑。
从上图可以看到:
- 从
META-INF/spring.factories
文件夹下下面加载了spring.factories
文件资源 - 然后读取文件中的
ClassName
作为值放入Properties
。
然后通过反射机制,对spring.factories
里面的类资源进行实例化,所以spring.factories
文件里面究竟写了什么类?这些类是做什么的?就是我们下一步要探究的问题了。
@EnableAutoConfiguration 作用
SpringBoot入口启动类使用了SpringBootApplication,实际上就是开启了自动配置功能@EnableAutoConfiguration。
SpringFactoriesLoader会以@EnableAutoConfiguration的包名和类名org.springframework.boot.autoconfigure.EnableAutoConfiguration为Key查找spring.factories文件,并将value中的类名实例化加载到Spring Boot应用中。如下图:
spring.factories文件中的每一行都是一个自动装配类。
Bean的自动装配实现原理简述
每一个自动配置类进行自动配置功能(spring.factories
中的每一行对应的类),我们以HttpEncodingAutoConfiguration
为例讲解一下:
//加载application全局配置文件内的部分配置到HttpEncodingProperties里面
@Configuration
@EnableConfigurationProperties({HttpEncodingProperties.class})
//当web容器类型是servlet的时候执行本类中的自动装配代码
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//当有一个CharacterEncodingFilter的这样一个类的字节码文件时时执行本类中的自动装配代码
@ConditionalOnClass({CharacterEncodingFilter.class})
//当spring.http.encoding配置值为enabled的时候执行本类中的自动装配代码
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true //如果application配置文件里面不配置,默认为true
)
public class HttpEncodingAutoConfiguration {
private final HttpEncodingProperties properties;
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean
//当没有CharacterEncodingFilter这个Bean就实例化CharacterEncodingFilter为一个bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.RESPONSE));
return filter;
}
@Bean
public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
}
//此处省略与自动加载无关的代码:HttpEncode的逻辑及其他
}
在配置类加载过程中,大量的使用到了条件加载注解:
我们讲的这个实现原理实际上就是一个自定义spring-boot-starter的实现原理,我们会在后面章节中自己编码实现一个分布式文件系统fastdfs与spring boot整合的starter。大家届时会有更深一步的理解。在以上的自动装配过程中依赖于HttpEncodingProperties的自定义属性,我们后面会讲如何读取自定义配置属性。
YAML
YAML语法及占位符语法
设计一个YAML数据结构
首先我们提出这样一个需求:
# 1. 一个家庭有爸爸、妈妈、孩子。
# 2. 这个家庭有一个名字(family-name)叫做“happy family”
# 3. 爸爸有名字(name)和年龄(age)两个属性
# 4. 妈妈有两个别名
# 5. 孩子除了名字(name)和年龄(age)两个属性,还有一个friends的集合
# 6. 每个friend有两个属性:hobby(爱好)和性别(sex)
上面的数据结构用yaml该如何表示呢?
family:
family-name: "happy family"
father:
name: dhy
age: 18
mother:
alias:
- lovely
- ailice
child:
name: xpy
age: 5
friends:
- hobby: football
sex: male
- hobby: basketball
sex: female
或者是friends的部分写成
friends:
- {hobby: football,sex: male}
- {hobby: basketball,sex: female}
规则1:字符串的单引号与双引号
- 双引号: 会转义字符串里面的特殊字符,如下面 被转义为换行:
name: “zhangsan
lisi”:输出:zhangsan 换行 lisi
- 单引号: 不会转义特殊字符,特殊字符最终只是作为一个普通的字符串数据,如:
name: ‘zhangsan
lisi’:输出:zhangsan
lisi
规则2:支持松散的语法
在spring Boot应用中YAML数据格式支持松散的绑定语法,也就是下面的三种key都是一样的。
family-name = familyName = family_name
但是不绝对,笔者印象中曾经遇到过某些写法不被兼容的情况。我通常使用中划线分隔的这种语法。
配置文件占位符
Spring Boot配置文件支持占位符,一些用法如下:为persopn.age设置一个随机数
person:
age: ${random.int}
随机数占位符
- ${random.value} - 类似uuid的随机数,没有"-"连接
- ${random.int} - 随机取整型范围内的一个值
- ${random.long} - 随机取长整型范围内的一个值
- ${random.long(100,200)} - 随机生成长整型100-200范围内的一个值
- ${random.uuid} - 生成一个uuid,有短杠连接
- ${random.int(10)} - 随机生成一个10以内的数
- ${random.int(100,200)} - 随机生成一个100-200 范围以内的数
默认值
占位符获取之前配置的值,如果没有可以是用“冒号”指定默认值
格式例如,xxxxx.yyyy是属性层级及名称,如果该属性不存在,冒号后面填写默认值
${xxxxx.yyyy:默认值}
比如为配置father.best
属性
father:
best: ${family.father.name:dhy}
如果family.father.name
存在则father.best=${family.father.name}
,family.father.name这个配置不存在,则取值father.best=dhy
YAML配置绑定变量两种方式
使用@Value获取配置值
通过@Value注解将family.family-name属性的值绑定到familyName成员变量上面。
@Data
@Component
public class Family {
@Value("${family.family-name}")
private String familyName;
}
使用@ConfigurationProperties获取配置值
下面是用于接收上一节中yml配置的java实体类,先不要看我写的代码。测试一下,看看你自己能不能根据yml的嵌套结构,写出来对应的java实体类:
// 1. 一个家庭有爸爸、妈妈、孩子。
// 2. 这个家庭有一个名字(family-name)叫做“happy family”
@Data
@Component
@ConfigurationProperties(prefix = "family") //表示配置的整体前缀
public class Family {
private String familyName; //成员变量名称要和yml配置项key一一对应
private Father father;
private Mother mother;
private Child child;
}
// 3. 爸爸有名字(name)和年龄(age)两个属性
@Data
public class Father {
private String name;
private Integer age;
}
// 4. 妈妈有两个别名
@Data
public class Mother {
private String[] alias;
}
//5. 孩子除了名字(name)和年龄(age)两个属性,还有一个friends的集合
@Data
public class Child {
private String name;
private Integer age;
private List<Friend> friends;
}
// 6. 每个friend有两个属性:hobby(爱好)和性别(sex)
@Data
public class Friend {
private String hobby;
private String sex;
}
测试用例
写一个测试用例测试一下,看看yml配置属性是否真的绑定到类对象的成员变量上面。
// @RunWith(SpringRunner.class) Junit4
@ExtendWith(SpringExtension.class) //Junit5
@SpringBootTest
public class CustomYamlTest {
@Autowired
Family family;
@Test
public void hello(){
System.out.println(family.toString());
}
}
测试结果,不能有为null的输出字段,如果有表示你的java实体数据结构写的不正确:
Family(familyName=happy family, father=Father(name=zimug, age=18),
mother=Mother(alias=[lovely, ailice]), child=Child(name=zimug2, age=5,
friends=[Friend(hobby=football, sex=male), Friend(hobby=basketball, sex=female)]))
比较一下二者
配置属性值数据绑定校验
为什么要对配置属性值校验
我们都知道配置文件是需要开发人员手动来修改的,只要是人为参与就会有出错的可能。为了避免人为配置出错的可能,我们需要对配置属性值做校验。
比如:
- 针对数据库密码配置:需要限定最小长度或者复杂度限制
- 针对系统对外发邮件,邮件发送方的邮箱地址配置:字符串配置要符合一定的邮件正则表达式规则
- 针对某些不能为空的配置:开发人员有可能忘了为它赋值,等等场景
我们不能等到程序上线之后,才发现相关的配置错误。所以我们通常对配置属性与类对象的成员变量绑定的时候,就加上一些校验规则。如果配置值不符合校验规则,在应用程序在启动的时候就会抛出异常
。
如何对绑定的属性值进行校验
比如:我们希望对之前章节定义的family类里面爸爸的年龄,进行校验。让其不能小于21岁,小于21就是不合理的配置,也就是错误配置。那我们该怎么做呢?
在需要校验的属性装配类上加@Validated注解
@Data
@Component
@Validated
@ConfigurationProperties(prefix = "family")
public class Family {
- 校验父亲的年龄,必须大于21岁
public class Father {
private String name;
@Min(21)
private Integer age;
}
- 校验familyName,必须不能为空
@NotEmpty
private String familyName;
这些校验规则注解是在JSR 303(java)规范中定义的,但是JSR 303只是一个规范,并没有很多比较具体的实现。目前通常都是使用hibernate-validator进行统一参数校验,hibernate-validator是对JSR 303规范的实现。
所以当你使用注解的时候,如果org.hibernate.validator.constraints包和javax.validation.constraints包同时存在某个校验注解,要import使用org.hibernate.validator.constraints包。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
在之前的Spring Boot 版本中,hibernate-validator是作为默认引入的web开发的集成package,但是在我最新使用的Spring Boot 2.3.0.RELEASE已经不是默认引入的了,所以需要通过上面的maven坐标单独引入。
当校验失败的时候抛出异常
针对Family的属性校验,只需要写一个测试类,将Family类注入就可以
//随机端口启动
@SpringBootTest(classes = Application.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith({SpringExtension.class})
public class test
{
//注入启动的端口
@LocalServerPort
private Integer port;
@Resource
private Family family;
@Test
public void test()
{
System.out.println("启动的端口: "+port);
System.out.println(family);
}
}
如果我们修改family.father.age=18,也就是说不满足最小值是21的这样一个校验规则
其他参考例子:
- @size (min=6, max=20, message=“密码长度只能在6-20之间”)
- @pattern(regexp="[a-za-z0-9._%±]+@[a-za-z0-9.-]+.[a-za-z]{2,4}",message=“请输入正确的邮件格式”)
- @Length(min = 5, max = 20, message = “用户名长度必须位于5到20之间”)
- @Email(message = “请输入正确的邮箱”)
- @NotNull(message = “用户名称不能为空”)
- @Max(value = 100, message = “年龄不能大于100岁”)
- @Min(value= 18 ,message= “必须年满18岁!” )
- @AssertTrue(message = “bln4 must is true”)
- @AssertFalse(message = “blnf must is falase”)
- @DecimalMax(value=“100”,message=“decim最大值是100”)
- @DecimalMin(value=“100”,message=“decim最小值是100”)
- @NotNull(message = “身份证不能为空”)
- @Pattern(regexp="^(d{18,18}|d{15,15}|(d{17,17}[x|X]))$",message=“身份证格式错误”)
JSR303校验详细
附录、常用校验注解
实际上这些校验注解不仅可以校验配置属性值,也可以校验HTTP请求参数值,我们后面的章节会为大家再次介绍
官方JSR 303规范(国外网址,国内访问比较慢,需要耐心等)
加载额外配置文件的两种方式
使用@PropertySource加载自定义yml或properties文件
properties配置文件加载
family.properties
这种格式的配置文件,在之前章节的代码基础之上,使用如下的注解就可以将文件中的配置属性进行加载,非常简单!
@PropertySource(value = {"classpath:family.properties"})
public class Family {
然后配合@Value注解,进行注入即可
也可以配合@ConfigurationProperties将properties中指定前缀的值和当前类进行绑定
Spring高级之注解@PropertySource详解(超详细)
spring 官方文档明确说明不支持使用@PropertySource加载YAML配置文件,但是我们仍然有办法,跟着我继续。
- 我们新建一个配置文件family.yml,把上一节用到的YAML数据结构放里面。用来模拟第三方jar包的额外配置文件(非application配置文件)。
# 1. 一个家庭有爸爸、妈妈、孩子。
# 2. 这个家庭有一个名字(family-name)叫做“happy family”
# 3. 爸爸有名字(name)和年龄(age)两个属性
# 4. 妈妈有两个别名
# 5. 孩子除了名字(name)和年龄(age)两个属性,还有一个friends的集合
# 6. 每个friend有两个属性:hobby(爱好)和性别(sex)
family:
family-name: "happy family"
father:
name: zimug
age: 18
mother:
alias:
- lovely
- ailice
child:
name: zimug2
age: 5
friends:
- hobby: football
sex: male
- hobby: basketball
sex: female
- 笔者通过阅读代码了解到,
DefaultPropertySourceFactory
是进行配置文件加载的工厂类。 - 尽管其默认不支持读取YAML格式外部配置文件,但是我们可以通过继承
DefaultPropertySourceFactory
,然后对它的createPropertySource
进行一下改造。就可以实现YAML的“额外”配置文件加载。
public class MixPropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(@Nullable String name,
EncodedResource resource)
throws IOException {
String sourceName = name != null ? name : resource.getResource().getFilename();
if (sourceName != null
&&(sourceName.endsWith(".yml") || sourceName.endsWith(".yaml"))) {
Properties propertiesFromYaml = loadYml(resource);
//将YML配置转成Properties之后,再用PropertiesPropertySource绑定
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
} else {
return super.createPropertySource(name, resource);
}
}
//将YML格式的配置转成Properties配置
private Properties loadYml(EncodedResource resource) throws IOException{
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
}
}
- 然后基于上一节的代码,在Family类的上面加上如下注解即可。通过factory属性明确的指定使用我们自定义的MixPropertySourceFactory加载yml配置文件。
@PropertySource(value = {"classpath:family.yml"}, factory = MixPropertySourceFactory.class)
public class Family {
使用@ImportResource加载Spring的xml配置文件
在没有Spring注解的时代,spring的相关配置都是通过xml来完成的,如:beans.xml。下面的XML配置的含义是:
将com.dhy.bootlaunch.service.TestBeanService实例化并注入到Spring上下文环境中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="testBeanService" class="com.dhy.bootlaunch.service.TestBeanService"></bean>
</beans>
- 新建一个空类,com.dhy.bootlaunch.service.TestBeanService。
- 测试用例,测试Spring上下文环境中是否有testBeanService这样一个bean,有的话表示xml配置文件已经生效,成功将testBeanService实例化并注入到Spring上下文环境中:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ImportResourceTests {
@Autowired
private ConfigurableApplicationContext ioc;
@Test
public void testHelloService() {
//测试Spring上下文环境中是否有testBeanService这样一个bean,有的话表示xml配置文件生效
boolean testBeanService= ioc.containsBean("testBeanService");
System.out.println(testBeanService);
}
}
因为还没使用@ImportResource
加载beans.xml,此时执行测试用例,输出false表示beans.xml配置文件并未加载,所以没有testBeanService的存在
在spring boot应用入口启动类上加@ImportResource(locations = {"classpath:beans.xml"})
,该注解用来加载Spring XML配置文件。
此时再试一下测试用例,输出:true。表示beans.xml配置文件被正确加载。
使用SpEL表达式绑定配置项
使用SpEL表达式绑定字符串集合
创建一个配置文件employee.properties,内容如下:
employee.names=james,curry,zimug,姚明
employee.type=教练,球员,经理
employee.age={one:'27', two : '35', three : '34', four: '26'}
- 上文中names和type属性分别代表雇员employee的名字和分类,是字符串类型属性
- age属性代表雇员的年龄,是一组键值对、类对象数据结构
创建一个配置类 Employee
,代码如下:
@Data
@Configuration
@PropertySource(name = "employeeProperties",<H
value = "classpath:employee.properties",
encoding = "utf-8")
public class Employee {
//使用SpEL读取employee.properties配置文件
@Value("#{'${employee.names}'.split(',')}")
private List<String> employeeNames;
}
测试用例
使用如下测试用例,将属性值绑定到Employee类对象上,并将其打印
//随机端口启动
@SpringBootTest(classes = Application.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith({SpringExtension.class})
public class test
{
@Resource
Employee employee;
@Test
public void valueBindTests2() throws Exception {
System.out.println(employee.toString());
}
}
上面的例子中,我们使用SpEL表达式读取了employee.names属性,并将其从字符串属性,以逗号为分隔符转换为List类型。属性值注入完成之后,employeeNames=[james, curry, zimug, 姚明]
SpEL结合@Value注解读取配置文件属性–更多示例
- 假如我们需要获取第一位(数组下标从0开始)雇员的姓名,可以使用如下的SpEL表达式:
@Value ("#{'${employee.names}'.split(',')[0]}")
private String firstEmployeeName;
属性值注入完成之后,firstEmployeeName=‘’james‘’
- 我们还可以使用@Value注解将键值对、类对象的数据结构转换为java的Map数据类型
@Value ("#{${employee.age}}")
private Map<String, Integer> employeeAge;
属性值注入完成之后,employeeAge={one=27, two=35, three=34, four=26}
- 假如我们需要根据Map的Key获取Value属性值,可以使用如下的SpEL表达式:
@Value ("#{${employee.age}.two}")
// @Value ("#{${employee.age}['two']}") //这样写也可以
private String employeeAgeTwo;
属性值注入完成之后,employeeAgeTwo=35
- 如果我们不确定,Map中的某个key是否存在,可以使用如下的SpEL表达式。如果key存在就获取对应的value,如果不存在就获得默认值31
@Value ("#{${employee.age}['five'] ?: 31}")
private Integer ageWithDefaultValue;
属性值注入完成之后,ageWithDefaultValue=31
SpEL结合 @Value注解读取系统环境变量
还可以使用SpEL表达式读取系统环境变量,示例如下,获取JAVA_HOME目录:
@Value ("#{systemProperties['java.home']}")
private String javaHome;
同理,可以获取系统用户工作目录
@Value ("#{systemProperties['user.dir']}")
private String userDir;
当然,除了以上在Spring Boot中使用SpEL的常用用法,SpEL还可以完成算术运算、逻辑运算、正则匹配运算、条件运算等功能
读取properties文件中文乱码问题的解决
File->settings->File Encoding->图所示选项及勾选
使用PropertySource注解时指定encoding
profile不同环境使用不同配置
配置文件规划
我们开发的服务通常会部署在不同的环境中,例如开发环境、测试环境,生产环境等,而不同环境需要不同的配置。最典型的场景就是在不同的环境下需要连接不同的数据库,需要使用不同的数据库配置。我们期待实现的配置效果是:
- 减少配置修改次数
- 方便环境配置切换
Spring Boot 默认的配置文件是 application.properties(或yml)。那么如何实现不同的环境使用不同的配置文件呢?一个比较好的实践是为不同的环境定义不同的配置文件,如下所示:
全局配置文件:application.yml 开发环境配置文件:application-dev.yml 测试环境配置文件:application-test.yml 生产环境配置文件:application-prod.yml
切换环境的方式
1. 通过配置application.yml
application.yml是默认使用的配置文件,在其中通过spring.profiles.active设置使用哪一个配置文件,下面代码表示使用application-prod.yml配置,如果application-prod.yml和application.yml配置了相同的配置,比如都配置了运行端口,那application-prod.yml的优先级更高
#需要使用的配置文件
spring:
profiles:
active: prod
2. VM options、Program arguments、Active Profile
VM options设置启动参数 -Dspring.profiles.active=prod
Program arguments设置 --spring.profiles.active=prod
Active Profile 设置 prod
这三个参数不要一起设置,会引起冲突,选一种即可,如下图
3.命令行方式
将项目打成jar包,在jar包的目录下打开命令行,使用如下命令启动:
java -jar spring-boot-profile.jar --spring.profiles.active=prod
关于 Spring Profiles 更多信息可以参见:Spring Profiles。
https://www.baeldung.com/spring-profiles
配置及配置文件的加载优先级
全局配置文件加载优先级
spring boot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件.数值越小的标号优先级越高。
- file:./config/ (当前项目路径config目录下);
- file:./ (当前项目路径下);
- classpath:/config/ (类路径config目录下);
- classpath:/ (类路径下).
以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级配置内容会覆盖低优先级配置内容。
SpringBoot会从这四个位置全部加载主配置文件,如果高优先级中配置文件属性与低优先级配置文件不冲突的属性,则会共同存在—互补配置。假如我们在上面的四个配置文件分别设置server.port=6666、7777、8888、9999。然后启动应用,最终的启动端口为6666,因为file:./config/
(当前项目路径config目录下配置文件)优先级是最高的。
自定义改变全局配置文件的加载位置:(优先级最高)
我们也可以通过配置spring.config.location来改变默认配置。
java -jar ./boot-launch-1.0.jar --spring.config.location=D:/application.yml
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置。
配置加载优先级
SpringBoot也可以从以下位置加载配置:优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置
。
- 命令行参数
- 来自java:comp/env的JNDI属性
- Java系统属性(System.getProperties())
- 操作系统环境变量
- RandomValuePropertySource配置的random.*属性值
- jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
- jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
- jar包外部的application.properties或application.yml(不带spring.profile)配置文件
- jar包内部的application.properties或application.yml(不带spring.profile)配置文件
- @Configuration注解类上的@PropertySource
- 通过SpringApplication.setDefaultProperties指定的默认属性
其实大家关于配置的优先级不用特别的去记忆。用到的时候查一下、体验一下、一般来说:特殊指定配置(命令行、环境变量)大于通用配置、外部配置优先级高于内部配置、局部环境配置(带profile)大于全局普适性配置
配置文件敏感字段加密
Jasypt是什么
Jasypt是一个Java库,允许开发人员以很简单的方式添加基本加密功能,而无需深入研究加密原理。利用它可以实现高安全性的,基于标准的加密技术,无论是单向和双向加密。加密密码,文本,数字,二进制文件。
- 高安全性的,基于标准的加密技术,无论是单向和双向加密。加密密码,文本,数字,二进制文件…
- 集成Hibernate的。
- 可集成到Spring应用程序中,与Spring Security集成。
- 集成的能力,用于加密的应用程序(即数据源)的配置。
- 特定功能的高性能加密的multi-processor/multi-core系统。
- 与任何JCE(Java Cryptography Extension)提供者使用开放的API
说了这么多,我们spring boot 配置管理到底用Jasypt做什么?
- 出于安全考量,使用“密钥”加密敏感字符串(如数据库密码),并将加密后的字符串保存到配置文件中。
- spring boot集成Jasypt后实现加密字符串的自动解密配置值,不需要人为参与。当然spring boot需要密钥才能进行解密。
- “密钥”与配置文件分开存放,分开使用,从而保证应用配置的安全性
使用bat脚本生成加密串
为了方便,简单编写了一个bat脚本方便使用。
@echo off
set/p input=待加密的明文字符串:
set/p password=加密密钥(盐值):
echo 加密中......
java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI ^
input=%input% password=%password% ^
algorithm=PBEWithMD5AndDES
pause
- 使用jasypt-1.9.2.jar中的org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI类进行加密
- input参数是待加密的字符串,password参数是加密的密钥(盐值)
- 使用PBEWithMD5AndDES算法进行加密
注意:jasypt-1.9.2.jar 文件需要和bat脚本放在相同目录下。此包可直接在示例项目中直接下载。
使用示例,双击上面的bat脚本文件,输入待加密内容和密钥,得到加密结果:
注意:相同的盐值(密钥),每次加密的结果是不同的。
Jasypt与spring boot整合
首先引入Jasypt的maven坐标
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>1.18</version>
</dependency>
在properties或yml文件中需要对明文进行加密的地方使用ENC()包裹,如原值:“happy family”,加密后使用ENC(密文)
替换。程序中像往常一样使用@Value("${}")
获取该配置即可,获取的是解密之后的明文值。
文本被加密之后,我们需要告知Spring Boot该如何解密,因为Spring Boot要读取该配置的明文内容。在application.properties或yml文件中,做如下配置:
# 设置盐值(加密解密密钥),我们配置在这里只是为了测试方便
# 生产环境中,切记不要这样直接进行设置,可通过环境变量、命令行等形式进行设置。下文会讲
jasypt:
encryptor:
password: 123456
“密钥”与配置文件分开存放
本身加解密过程都是通过盐值
进行处理的,所以正常情况下盐值
和加密串
是分开存储的。出于安全考量,盐值
应该放在系统属性
、命令行
或是环境变量
来使用,而不是放在同一个配置文件里面。
命令行存储方式示例
java -jar xxx.jar --jasypt.encryptor.password=xxx &;
环境变量存储方式示例
设置环境变量(linux):
# 打开/etc/profile文件
vim /etc/profile
# 文件末尾插入
export JASYPT_PASSWORD = xxxx
启动命令:
java -jar xxx.jar --jasypt.encryptor.password=${JASYPT_PASSWORD} &;
这样真的安全么?
有的同学会问这样的问题:如果的linux主机被攻陷了怎么办,黑客不就知道了密钥?
对于这个问题:我只能这么说,如果你的应用从内部被攻陷,在这个世界上没有一种加密方法是绝对安全的。这种加密方法只能做到:防君子不防小人。大家可能都听说过,某著名互联网公司将明文数据库密码上传到了github上面,导致用户信息被泄露的问题。这种加密方式,无非是将密钥与加密结果分开存放,减少个人疏忽导致的意外,增加破解难度。
如果密钥被从内部渗透暴露了,任何加密都是不安全的。就像你的组织内部有离心离德的人,无论你如何加密都不安全,你需要做的是把他找出来干掉,或者防范他加入你的组织!
相关文章
- CSS 奇思妙想边框动画
- CSS 奇技淫巧:动态高度过渡动画
- 一行 CSS 代码的魅力
- 使用纯 CSS 实现滚动阴影效果
- CSS 技巧一则 -- 不定宽溢出文本适配滚动
- CSS 故障艺术
- 深入理解 CSS(Cascading Style Sheets)中的层叠(Cascading)
- 巧用 CSS 实现酷炫的充电动画
- CSS 阴影动画优化技巧一则
- Web 字体 font-family 再探秘
- 你所不知道的 CSS 负值技巧与细节
- CSS 火焰?不在话下
- 不可思议的纯 CSS 实现鼠标跟随效果
- 不可思议的纯 CSS 滚动进度条效果
- 你所不知道的 CSS 阴影技巧与细节
- 滚动视差?CSS 不在话下
- 不可思议的纯CSS导航栏下划线跟随效果
- 两行 CSS 代码实现图片任意颜色赋色技术
- 你所不知道的 CSS 滤镜技巧与细节
- 你所不知道的 CSS 动画技巧与细节