一般框架,启动之后都会尽快加载配置文件,springboot也不例外,下面就开始分析一下springboot加载配置文件的流程。
springboot配置的加载是从listener类开始的,还记得上一节我说listener类的调用没那么简单么,这一节就先从listener类的调用开始。
run方法中,listeners初始化的地方。
public ConfigurableApplicationContext run(String... args) { ... SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); ... } 复制代码
listener类在SpringApplication对象初始化的时候,我们已经从配置文件中获取到了,并存放在了集合里,那么这边为什么没有直接调用而是又绕了一个逻辑呢,先进入getRunListeners方法。
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); } 复制代码
getRunListeners方法中,首先获取了SpringApplicationRunListener对象,并且使用SpringApplication,和args作为的构造函数的参数。然后在使用获得的SpringApplicationRunListener对象集合作为参数,构造了SpringApplicationRunListeners对象。 我们先去看看SpringApplicationRunListener对象是啥。
getSpringFactoriesInstances这个方法大家 应该很熟了,从SpringApplication对象新建时候就一直在调用,所以我们可以直接到配置文件中看一下,获取的SpringApplicationRunListener对象到底是啥。
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } } 复制代码
在配置文件中发现了EventPublishingRunListener对象,这就是getRunListeners方法中获得到的SpringApplicationRunListener对象,构造函数很简单,我就不详细分析了。原本SpringApplication类中的listener对象,现在被封装到了EventPublishingRunListener对象中。
回过头来再看,SpringApplicationRunListener类又被封装到了SpringApplicationRunListeners对象中,这样getRunListeners方法的逻辑就执行完了。
现在看看listeners.starting()方法的调用逻辑。
public void starting() { for (SpringApplicationRunListener listener : this.listeners) { //遍历调用starting方法 listener.starting(); } } public void starting() { //这个地方封装了一个事件,大概猜一下应该是打算使用策略模式 this.initialMulticaster.multicastEvent( new ApplicationStartingEvent(this.application, this.args)); } public void multicastEvent(ApplicationEvent event) { multicastEvent(event, resolveDefaultEventType(event)); } public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); //getApplicationListeners获取了符合策略的监听器 for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } } protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { ... doInvokeListener(listener, event); ... } private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { ... listener.onApplicationEvent(event); ... } 复制代码
最后终于在doInvokeListener方法中,看到了监听器的执行。所有监听器的执行都使用的策略模式,如果想符合某些事件,在监听器的onApplicationEvent方法中配置一下即可。在这儿,我们也可以感受到spring框架设计的规范性,使用策略模式可以很方便的基于事件做相应扩展。
上面我们已经了解了listener类的启动逻辑,下面开始正式分析配置文件的加载。
public ConfigurableApplicationContext run(String... args) { ... ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); ... } private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { ... listeners.environmentPrepared(environment); ... } 复制代码
在run方法中,找到prepareEnvironment方法,进入之后,会看到监听器启动了environmentPrepared事件,所以我们就去监听器里面,找找看符合环境事件的监听器。
看名字也能看出来,就是他ConfigFileApplicationListener。找到他的onApplicationEvent方法,开始分析。
public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { //入口在这儿 onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { //虽然这边还有其他几个监听器,但是最重要的依然是他本身所以,我们还是分析他本身的postProcessEnvironment方法 postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } } 复制代码
postProcessEnvironment方法逻辑如下
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); } protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); //关键代码在这儿 new Loader(environment, resourceLoader).load(); } //先看构造函数 Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment; this.placeholdersResolver = new PropertySourcesPlaceholdersResolver( this.environment); this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(); //这个方法又见到了,话不多说,打开配置文件 this.propertySourceLoaders = SpringFactoriesLoader.loadFactories( PropertySourceLoader.class, getClass().getClassLoader()); } //这就是yml和properties配置支持的来源 org.springframework.boot.env.PropertySourceLoader=/ org.springframework.boot.env.PropertiesPropertySourceLoader,/ org.springframework.boot.env.YamlPropertySourceLoader 复制代码
Loader类在构造函数中获取了yml和properties配置文件的支持。下面开始分析load函数。
public void load() { ... //前面是关于profile的配置逻辑不复杂应该可以看懂,关键方法是load load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); ... } private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { //关注getSearchLocations方法 getSearchLocations().forEach((location) -> { boolean isFolder = location.endsWith("/"); //关注getSearchNames方法 Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES; names.forEach( (name) -> load(location, name, profile, filterFactory, consumer)); }); } 复制代码
在getSearchLocations方法中,可以看到如果没有指定地址的话,默认地址就是"classpath:/,classpath:/config/,file:./,file:./config/",如果想指定的话,启动时需要加上spring.config.location参数
在getSearchNames方法中,可以看到如果没有指定配置文件名称的话,配置文件的名字就按照application来搜索。如果想指定的话,启动时需要加上spring.config.name参数
所以继续往下看load方法
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { ... Set<String> processed = new HashSet<>(); for (PropertySourceLoader loader : this.propertySourceLoaders) { for (String fileExtension : loader.getFileExtensions()) { if (processed.add(fileExtension)) { loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer); } } } } 复制代码
在这一层的load方法中,看到了Loader类新建时,获取的yml和properties格式支持类propertySourceLoaders,查看两个类的getFileExtensions方法
public class YamlPropertySourceLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[] { "yml", "yaml" }; } public class PropertiesPropertySourceLoader implements PropertySourceLoader { private static final String XML_FILE_EXTENSION = ".xml"; @Override public String[] getFileExtensions() { return new String[] { "properties", "xml" }; } 复制代码
到了这一步,我们终于摸清了为什么默认的配置文件名字必须是application,而且可以为yml和properties格式。
最后加载的过程其实没啥好分析的了。经过了我们的一通操作,我们已经顺利的摸清了springboot默认配置加载的来源,并且了解了如果想指定配置该怎么做。