前言
首先我们都知道springboot的约定大于配置的一条约定就是classpath下指定application.properties或者yaml文件的配置文件会被加载到spring中作为配置文件
那么在springboot中它是如何加载的呢?本文就是从源码介绍application文件的加载
源码解析
从springboot启动类入手
SpringApplication.run(ProvideApplication.class, args)
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); }
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 核心方法 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 。。。。。。 }
看名字就知道是处理环境变量Environment的
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment // 核心1 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 核心2 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 核心3 ConfigurationPropertySources.attach(environment); // 核心4 listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
先看核心1,创建了environment 对象
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } }
这里打断点发现,我当前的启动方式会使用StandardEnvironment的环境,如果添加了servlet或者reactive相关的类会切换成其他的
public class StandardEnvironment extends AbstractEnvironment { public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; public StandardEnvironment() { } protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment())); } }
看父类AbstractEnvironment 的构造方法
public AbstractEnvironment() { this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources); this.customizePropertySources(this.propertySources); }
customizePropertySources回调到子类StandardEnvironment的customizePropertySources
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment())); }
可以看到创建了Environment对象,并且Environment对象有一个propertySources集合,往里面添加了两个propertySource,这两个propertySource分别对应的是
- systemProperties:应用启动时指定的参数,如java -jar -Dserver.port=8888 demo.jar设置的参数
- systemEnvironment:系统环境变量,如设置的JAVA_HOME
然后回到prepareEnvironment方法,继续看核心2 configureEnvironment方法
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } // 核心1 configurePropertySources(environment, args); // 核心2 configureProfiles(environment, args); }
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { // 核心1 sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties)); } if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { // 核心2 sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
这里又设置了两个propertySource
- defaultProperties:默认参数,是加到最后面的,优先级低
- SimpleCommandLinePropertySource:命令行参数propertySource,是加到最前面的,例如:java -jar demo.jar –server.port=8888 设置的参数
然后看核心2,configureProfiles方法
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); environment.setActiveProfiles(StringUtils.toStringArray(profiles)); }
这个方法把配置文件中指定的激活相关的环境设置到了environment中,比如设置了spring.profiles.active=dev,在后续会使用到
继续看prepareEnvironment的核心3,ConfigurationPropertySources.attach(environment)
public static void attach(Environment environment) { Assert.isInstanceOf(ConfigurableEnvironment.class, environment); MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources(); PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME); if (attached != null && attached.getSource() != sources) { sources.remove(ATTACHED_PROPERTY_SOURCE_NAME); attached = null; } if (attached == null) { // 核心 sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources))); } }
这里又添加了一个PropertySource,这个PropertySource不太关心是干嘛的,感兴趣的自己了解以下
继续看核心4,listeners.environmentPrepared
这里的listeners是在上面的run方法创建的
SpringApplicationRunListeners listeners = getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
创建SpringApplicationRunListeners ,并且通过spi找到SpringApplicationRunListener从构造方法传进去了,这里spi只能找到一个EventPublishingRunListener
void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } }
这里的listeners只有一个EventPublishingRunListener,上面通过spi创建的
public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); }
发布一个ApplicationEnvironmentPreparedEvent事件,这里里面不看了,会查找所有的事件类ApplicationListener,调用他们相应的onApplicationEvent,此时有很多个ApplicationListener,需要找到相关的监听类,这里是一个ConfigFileApplicationListener,看一下它的onApplicationEvent方法
@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { // 核心方法 onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }
专门处理ApplicationEnvironmentPreparedEvent事件的
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); // 核心1 postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } }
核心1,获得了很多EnvironmentPostProcessor,并且把当前ConfigFileApplicationListener也加进去了,ConfigFileApplicationListener同时继承了EnvironmentPostProcessor
然后循环执行所有的postProcessEnvironment方法,这里就看ConfigFileApplicationListener的postProcessEnvironment就行了
@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); }
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { // 核心1 RandomValuePropertySource.addToEnvironment(environment); new Loader(environment, resourceLoader).load(); }
public static void addToEnvironment(ConfigurableEnvironment environment) { environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME)); logger.trace("RandomValuePropertySource add to Environment"); }
这里又加了一个RandomValuePropertySource,不太关心
继续看new Loader(environment, resourceLoader).load();
void load() { FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY, (defaultProperties) -> { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); initializeProfiles(); // 核心1 while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); if (isDefaultProfile(profile)) { addProfileToEnvironment(profile.getName()); } // 核心2 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } // 核心3 load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 核心4 addLoadedPropertySources(); applyActiveProfiles(defaultProperties); }); }
核心1处取到了我们之前设置的spring.profiles.active参数,因为要拼接为application-dev.properties等文件名进行查找,核心2就是处理这种情况的
这里没有设置spring.profiles.active就直接看核心3即可
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { getSearchLocations().forEach((location) -> { boolean isDirectory = location.endsWith("/"); Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES; names.forEach((name) -> load(location, name, profile, filterFactory, consumer)); }); }
这个getSearchLocations()先看一下
可以看到这个是要查找的路径,进行遍历,这里可以看到我们的application文件不仅可以放着classpath下,也可以放着config目录下
这里断点直接找到classpath:/这个name,因为其他路径下没有配置要找的配置文件
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { 。。。。。。 Set<String> processed = new HashSet<>(); // 核心1 for (PropertySourceLoader loader : this.propertySourceLoaders) { // 核心2 for (String fileExtension : loader.getFileExtensions()) { if (processed.add(fileExtension)) { // 核心3 loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer); } } } }
核心1,这里的propertySourceLoaders,可以看到分别是处理properties和yaml文件的,这里的2个类是通过spi获取的,也可以自己扩展,比如我想扩展.json后缀的也可以,只需要使用springboot的spi机制即可,可以看这篇文章:https://blog.csdn.net/qq_31086797/article/details/107397463
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); if (profile != null) { // Try profile-specific file & profile section in profile file (gh-340) 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); }
继续看load
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter, DocumentConsumer consumer) { // 核心1 Resource[] resources = getResources(location); for (Resource resource : resources) { 。。。。。。 // 核心2 List<Document> documents = loadDocuments(loader, name, resource); if (CollectionUtils.isEmpty(documents)) { if (this.logger.isTraceEnabled()) { StringBuilder description = getDescription("Skipped unloaded config ", location, resource, profile); this.logger.trace(description); } continue; } List<Document> loaded = new ArrayList<>(); for (Document document : documents) { if (filter.match(document)) { addActiveProfiles(document.getActiveProfiles()); addIncludedProfiles(document.getIncludeProfiles()); loaded.add(document); } } Collections.reverse(loaded); if (!loaded.isEmpty()) { // 核心3 loaded.forEach((document) -> consumer.accept(profile, document)); if (this.logger.isDebugEnabled()) { StringBuilder description = getDescription("Loaded config file ", location, resource, profile); this.logger.debug(description); } } } catch (Exception ex) { StringBuilder description = getDescription("Failed to load property source from ", location, resource, profile); throw new IllegalStateException(description.toString(), ex); } } }
核心1的location等于classpath:/application.properties,也就是查找该目录下的资源,找到了才能继续往下走
核心2,loadDocuments
private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException { DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource); List<Document> documents = this.loadDocumentsCache.get(cacheKey); if (documents == null) { // 核心1 List<PropertySource<?>> loaded = loader.load(name, resource); documents = asDocuments(loaded); this.loadDocumentsCache.put(cacheKey, documents); } return documents; }
核心1处使用之前的PropertySourceLoader,这里是专门处理properties后缀的PropertySourceLoader ,调用load方法,传入资源resource,最终返回了PropertySource,并且包装为了Document对象返回
核心3处,consumer.accept(profile, document)),这个consumer是一个匿名类,是之前ConfigFileApplicationListener类load方法创建的,在看一下这个load方法
void load() { FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY, (defaultProperties) -> { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); initializeProfiles(); while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); if (isDefaultProfile(profile)) { addProfileToEnvironment(profile.getName()); } load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } // 核心代码 load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); addLoadedPropertySources(); applyActiveProfiles(defaultProperties); }); }
核心处通过addToLoaded方法传入了一个DocumentConsumer赋值
private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod, boolean checkForExisting) { return (profile, document) -> { if (checkForExisting) { for (MutablePropertySources merged : this.loaded.values()) { if (merged.contains(document.getPropertySource().getName())) { return; } } } // 核心 MutablePropertySources merged = this.loaded.computeIfAbsent(profile, (k) -> new MutablePropertySources()); addMethod.accept(merged, document.getPropertySource()); }; }
所以之前的consumer.accept(profile, document)会调用到这里的lambda表达式
这个就是把document的propertySource添加到了loaded的这个map中,key是Profile,value是MutablePropertySources
这里的逻辑走完,假如配置了参数spring.profiles.active=dev,那么这个loader中最终就会有两个值。
一个是标准的application.properties的另外一个是application-dev.properties的
这里看完了,load加载配置文件就全部完成了,继续看之前的核心4,addLoadedPropertySources()
private void addLoadedPropertySources() { MutablePropertySources destination = this.environment.getPropertySources(); List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values()); Collections.reverse(loaded); String lastAdded = null; Set<String> added = new HashSet<>(); for (MutablePropertySources sources : loaded) { for (PropertySource<?> source : sources) { if (added.add(source.getName())) { // 核心 addLoadedPropertySource(destination, lastAdded, source); lastAdded = source.getName(); } } } }
遍历所有的loaded,之前加载就是把配置文件存储到这里,然后addLoadedPropertySource
private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded, PropertySource<?> source) { if (lastAdded == null) { if (destination.contains(DEFAULT_PROPERTIES)) { destination.addBefore(DEFAULT_PROPERTIES, source); } else { destination.addLast(source); } } else { destination.addAfter(lastAdded, source); } }
这里的destination就是environment里面的MutablePropertySources,里面存储了目前所有的PropertySources,然后把现在loaded里面的PropertySource全部加到environment的PropertySources中
到这里application.properties的配置文件解析就完成了
下一篇,看看spring是如何从environment中的PropertySources取值注入到bean的@Value注解中的
转自:
https://blog.csdn.net/qq_31086797/article/details/124109200