SpringBoot 2.0 系列004 --启动实战之配置文件



配置文件



配置文件加载流程

很多文档包括官方文档说SB的默认配置文件是application开头的文件,那么是为什么呢?

  • 我们先看下流程图
  •  

    spring启动时指定static目录_java

由上述流程我们发现,在执行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启动时指定static目录_spring_02

  • 注入 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目录下的文件,如下

spring启动时指定static目录_spring启动时指定static目录_03

  • 第一步 在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/