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两条属性控制。由此可知,在启动前注入此属性即可。
默认端口是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目录下管理

这里 只罗列一种方式 其他的和上面修改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/
演示项目地址,欢迎fork和star
码云:SpringBootLearn
最后