在前面的课程中,咱们给你们分享过SpringBoot精妙的启动配置,主要阐述的是spring的IoC容器在SpringBoot中的加载过程,并与传统项目中Spring的IoC容器加载过程进行了一个对比.咱们在开发的过程当中,除了IoC容器的配置以外,固然还有许多其余的配置,诸如数据库的连接信息,端口,以及项目的内部使用的一些个性化信息等.那SpringBoot是如何管理这些配置呢?我今天呢,就从如下这三个方面来给你们分享一下SpringBoot是如何管理配置信息的.html
配置文件和属性获取前端
配置文件的名字、目录和优先级java
传统的properties与YAMLmysql
1.1 传统配置文件的值获取与SpringBoot中的值获取web
在传统的项目里,咱们的配置信息通常都写在配置文件中,而后关于spring须要的信息,咱们就在spring的xml文件里引用,大略以下所示:spring
classpath下的config里建立一个jdbc.propertiessql
jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true jdbc.username=develop jdbc.password=&dT$BvYdOlH4*m9G
而后在咱们的application.xml里引入咱们须要的这个数据源:数据库
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:property-placeholder location="classpath:config/jdbc.properties" /> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> ... ...其余配置 </beans>
固然除了spring的数据源信息以外,咱们每每也会有一些其余的信息,好比项目返回给前端的一些报错信息等,此时咱们一般的作法是使用java的原生方法去加载。如如下所示:apache
在classpath的config下有个webreslt.properties缓存
0=SUCCESS 100000=参数有误 100001=AppKey不能为空 100002=Signature不能为空 100003=参数列表(paramMap)不能为空 100004=secret不能包含在参数列表中 100005=参数签名不合法 100006=手机号不能为空 100007=验证码不能为空 100008=邮件内容不能为空 100009=收件人不能为空 100010=邮件主题不能为空 200000=应用无权限 200001=应用未注册 300000=Api异常 300001=短信发送失败 300002=短信验证失败 300003=邮件发送失败 300004=短信发送超过最大条数限制
获取值的方法以下:
package com.ailu.paas.common.utils; import org.apache.commons.lang3.StringUtils; import java.util.Locale; import java.util.ResourceBundle; public class PropertyFileReader { public static String getItem(String key) { return getItem(key, ""); } public static String getItem(String key, String defaultValue) { ResourceBundle rb = ResourceBundle.getBundle("config/webresult"); String value = ""; try { value = new String(rb.getString(key).getBytes("ISO-8859-1"), "UTF-8"); } catch (Exception e) { e.printStackTrace(); } if (StringUtils.isEmpty(value)) { value = defaultValue; } return value.trim(); } }
而后在其余的地方,咱们就能够直接使用如下这样的方式去获取属性文件中的值了.
String value=PropertyFileReader.getItem(key);
而在SpringBoot中呢,咱们则可使用@Component+@Value这两个组合,来快速的读取配置文件中的值,
仍然是在classpath下,咱们在配置文件里写上以下配置:
name="Lianmengtu"
而后咱们建立一个java类,并加上@Component和@Value,以下所示:
package top.lianmengtu.testprofile.common; import lombok.Getter; import lombok.Setter; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component @Getter @Setter public class TestProperty { @Value("${name}") private String name; }
其中@Component是为了把TestProperty做为组件放入到Spring的IoC容器中,而@Value("${name}")则是为了从配置文件中取值,${name}中的name便是配置文件中的key.而后咱们就能够在其余地方经过注入直接使用了:
@Autowired private TestProperty testProperty; public void test(){ System.out.println(testProperty.getName()); }
1.2 随机值的绑定
在某些场景下,咱们可能须要在项目的配置中添加一些随机值,而且这些值在咱们项目启动后就自动的初始化,而SpringBoot就考虑到了这种状况,因而给咱们准备了一些工具,方便咱们的使用.使用状况以下:
# 随机字符串 secret=${random.value} #随机数 setup=${random.int} #0-10之间的随机数 range-int=${random.int[0,10]} #生成uuid uuid=${random.uuid}
获取方式和其余的属性相同,以下所示:
package top.lianmengtu.testprofile.common; import lombok.Getter; import lombok.Setter; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component @Getter @Setter public class TestProperty { @Value("${secret}") private String secret; @Value("${setup}") private Integer setup; @Value("${range-int}") private Integer rangeInt; @Value("${uuid}") private String uuid; }
1.3 变量的引用与占位符
有些时候咱们会在配置项中引用另外一项的值,固然,是以变量的形式进行引用.以下所示:
protocal=http:// domain=${protocal}ask.lianmengtu.top
这里咱们看到,在springBoot中使用的变量占位符是${key}.这时候就有一个问题,由于咱们如今大多数的开发环境都是maven,咱们都知道maven也是支持变量占位的,咱们能够在打包不一样环境的时候能够激活不一样的profile,而后对变量值进行替换,而咱们往常在使用Maven的时候,用的变量占位符正好是${key},那此时,咱们该怎么办呢?SpringBoot是否是不支持Maven的变量占位呢?咱们必需要二选其一吗?
固然不是.SpringBoot也考虑到了这个问题,所以给maven占位提供了另一个符号即@key@,以下所示:
protocal=http:// domain=${protocal}ask.lianmengtu.top username=@username@ password=@password@
而后咱们就能够在咱们的pom里这么写了,而当咱们激活某一个profile的时候,相应的maven变量就会被替换了
<profiles> <profile> <id>dev</id> <properties> <username>dev</username> <password>dev123</password> </properties> </profile> <profile> <id>test</id> <properties> <username>test</username> <password>test123</password> </properties> </profile> </profiles>
SpringBoot是除了支持properties这种方式以外,它也支持YAML这种方式,而且由于yml结构清晰,又能够继承,因此使用yml这种方式的人也愈来愈多了.而我就是这么对yml路转粉的.
2.1 yml结构化
yml的第一个好处是结构化,这与properties是明显的差异.这里放上两份一样的配置文件来个视觉对比,首先是properties
environments.dev.url= environments.dev.name=Developer Setup environments.prod.url= environments.prod.name=My Cool App my.servers[0]=dev.example.com my.servers[1]=another.example.com
对比yml
environments: dev: url: http://dev.example.com name: Developer Setup prod: url: http://another.example.com name: My Cool App my: servers: - dev.example.com - another.example.com
这里只是举个例子,因此可能这两三行你们看起来没什么感受,但要知道在实际的项目中,咱们的配置可能包含各类各样的信息,数据库的,缓存的,第三方平台的等等,那时候,若是咱们还用properties,那么看着将会很是头大.
2.2 yml的继承
在咱们使用配置文件的时候,尤为是分环境使用的时候,经常会碰到这么一个问题: 大多数的项目配置都同样,只有少数的不同,此时,若是是使用properties,那么咱们就只能每一个文件各写一份,而在yml里,咱们就不用,咱们只须要将通用的那部分写到application-common.yml里,而后少许不一样的,咱们在分环境进行描述,application-dev.yml,application-prod.yml里,这样咱们只须要激活一份文件,剩下的就会自动的进行继承和使用了,具体方式以下:
application.yml
app: name: "lianmengtu" country: "China" username: "melon" password: "melon123"
application-dev.yml:
app: username: "jacobdev" password: "dev123456"
application-prod.yml
app: username: "LMTprod" password: "prod456"
这样当咱们在启动时,激活不一样的配置时,username和password会不一样,但name和country则是从默认的yml中继承过来的.
2.3 指定配置文件的名字和地址
刚刚咱们提到过多种环境,配置文件之因此要区分环境,就是由于有些信息是须要保密的,没法公开.而SpringBoot则容许从外部,经过命令行的形式,对配置文件进行指定,固然也能够指定变量.
2.3.1 指定配置文件的名字
咱们可使用--spring.config.name=xxx 这样的参数形式指定配置文件的名字:
$ java -jar myproject.jar --spring.config.name=myproject
2.3.2 配置指定目录下的配置文件
咱们可使用--spring.config.location=xxxxx这样的参数形式来配置指定目录下的配置文件,以下文则指定了classpath下的config目录下的test.yml
java -jar myproject.jar --spring.config.location=classpath:/config/test.yml
固然从1.x转过来的人可能更喜欢指定目录,这里要特别说明一下,若是--spring.config.location是以目录结尾的,则必须加/ 以下所示:
java -jar myproject.jar --spring.config.location=classpath:/config/
2.3.3 配置的优先级
在咱们没有明确指定文件名字的时候,springBoot会按着如下顺序进行考虑
当前目录下的config目录里的application.properties
当前目录下的application.properties
classpath下的config目录下的application.properties
classpath下的application.properties
固然除了这些以外,命令行也是能够传参数的,而且命令行参数的优先级是最高的.
使用@Value()来进行属性注入有些时候会显得比较笨重,尤为是使用多个配置或者咱们的配置项是呈垂直结构化的数据时,更是这样.SpringBoot提供了另一种方法来处理这类比较复杂的数据.这就是咱们要说的@ConfigurationProperties
.
首先咱们有这样一个配置文件:
app: name: "lianmengtu" enabled: false security: username: "jacob" password: "jacob123" roles: - USER - ADMIN
咱们看到在这个配置文件里,app下有name属性,有enabled属性,其中较为特殊的是security,由于他还包含了些其余的属性,包括username,包括password,还有一个类型为String的roles集合,那么此时,咱们能够对应的写成下面这个属性类
package top.lianmengtu.testprofile.common; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.List; @ConfigurationProperties("app") public class ComplexProperty { private String name; private boolean enabled; private final Security security=new Security(); public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public Security getSecurity() { return security; } public static class Security { private String username; private String password; private List<String> roles =new ArrayList<>(Collections.singleton("USER")); public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } } }
以后,咱们能够在咱们的service层引用它:
package top.lianmengtu.testprofile.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import top.lianmengtu.testprofile.common.ComplexProperty; import java.util.List; @Service public class PropertyService { private final ComplexProperty complexProperty; @Autowired public PropertyService(ComplexProperty complexProperty){ this.complexProperty=complexProperty; } public String name(){ return complexProperty.getName(); } public boolean enabled(){ return complexProperty.isEnabled(); } public String userName(){ return complexProperty.getSecurity().getUsername(); } public String password(){ return complexProperty.getSecurity().getPassword(); } public String roles(){ StringBuffer roles=new StringBuffer(); List<String> roleArray=complexProperty.getSecurity().getRoles(); roleArray.forEach(role->{ roles.append(role).append("----"); }); return roles.toString(); } }
这里的构造函数注入,咱们也能够换成相应的@Autowried注入.为了使这个配置生效,咱们须要在Application上加上@EnableConfigurationProperties(ComplexProperty.class)
package top.lianmengtu.testprofile; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import top.lianmengtu.testprofile.common.ComplexProperty; @SpringBootApplication @EnableConfigurationProperties(ComplexProperty.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } }
刚刚举的那种类型能够说是比较复杂的一个类型了,除了那种内嵌的形式以外,若是说咱们只想获得内嵌类里面的属性或者说只有内嵌类里面有属性,则咱们能够写成如下这种形式,其余的地方都不用变.这种形式咱们称之为relaxed binding
@ConfigurationProperties("app.security") public class ComplexProperty { private String username; private String password; private List<String> roles; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } }
而且也支持map类型,配置文件以下所示,其中key能够有两种指定方式,一种是"[/key]",一种是/key:
app: name: "lianmengtu" enabled: false security: username: "jacob" password: "jacob123" roles: - USER - ADMIN work: "[/position]": "CEO" "[/company]": "bat" /address: "BeiJing"
而咱们的配置类则以下所示:
package top.lianmengtu.testprofile.common; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.List; import java.util.Map; @ConfigurationProperties("app.security") public class ComplexProperty { private String username; private String password; private List<String> roles; private Map<String,String> work; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } public Map<String, String> getWork() { return work; } public void setWork(Map<String, String> work) { this.work = work; } }
这两种方式都是Ok的,
今天的内容,其实到这里就已经结束了,在使用配置文件的过程当中,咱们还碰到了一些问题,首先,咱们在使用这种配置方式,不管是@Component+@Value仍是后来的@ConfigurationProperties这两种方式,他都是在进入spring的时候进行初始化的,这也就意味着,若是咱们没有从Spring的容器中去取咱们的属性容器的话,那么咱们的属性值是没有办法注入的,这一点但愿你们可以注意,其次,今天只是讲了主要的几种方式,还有一些像复杂类型的属性合并,以及属性验证,这些但愿你们能够研究一下,若是有不明白的,欢迎你们在论坛上提出来,咱们能够一块儿探讨.如下是@ConfigurationProperties和@Value的一些对比:
Feature | @ConfigurationProperties |
@Value |
---|---|---|
Yes |
No |
|
Yes |
No |
|
|
No |
Yes |