springBoot配置文件加载原理探究
2017-10-24 22:32
871 查看
尽管用springBoot做开发已经有很长一段时间了,在开发时一般都是直接将application.properties或application.yml,放在开发环境的resources下的,运行起来感觉也没什么问题。
但是由于项目最终都是要通过打包,最终打包为一个jar包运行的。但如果一个项目由于环境不同需要对配置文件修改时,直接将在IDE中修改配置文件再重新打成一个JAR包很耗费时间。
最终通过搜索,得到一个理想的配置文件设置方式。可以在打好的将要运行的springBoot的jar包同级目录下放置上配置文件,或在其同级目录下新建一个config目录,将配置文件放在config目录中就可以了。试了一下,确实可以,感觉挺高级的,非常棒!
对于一向充满好奇心的我来说,对于springBoot它能这样做的原理充满了兴趣,决定通过ide的debug跟踪下源码。
软件环境:
springBoot版本:1.5.4.RELEASE
run方法会调用到这里:
new SpringApplication(sources)方法中有调用initialize(sources)这个初始化方法
其initialize方法代码为:
其中重点关注此方法:setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))
在这个方法中,会初始化一些监听器,主要看下这个方法
它会调用此方法:
在此方法中会通过SpringFactoriesLoader.loadFactoryNames方法获得出一串names的集合,然后再通过createSpringFactoriesInstances方法将names实例化出来
其SpringFactoriesLoader.loadFactoryNames方法为:
其中此条语句:
会通过classLoader在jar内获取出FACTORIES_RESOURCE_LOCATION的资源。
其FACTORIES_RESOURCE_LOCATION的值可以通过源码找到这句
那么也就是springBoot在初始化的时候会加载所有依赖包的META-INF/spring.factories文件
为此,为了验证都能获取出哪些具体的spring.factories配置文件,我在这个springBoot项目的测试类中写了方法
其输出结果为:
其中前面的/D:/program%20files/maven_repo字段为我电脑本地maven仓库的路径
关注下这条输出记录:
urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/boot/spring-boot/1.5.4.RELEASE/spring-boot-1.5.4.RELEASE.jar!/META-INF/spring.factories
这个路径为springboot这个jar包下的spring.facotires文件
随后,代码又执行了如下语句:
先把此配置文件加载为properties,再获取出变量factoryClassName属性的值。通过debug或通过入参可以获取到factoryClassName为ApplicationListener.class这个类的名称,即org.springframework.context.ApplicationListener
获得了以上信息后,我们便可以打开对应的JAR包,找到对应的配置文件下的org.springframework.context.ApplicationListener键,查看都有哪些值(spring-boot-1.5.4.RELEASE.jar!/META-INF/spring.factories中的org.springframework.context.ApplicationListener的值)
通过打开JAR包,在此配置文件中发现了如下内容
上面的代码result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)))就将这些listener添加进了result集合中,最终返回给了它上一级的调用方法names
从代码可以看出,上面的代码将获取出来的listener全通过反射进行了实例化,最终回到了初始化方法,通过setListeners设置到了SpringApplication的类中
其中有如下两行代码:
starting方法的代码为:
通过代码可知stating方法就是通过遍历listeners,来依次触发listener的starting方法。最终会执行到这里:
上面的this.getApplicationListeners(event, type)会获得由上文初始化的那10多个集合,然后再通过迭代器进行遍历listener。每遍历到一个listener,就从线程器中开启一个线程,去执行这个listener
最终都会触发listener的onApplicationEvent方法。
这里只跟踪下ConfigFileApplicationListener这个监听器。
当触发到ConfigFileApplicationListener的onApplicationEvent时,会执行如下的代码
由debu跟踪,会发现初始化时运行的是onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event)这个方法
随后进入:
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication()方法为:
在addPropertySources方法中,发现了如下关键代码:
后面的new Loader(environment,resourceLoader).load()方法代码为:
上面首先初始化一个profiles队列,其队列为一个lifo队列(lastInFirstOut后进先出),代码为:
随后判断下当前环境中是否有设profile,没有的话,就使用默认的profile,在profiles中加入一个名为default的profile.随后又在profiles中加入了一个null,对于为什么要加入一个null,代码里也有相应的注释说明。
大致意思是说放一个null值在profiles队列的末尾,由于队列是lifo类型的,所以null值就会最先出队,先将默认配置给初始化。当其他激活的profile出队的时候,就会重载默认的配置。
而后关注这个方法中的这段代码:
在这里就是配置文件体现加载顺序的主要代码
String location : getSearchLocations()
在getSearchLocations代码中,在没有设置其他配置文件的情况下,就会在配置文件的路径中加入如下地址
及DEFAULT_SEARCH_LOCATIONS的值,而其值在此类的头部也可以找到它的定义:
这里写的先后顺序是classpath:/,classpath:/config/,file:./,file:./config/,但上面有注释说明,这个顺序是通过由后到前的顺序来进行选择的。
通过asResolvedSet这个方法,也可以得证:
Collections.reverse(list);
但是由于项目最终都是要通过打包,最终打包为一个jar包运行的。但如果一个项目由于环境不同需要对配置文件修改时,直接将在IDE中修改配置文件再重新打成一个JAR包很耗费时间。
最终通过搜索,得到一个理想的配置文件设置方式。可以在打好的将要运行的springBoot的jar包同级目录下放置上配置文件,或在其同级目录下新建一个config目录,将配置文件放在config目录中就可以了。试了一下,确实可以,感觉挺高级的,非常棒!
对于一向充满好奇心的我来说,对于springBoot它能这样做的原理充满了兴趣,决定通过ide的debug跟踪下源码。
软件环境:
springBoot版本:1.5.4.RELEASE
springBoot初始化listenner
从SpringApplication.run()方法开始打debug开始跟踪run方法会调用到这里:
/** * Static helper that can be used to run a {@link SpringApplication} from the * specified sources using default settings and user supplied arguments. * @param sources the sources to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */ public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); }
new SpringApplication(sources)方法中有调用initialize(sources)这个初始化方法
其initialize方法代码为:
@SuppressWarnings({ "unchecked", "rawtypes" }) private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
其中重点关注此方法:setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))
在这个方法中,会初始化一些监听器,主要看下这个方法
它会调用此方法:
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
在此方法中会通过SpringFactoriesLoader.loadFactoryNames方法获得出一串names的集合,然后再通过createSpringFactoriesInstances方法将names实例化出来
其SpringFactoriesLoader.loadFactoryNames方法为:
/** * Load the fully qualified class names of factory implementations of the * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given * class loader. * @param factoryClass the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading resources; can be * {@code null} to use the default * @see #loadFactories * @throws IllegalArgumentException if an error occurs while loading factory names */ public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
其中此条语句:
Enumeration<URL> urls=(classLoader!=null?classLoader.getResources(FACTORIES_RESOURCE_LOCATION):ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
会通过classLoader在jar内获取出FACTORIES_RESOURCE_LOCATION的资源。
其FACTORIES_RESOURCE_LOCATION的值可以通过源码找到这句
/** * The location to look for factories. * <p>Can be present in multiple JAR files. */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
那么也就是springBoot在初始化的时候会加载所有依赖包的META-INF/spring.factories文件
为此,为了验证都能获取出哪些具体的spring.factories配置文件,我在这个springBoot项目的测试类中写了方法
public static void main(String[] args) { IndexServiceApplicationTests indexServiceApplicationTests = new IndexServiceApplicationTests(); try { Enumeration<URL> urls = indexServiceApplicationTests.getClass().getClassLoader().getResources("META-INF/spring.factories"); System.out.println("urls:" + urls); while(urls.hasMoreElements()){ URL url = urls.nextElement(); System.out.println("urlItem:"+url); } } catch (Exception e) { e.printStackTrace(); } }
其输出结果为:
urls:sun.misc.CompoundEnumeration@f2a0b8e urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/cloud/spring-cloud-context/1.2.2.RELEASE/spring-cloud-context-1.2.2.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/cloud/spring-cloud-commons/1.2.2.RELEASE/spring-cloud-commons-1.2.2.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/cloud/spring-cloud-netflix-eureka-server/1.3.1.RELEASE/spring-cloud-netflix-eureka-server-1.3.1.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/cloud/spring-cloud-netflix-core/1.3.1.RELEASE/spring-cloud-netflix-core-1.3.1.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/cloud/spring-cloud-netflix-eureka-client/1.3.1.RELEASE/spring-cloud-netflix-eureka-client-1.3.1.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/boot/spring-boot-test/1.5.4.RELEASE/spring-boot-test-1.5.4.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/boot/spring-boot/1.5.4.RELEASE/spring-boot-1.5.4.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/boot/spring-boot-test-autoconfigure/1.5.4.RELEASE/spring-boot-test-autoconfigure-1.5.4.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/boot/spring-boot-autoconfigure/1.5.4.RELEASE/spring-boot-autoconfigure-1.5.4.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/spring-test/4.3.9.RELEASE/spring-test-4.3.9.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/spring-beans/4.3.9.RELEASE/spring-beans-4.3.9.RELEASE.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar!/META-INF/spring.factories urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/boot/spring-boot-actuator/1.5.4.RELEASE/spring-boot-actuator-1.5.4.RELEASE.jar!/META-INF/spring.factories
其中前面的/D:/program%20files/maven_repo字段为我电脑本地maven仓库的路径
关注下这条输出记录:
urlItem:jar:file:/D:/program%20files/maven_repo/org/springframework/boot/spring-boot/1.5.4.RELEASE/spring-boot-1.5.4.RELEASE.jar!/META-INF/spring.factories
这个路径为springboot这个jar包下的spring.facotires文件
随后,代码又执行了如下语句:
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
先把此配置文件加载为properties,再获取出变量factoryClassName属性的值。通过debug或通过入参可以获取到factoryClassName为ApplicationListener.class这个类的名称,即org.springframework.context.ApplicationListener
获得了以上信息后,我们便可以打开对应的JAR包,找到对应的配置文件下的org.springframework.context.ApplicationListener键,查看都有哪些值(spring-boot-1.5.4.RELEASE.jar!/META-INF/spring.factories中的org.springframework.context.ApplicationListener的值)
通过打开JAR包,在此配置文件中发现了如下内容
# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\ org.springframework.boot.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.logging.LoggingApplicationListener
上面的代码result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)))就将这些listener添加进了result集合中,最终返回给了它上一级的调用方法names
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
从代码可以看出,上面的代码将获取出来的listener全通过反射进行了实例化,最终回到了初始化方法,通过setListeners设置到了SpringApplication的类中
/** * Sets the {@link ApplicationListener}s that will be applied to the SpringApplication * and registered with the {@link ApplicationContext}. * @param listeners the listeners to set */ public void setListeners(Collection<? extends ApplicationListener<?>> listeners) { this.listeners = new ArrayList<ApplicationListener<?>>(); this.listeners.addAll(listeners); }
ConfigFileApplicationListener执行过程
通过上面,可以得知在springApplication初始化的时候会加载ConfigFileApplicationListener这个类,那么它是在什么时候调用了这个类呢?仍然是通过源码,探究Application的run方法/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); analyzers = new FailureAnalyzers(context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } }
其中有如下两行代码:
SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting();
starting方法的代码为:
public void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } }
通过代码可知stating方法就是通过遍历listeners,来依次触发listener的starting方法。最终会执行到这里:
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) { ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event); Iterator var4 = this.getApplicationListeners(event, type).iterator(); while(var4.hasNext()) { final ApplicationListener<?> listener = (ApplicationListener)var4.next(); Executor executor = this.getTaskExecutor(); if (executor != null) { executor.execute(new Runnable() { public void run() { SimpleApplicationEventMulticaster.this.invokeListener(listener, event); } }); } else { this.invokeListener(listener, event); } } }
上面的this.getApplicationListeners(event, type)会获得由上文初始化的那10多个集合,然后再通过迭代器进行遍历listener。每遍历到一个listener,就从线程器中开启一个线程,去执行这个listener
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) { ErrorHandler errorHandler = this.getErrorHandler(); if (errorHandler != null) { try { listener.onApplicationEvent(event); } catch (Throwable var7) { errorHandler.handleError(var7); } } else { try { listener.onApplicationEvent(event); } catch (ClassCastException var8) { String msg = var8.getMessage(); if (msg != null && !msg.startsWith(event.getClass().getName())) { throw var8; } Log logger = LogFactory.getLog(this.getClass()); if (logger.isDebugEnabled()) { logger.debug("Non-matching event type for listener: " + listener, var8); } } } } }
最终都会触发listener的onApplicationEvent方法。
这里只跟踪下ConfigFileApplicationListener这个监听器。
当触发到ConfigFileApplicationListener的onApplicationEvent时,会执行如下的代码
public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }
由debu跟踪,会发现初始化时运行的是onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event)这个方法
随后进入:
private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } }
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication()方法为:
@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); configureIgnoreBeanInfo(environment); bindToSpringApplication(environment, application); }
在addPropertySources方法中,发现了如下关键代码:
/** * Add config file property sources to the specified environment. * @param environment the environment to add source to * @param resourceLoader the resource loader * @see #addPostProcessors(ConfigurableApplicationContext) */ protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); new Loader(environment, resourceLoader).load(); }
后面的new Loader(environment,resourceLoader).load()方法代码为:
public void load() { this.propertiesLoader = new PropertySourcesLoader(); this.activatedProfiles = false; this.profiles = Collections.asLifoQueue(new LinkedList<Profile>()); this.processedProfiles = new LinkedList<Profile>(); // Pre-existing active profiles set via Environment.setActiveProfiles() // are additional profiles and config files are allowed to add more if // they want to, so don't call addActiveProfiles() here. Set<Profile> initialActiveProfiles = initializeActiveProfiles(); this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles)); if (this.profiles.isEmpty()) { for (String defaultProfileName : this.environment.getDefaultProfiles()) { Profile defaultProfile = new Profile(defaultProfileName, true); if (!this.profiles.contains(defaultProfile)) { this.profiles.add(defaultProfile); } } } // The default profile for these purposes is represented as null. We add it // last so that it is first out of the queue (active profiles will then // override any settings in the defaults when the list is reversed later). this.profiles.add(null); while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); for (String location : getSearchLocations()) { if (!location.endsWith("/")) { // location is a filename already, so don't search for more // filenames load(location, null, profile); } else { for (String name : getSearchNames()) { load(location, name, profile); } } } this.processedProfiles.add(profile); } addConfigurationProperties(this.propertiesLoader.getPropertySources()); }
上面首先初始化一个profiles队列,其队列为一个lifo队列(lastInFirstOut后进先出),代码为:
this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
随后判断下当前环境中是否有设profile,没有的话,就使用默认的profile,在profiles中加入一个名为default的profile.随后又在profiles中加入了一个null,对于为什么要加入一个null,代码里也有相应的注释说明。
// The default profile for these purposes is represented as null. We add it // last so that it is first out of the queue (active profiles will then // override any settings in the defaults when the list is reversed later). this.profiles.add(null);
大致意思是说放一个null值在profiles队列的末尾,由于队列是lifo类型的,所以null值就会最先出队,先将默认配置给初始化。当其他激活的profile出队的时候,就会重载默认的配置。
而后关注这个方法中的这段代码:
while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); for (String location : getSearchLocations()) { if (!location.endsWith("/")) { // location is a filename already, so don't search for more // filenames load(location, null, profile); } else { for (String name : getSearchNames()) { load(location, name, profile); } } } this.processedProfiles.add(profile); }
在这里就是配置文件体现加载顺序的主要代码
String location : getSearchLocations()
在getSearchLocations代码中,在没有设置其他配置文件的情况下,就会在配置文件的路径中加入如下地址
locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
及DEFAULT_SEARCH_LOCATIONS的值,而其值在此类的头部也可以找到它的定义:
// Note the order is from least to most specific (last one wins) private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
这里写的先后顺序是classpath:/,classpath:/config/,file:./,file:./config/,但上面有注释说明,这个顺序是通过由后到前的顺序来进行选择的。
通过asResolvedSet这个方法,也可以得证:
private Set<String> asResolvedSet(String value, String fallback) { List<String> list = Arrays.asList(StringUtils.trimArrayElements( StringUtils.commaDelimitedListToStringArray(value != null ? this.environment.resolvePlaceholders(value) : fallback))); Collections.reverse(list); return new LinkedHashSet<String>(list); }
Collections.reverse(list);
相关文章推荐
- spring boot 加载本地静态资源文件路径配置
- spring-boot 加载本地静态资源文件路径配置
- springboot配置文件加载不到的问题
- spring-boot 加载本地静态资源文件路径配置
- spring boot启动时加载外部配置文件的方法
- Springboot jar包外指定配置文件及原理
- spring-boot 加载本地静态资源文件路径配置
- Springboot jar包外指定配置文件及原理
- spring boot 中手动加载配置文件
- 【Spring】Spring的IOC(控制反转)/DI(依赖注入)原理(三):Spring启动加载配置文件源码分析
- 使用SpringBoot加载配置文件
- Spring Boot 系列(2) 配置文件的加载
- (二)Spring-boot 配置文件加载顺序
- springboot 修改默认加载的配置文件不为application.properties
- 解决eclipse创建spring boot项目加载不到application.properties配置文件的问题
- Spring Boot 配置文件加载顺序
- Spring Boot加载配置文件
- 详解Spring Boot加载properties和yml配置文件
- Spring Boot 01 加载配置文件和获取key/value值
- springboot配置文件加载不到的问题