SpringBoot 2.0 系列004 --启动实战之配置文件
配置文件
配置文件加载流程
很多文档包括官方文档说SB的默认配置文件是application开头的文件,那么是为什么呢?
由上述流程我们发现,在执行SpringApplication的run方法中的prepareEnvironment子方法时,触发ConfigFileApplicationListener类中的 load方法,完成配置文件的加载
- ConfigFileApplicationListener分析
public void load() {
this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// 核心初始化方法 负责初始化 profile (spring.profiles.active)
// 如果没有则使用默认的AbstractEnvironment类中的(spring.profiles.default)default(可以是application-default名称的,也可以起不加)
initializeProfiles();
// 不为空时 循环加载 初始化了 理论上不会为空
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
// 第二步 此方法加载 文件的前缀 active 以及后缀 和路径
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
// 默认加载的
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
- 接 上边标注第二步的位置
private void load(Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
// getSearch 如果你配置了spring.config.location 路径 则使用此处路径
// 否则是用默认的 ConfigFileApplicationListener.DEFAULT_SEARCH_LOCATIONS
// 即 private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
// getSearchNames 是你配置文件的名称 ,如果配置了spring.config.name 则使用配置的这个名字
// 否则使用默认的 private static final String DEFAULT_NAMES = "application";
// 这就是默认是application这个名字的原因
Set<String> names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES);
names.forEach(
// 第三步 location是路径 name是文件名 profile则是-defalut部分或者其他-dev之类的
(name) -> load(location, name, profile, filterFactory, consumer));
});
}
- 接上边标注第三步
private void load(String location, String name, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// 这里需要注意的是propertySourceLoaders 此处是在new loader的时候初始化的,即我们流程中的addPropertySources一步
//底层使用的是META-INF/spring.factories
/** org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
*/
// 这也就是 文件后缀支持 yml,yaml,properties,xml的原因
// 没有名字的情况
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile,
filterFactory.getDocumentFilter(profile), consumer);
}
}
}
// 带后缀名的情况
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
String prefix = location + name;
fileExtension = "." + fileExtension;
// 第四步 通过后缀名方式加载
loadForFileExtension(loader, prefix, fileExtension, profile,
filterFactory, consumer);
}
}
}
- 接第四步
private void loadForFileExtension(PropertySourceLoader loader, String prefix,
String fileExtension, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
// 前边说的 -default和-dev部分
if (profile != null) {
// Try profile-specific file & profile section in profile file (gh-340)
// prefix 是路径名+文件名
// fileExtension是.yml这种
// profile 则是第一步initializeProfiles 是this.profiles注入的
String profileSpecificFile = prefix + "-" + profile + fileExtension;
// 各处执行装载 扫描不同路径下是否有对应配置文件
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// Try profile specific sections in files we've already processed
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile
+ fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// Also try the profile-specific section (if any) of the normal file
各处执行装载 扫描不同路径下是否有对应配置文件
// 第五步
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
- 接 第五步
第三步和第四步都会执行下面的方法
private void load(PropertySourceLoader loader, String location, Profile profile,
DocumentFilter filter, DocumentConsumer consumer) {
try {
Resource resource = this.resourceLoader.getResource(location);
String description = getDescription(location, resource);
if (profile != null) {
description = description + " for profile " + profile;
}
if (resource == null || !resource.exists()) {
this.logger.trace("Skipped missing config " + description);
return;
}
if (!StringUtils.hasText(
StringUtils.getFilenameExtension(resource.getFilename()))) {
this.logger.trace("Skipped empty config extension " + description);
return;
}
String name = "applicationConfig: [" + location + "]";
// 转换成docment对象
List<Document> documents = loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
this.logger.trace("Skipped unloaded config " + description);
return;
}
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
// 文档Filter
if (filter.match(document)) {
maybeActivateProfiles(document.getActiveProfiles());
addProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
// consumer对象来自 addToLoaded方法 目的是装载到PropertySources中
loaded.forEach((document) -> consumer.accept(profile, document));
// 开启debug后 打印出这句话 表示文件被加载完毕。
this.logger.debug("Loaded config file " + description);
}
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load property "
+ "source from location '" + location + "'", ex);
}
}
配置文件使用
由上述分析可知,默认的配置文件名为application(-{profile}).yml/xml/yaml/properties,且默认支持项目的resources路径
问题1 怎么使用名称不是application的文件?
根据流程分析可知,文件默认完整名称是由spring.config.name和spring.profiles.default两条属性控制。由此可知,在启动前注入此属性即可。
- 目标 ,修改默认application为ricky01 ,defalut默认bgt01
- 在resources新建如下几个文件及内容
默认端口是8010 其他是8011,8012,8013,
- 注入 spring.config.name和spring.profiles.default属性
主要有如下3种方式
/**
* 测试自定义加载文件的方式 01 通过启动设置参数
* --spring.config.name=ricky01 --spring.profiles.default=bgt01
* @param args
*/
public static void main01(String[] args) throws InterruptedException {
SpringApplication application = new SpringApplication(SpringBootApplication01.class);
application.run(args);
}
/**
* 测试自定义加载文件的方式 01 通过启动设置参数
* 设置环境变量
* @param args
*/
public static void main02(String[] args) throws InterruptedException {
System.setProperty("spring.config.name", "ricky01");
System.setProperty("spring.profiles.default", "bgt02");
SpringApplication application = new SpringApplication(SpringBootApplication01.class);
application.run(args);
}
/**
* 测试自定义加载文件的方式 01 通过启动设置参数
* 设置传入参数
* @param args
*/
public static void main(String[] args) throws InterruptedException {
args=new String[2];
args[0]="--spring.config.name=ricky01";
args[1]="--spring.profiles.default=bgt03";
SpringApplication application = new SpringApplication(SpringBootApplication01.class);
application.run(args);
}
- 结果
公用ricky01.yml中的contextpath,服务端口却是各自定义的。以下是bgt03时的启动日志
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.1.RELEASE)
2018-05-16 13:40:25.165 INFO 24984 --- [ main] com.ricky.SpringBootApplication01 : Starting SpringBootApplication01 on jsb-bgt with PID 24984 (D:\work\ideawork\SpringBootLearn\chapter04\target\classes started by zdwljs in D:\work\ideawork\SpringBootLearn)
2018-05-16 13:40:25.169 INFO 24984 --- [ main] com.ricky.SpringBootApplication01 : No active profile set, falling back to default profiles: bgt03
2018-05-16 13:40:25.263 INFO 24984 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3e92efc3: startup date [Wed May 16 13:40:25 CST 2018]; root of context hierarchy
2018-05-16 13:40:28.721 INFO 24984 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8013 (http)
2018-05-16 13:40:28.756 INFO 24984 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2018-05-16 13:40:28.757 INFO 24984 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.29
2018-05-16 13:40:28.770 INFO 24984 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [D:\Program Files\Java\jdk1.8.0_161\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;d:\work\Git\cmd;C:\Program Files (x86)\MySQL\MySQL Server 5.5\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Users\zdwljs\AppData\Local\Microsoft\WindowsApps;D:\Program Files\Java\jdk1.8.0_161\bin;;.]
2018-05-16 13:40:28.924 INFO 24984 --- [ost-startStop-1] o.a.c.c.C.[.[localhost].[/ricky01] : Initializing Spring embedded WebApplicationContext
2018-05-16 13:40:28.924 INFO 24984 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3672 ms
2018-05-16 13:40:29.149 INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2018-05-16 13:40:29.155 INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-05-16 13:40:29.155 INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-05-16 13:40:29.155 INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-05-16 13:40:29.155 INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2018-05-16 13:40:29.371 INFO 24984 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 13:40:29.783 INFO 24984 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3e92efc3: startup date [Wed May 16 13:40:25 CST 2018]; root of context hierarchy
2018-05-16 13:40:29.905 INFO 24984 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-05-16 13:40:29.907 INFO 24984 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-05-16 13:40:29.947 INFO 24984 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 13:40:29.947 INFO 24984 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 13:40:30.183 INFO 24984 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2018-05-16 13:40:30.245 INFO 24984 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8013 (http) with context path '/ricky01'
2018-05-16 13:40:30.251 INFO 24984 --- [ main] com.ricky.SpringBootApplication01 : Started SpringBootApplication01 in 5.845 seconds (JVM running for 6.639)
问题2 怎么使用外部配置文件加载 方便统一管理
比如我们放在D:\work\ricky\config目录下管理
- 目标 使用d盘config目录下的文件,如下
- 第一步 在d盘新建上述文件 默认端口是8020 bgt01端口则是8021
- 第二步 更改加载路径,如下
这里 只罗列一种方式 其他的和上面修改apllication方式一样
public static void main(String[] args) throws InterruptedException {
args=new String[3];
args[0]="--spring.config.location=file:d:/work/ricky/config/";
args[1]="--spring.config.name=ricky02";
args[2]="--spring.profiles.default=bgt01";
SpringApplication application = new SpringApplication(SpringBootApplication02.class);
application.run(args);
}
- 结果
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.1.RELEASE)
2018-05-16 14:09:21.111 INFO 19708 --- [ main] com.ricky.SpringBootApplication02 : Starting SpringBootApplication02 on jsb-bgt with PID 19708 (D:\work\ideawork\SpringBootLearn\chapter04\target\classes started by zdwljs in D:\work\ideawork\SpringBootLearn)
2018-05-16 14:09:21.118 INFO 19708 --- [ main] com.ricky.SpringBootApplication02 : No active profile set, falling back to default profiles: bgt01
2018-05-16 14:09:21.221 INFO 19708 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1ab06251: startup date [Wed May 16 14:09:21 CST 2018]; root of context hierarchy
2018-05-16 14:09:24.122 INFO 19708 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8021 (http)
2018-05-16 14:09:24.187 INFO 19708 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2018-05-16 14:09:24.187 INFO 19708 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.29
2018-05-16 14:09:24.208 INFO 19708 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [D:\Program Files\Java\jdk1.8.0_161\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;d:\work\Git\cmd;C:\Program Files (x86)\MySQL\MySQL Server 5.5\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Users\zdwljs\AppData\Local\Microsoft\WindowsApps;D:\Program Files\Java\jdk1.8.0_161\bin;;.]
2018-05-16 14:09:24.427 INFO 19708 --- [ost-startStop-1] o.a.c.c.C.[.[localhost].[/ricky02] : Initializing Spring embedded WebApplicationContext
2018-05-16 14:09:24.429 INFO 19708 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3225 ms
2018-05-16 14:09:24.748 INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2018-05-16 14:09:24.756 INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-05-16 14:09:24.757 INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-05-16 14:09:24.757 INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-05-16 14:09:24.758 INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2018-05-16 14:09:24.980 INFO 19708 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 14:09:25.424 INFO 19708 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1ab06251: startup date [Wed May 16 14:09:21 CST 2018]; root of context hierarchy
2018-05-16 14:09:25.583 INFO 19708 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-05-16 14:09:25.586 INFO 19708 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-05-16 14:09:25.672 INFO 19708 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 14:09:25.672 INFO 19708 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 14:09:25.989 INFO 19708 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2018-05-16 14:09:26.185 INFO 19708 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8021 (http) with context path '/ricky02'
2018-05-16 14:09:26.191 INFO 19708 --- [ main] com.ricky.SpringBootApplication02 : Started SpringBootApplication02 in 13.196 seconds (JVM running for 14.36)
这里实现的只是外部的,如果你还是需要在项目中则可以修改location=classpath:/XXX/