Spring Boot 源码分析(5) -- applicationContext.properties资源文件分析
Spring Boot 源码分析(5) -- applicationContext.properties资源文件分析
在前面分析监听器时,我们提到过ConfigFileApplicationListener,这个监听器在Spring Boot中负责处理applicationContext.properties属性文件的读取。
在Spring Boot 源码分析(2)中我们分析过监听器和监听事件。在环境准备完毕时会触发ApplicationEnvironmentPreparedEvent事件,在上下文加载完成时会触发ApplicationPreparedEvent事件。
在ConfigFileApplicationListener类中,监听的入口在onApplicationEvent(ApplicationEvent event)方法。代码如下:
public void onApplicationEvent(ApplicationEvent event) { // 监听环境准备完毕事件 if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } // 监听上下文准备完毕事件 if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }
监听环境准备完毕
从/META-INF/spirng.fatories中获取EnvironmentPostProcessor实例,当然ConfigFileApplicationListener本身也实现了这个接口。然后处理EnvironmentPostProcessor的postProcessEnvironment方法。很显然这个接口是对环境做后置处理。
private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { // 从/META-INF/spirng.fatories中获取EnvironmentPostProcessor实例 List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); // ConfigFileApplicationListener本身也实现了这个接口 postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); // 环境后置处理 for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } }
我们看在ConfigFileApplicationListener中对环境的后置处理:
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { // 添加属性源 addPropertySources(environment, application.getResourceLoader()); // 配置忽略的Bean信息 configureIgnoreBeanInfo(environment); // 绑定到SpringApplication bindToSpringApplication(environment, application); }
添加属性源
首先将随机值属性源添加到环境中来,随机值属性源对象为RandomValuePropertySource。然后使用内部类Loader对环境进行加载。由于Loader类分析比较复杂,我们放到后面分析。
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { // 将随机值属性添加到环境中 RandomValuePropertySource.addToEnvironment(environment); // 加载器加载属性源 new Loader(environment, resourceLoader).load(); }
配置忽略Bean信息
可以从系统环境中设置spring.beaninfo.ignore属性来表示是否忽略Bean信息,这个属性是Boolean类型的值。
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { // 判断系统属性中是否存在spring.beaninfo.ignore if (System.getProperty( CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) { // 如果不存在,放宽限制获取前缀为spring.beaninfo.的属性 RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment, "spring.beaninfo."); // 判断是否存在ignore属性,提供默认值true Boolean ignore = resolver.getProperty("ignore", Boolean.class, Boolean.TRUE); // 设置到系统属性中去 System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString()); } }
绑定环境到SpringApplication
protected void bindToSpringApplication(ConfigurableEnvironment environment, SpringApplication application) { PropertiesConfigurationFactory<SpringApplication> binder = new PropertiesConfigurationFactory<SpringApplication>( application); binder.setTargetName("spring.main"); binder.setConversionService(this.conversionService); binder.setPropertySources(environment.getPropertySources()); try { binder.bindPropertiesToTarget(); } catch (BindException ex) { throw new IllegalStateException("Cannot bind to SpringApplication", ex); } }
内部类Loader分析
在分析Loader之前,我们先对ConfigFileApplicationListener属性域做一些介绍,便于后面理解。
- 常量定义
// 定义默认属性值 private static final String DEFAULT_PROPERTIES = "defaultProperties"; // 定义默认的文件搜索路径,优先级从低到高 private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; // 默认配置文件名称 private static final String DEFAULT_NAMES = "application"; /** * 定义profile行为的属性 */ public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; /** * 定义profile包含的属性 */ public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include"; /** * 定义配置文件名称的属性 */ public static final String CONFIG_NAME_PROPERTY = "spring.config.name"; /** * 定义配置路径的属性 */ public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; /** * 定义Order排序值 */ public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; /** * 定义应用配置的属性源名称 */ public static final String APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME = "applicationConfigurationProperties";
- 属性域
// 文件搜索路径 private String searchLocations; // 配置文件名称 private String names; // Order排序值 private int order = DEFAULT_ORDER;
Loader属性和构造方法
在Loader中定义了profile信息,以及资源加载器。
- 属性
// 环境 private final ConfigurableEnvironment environment; // 资源加载器 private final ResourceLoader resourceLoader; // 属性加载器 private PropertySourcesLoader propertiesLoader; // profile队列 private Queue<Profile> profiles; // 被处理的profiles private List<Profile> processedProfiles; // 处理活动状态的profiles private boolean activatedProfiles;
- 构造方法
构造方法从SpringAppliaction中获取了环境和资源加载器,如果资源加载器为空,则使用默认的加载器DefaultResourceLoader。
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment; this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader() : resourceLoader; }
Loader#load()方法分析
首先从环境中获取spring.profiles.active和spring.profiles.include这两个配置,存放到initialActiveProfiles集合中去。注意不是从配置文件中,因为在这时还没有加载配置文件。然后通过environment.getActiveProfiles()方法获取已经存在的集合,将此集合和initialActiveProfiles集合做差集,获得未在environment中的profiles添加到profiles队列中去,如果没有则添加默认的profiles。最后添加一个null到队列中去。
public void load() { this.propertiesLoader = new PropertySourcesLoader(); this.activatedProfiles = false; this.profiles = Collections.asLifoQueue(new LinkedList<Profile>()); this.processedProfiles = new LinkedList<Profile>(); // 从环境中获取spring.profiles.active和spring.profiles.include配置的profiles Set<Profile> initialActiveProfiles = initializeActiveProfiles(); // 获取环境中还未处理的profiles this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles)); if (this.profiles.isEmpty()) { // 获取默认profiles for (String defaultProfileName : this.environment.getDefaultProfiles()) { Profile defaultProfile = new Profile(defaultProfileName, true); if (!this.profiles.contains(defaultProfile)) { this.profiles.add(defaultProfile); } } } // 添加null,null会第一个出队列 this.profiles.add(null); // 循环处理,队列中存在[null, default] while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); // 搜索文件路径,以/结尾表示文件夹搜索,否则为文件。 for (String location : getSearchLocations()) { if (!location.endsWith("/")) { load(location, null, profile); } else { // 获取需要搜索的文件名称 for (String name : getSearchNames()) { load(location, name, profile); } } } this.processedProfiles.add(profile); } // 添加配置属性到环境中去 addConfigurationProperties(this.propertiesLoader.getPropertySources()); }
从profiles队列中逐一获取,然后进行文件路径搜索,/结尾的表示文件夹,否则为文件。使用load(location, name, profile)进行文件的加载。加载的属性源文件存放在PropertySourcesLoader中,然后添加到配置属性中去。
getSearchLocations()方法获取需要搜索的文件路径,默认使用的是classpath:/,classpath:/config/,file:./,file:./config/。首先判断环境中是否存在spring.config.location属性配置了指定的路径,如果存在则添加路径到locations中。然后将从searchLocations变量中获取路径,如果没有则使用默认的classpath:/,classpath:/config/,file:./,file:./config/。注意searchLocations可以使用逗号分隔多个路径地址,最后的优先级最高。
private Set<String> getSearchLocations() { Set<String> locations = new LinkedHashSet<String>(); // 判断环境中是否存在spring.config.location属性 if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { // 遍历获取配置路径 // 如果是文件路径,需要加file:前缀 for (String path : asResolvedSet( this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) { if (!path.contains("$")) { path = StringUtils.cleanPath(path); if (!ResourceUtils.isUrl(path)) { path = ResourceUtils.FILE_URL_PREFIX + path; } } locations.add(path); } } // asResolvedSet方法会对List数据进行顺序反转 locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; }
getSearchNames()方法获取需要搜索的文件名称,默认名称是application。首先从环境中获取spring.config.name属性,如果存在则使用环境中配置的文件名称,可以配置多个,用逗号分隔。如果没有则使用默认的文件名称application
private Set<String> getSearchNames() { if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), null); } return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); }
在获取到配置的locations之后,对路径中搜索的文件进行加载,使用load(String location, String name, Profile profile)方法。
private void load(String location, String name, Profile profile) { // 分组,第一个prifile为空,当location为文件时name为空 String group = "profile=" + (profile == null ? "" : profile); if (!StringUtils.hasText(name)) { // 直接加载路径(文件) loadIntoGroup(group, location, profile); } else { // 获取支持加载的文件后缀 .properties、.yaml、.yam for (String ext : this.propertiesLoader.getAllFileExtensions()) { if (profile != null) { // 加载profile文件,例如:classpath:/application-dev.properties loadIntoGroup(group, location + name + "-" + profile + "." + ext, null); // 加载被处理过的profile,为什么? for (Profile processedProfile : this.processedProfiles) { if (processedProfile != null) { loadIntoGroup(group, location + name + "-" + processedProfile + "." + ext, profile); } } // Sometimes people put "spring.profiles: dev" in // application-dev.yml (gh-340). Arguably we should try and error // out on that, but we can be kind and load it anyway. // loadIntoGroup(group, location + name + "-" + profile + "." + ext, profile); } // 首先会加载最通用的文件,例如: application.proerties loadIntoGroup(group, location + name + "." + ext, profile); } } }
从上面分析看,在加载的过程中会对搜索文件路径和支持的后缀文件进行遍历处理,都调用loadIntoGroup方法,添加到分组中去。loadIntoGroup方法只调用了doLoadIntoGroup方法。下面继续分析doLoadIntoGroup方法,看文件加载的过程。
private PropertySource<?> doLoadIntoGroup(String identifier, String location, Profile profile) throws IOException { // 使用资源加载器获取文件资源 Resource resource = this.resourceLoader.getResource(location); PropertySource<?> propertySource = null; StringBuilder msg = new StringBuilder(); // 如果加载到文件就处理,否则跳过处理 if (resource != null && resource.exists()) { String name = "applicationConfig: [" + location + "]"; String group = "applicationConfig: [" + identifier + "]"; // 使用属性源加载器处理资源文件 propertySource = this.propertiesLoader.load(resource, group, name, (profile == null ? null : profile.getName())); if (propertySource != null) { msg.append("Loaded "); // 将获取的属性源中的profiles存放到profiles属性域中。 handleProfileProperties(propertySource); } else { msg.append("Skipped (empty) "); } } else { msg.append("Skipped "); } msg.append("config file "); msg.append(getResourceDescription(location, resource)); if (profile != null) { msg.append(" for profile ").append(profile); } if (resource == null || !resource.exists()) { msg.append(" resource not found"); this.logger.trace(msg); } else { this.logger.debug(msg); } return propertySource; }
PropertySourcesLoader 属性源加载器
在上面的doLoadIntoGroup(String group, String location,Profile profile)方法中,将搜索到的文件进行分组后使用PropertySourcesLoader的load方法进行处理资源文件。下面我们具体分析下这个类。
先从构造方法分析:
public PropertySourcesLoader() { this(new MutablePropertySources()); } public PropertySourcesLoader(MutablePropertySources propertySources) { Assert.notNull(propertySources, "PropertySources must not be null"); this.propertySources = propertySources; this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); }
在Loader#load()方法调用时首先就通过this.propertiesLoader = new PropertySourcesLoader()创建了实例对象。通过上面的构造方法得知this.loaders从/META-INF/spring.fatories中获取PropertySourceLoader实例,这两个实例对象是PropertiesPropertySourceLoader和YamlPropertySourceLoader,分别处理properties文件和yaml文件。
在spring-boot-1.5.8.RELEASE.jar包中的/META-INF/spring.fatories文件中配置如下:
# PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader
PropertySourcesLoader类是属性资源加载的工具类,相当于PropertySourceLoader接口的代理层。下面看看load方法。
public PropertySource<?> load(Resource resource, String group, String name, String profile) throws IOException { // 判断资源是否是文件 if (isFile(resource)) { // 组合成文件名称 String sourceName = generatePropertySourceName(name, profile); // 遍历资源加载器 for (PropertySourceLoader loader : this.loaders) { // 根据后缀判断文件是否可以被加载 if (canLoadFileExtension(loader, resource)) { // 开始加载文件 PropertySource<?> specific = loader.load(sourceName, resource, profile); // 将获得的属性源添加到this.propertySources中去 addPropertySource(group, specific, profile); return specific; } } } return null; }
PropertiesPropertySourceLoader加载器负责加载properties、xml文件,YamlPropertySourceLoader加载器负责加载yaml、yam文件。下面是这两个类的getFileExtensions()方法。
public class PropertiesPropertySourceLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[] { "properties", "xml" }; } } public class YamlPropertySourceLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[] { "yml", "yaml" }; } }
分析完了属性资源文件的加载,最后将加载完成的属性源对象保存在PropertySourcesLoader的集合中。在加载完成后,调用addConfigurationProperties方法,将属性源设置到环境中去。
private void addConfigurationProperties(MutablePropertySources sources) { List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>(); for (PropertySource<?> item : sources) { reorderedSources.add(item); } // 使用ConfigurationPropertySources将所有的属性源进行包装 addConfigurationProperties( new ConfigurationPropertySources(reorderedSources)); } private void addConfigurationProperties( ConfigurationPropertySources configurationSources) { MutablePropertySources existingSources = this.environment .getPropertySources(); // 判断环境中是否存在DEFAULT_PROPERTIES这个属性源,如果存在添加到之前 if (existingSources.contains(DEFAULT_PROPERTIES)) { existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources); } else { // 如果不存在添加到最后 existingSources.addLast(configurationSources); } }
但是属性源的配置还并没有结束,ConfigurationPropertySources统一包装多个属性源只是一个中间过程。后面的处理还需要分析监听上下文准备完毕事件。在上面的处理中只是将ConfigurationPropertySources和DEFAULT_PROPERTIES属性源的位置关系进行处理。
监听上下文准备完毕事件
在ConfigFileApplicationListener中会通过ApplicationPreparedEvent事件来触发onApplicationPreparedEvent(event)方法。方法内容如下:
private void onApplicationPreparedEvent(ApplicationEvent event) { this.logger.replayTo(ConfigFileApplicationListener.class); // 添加BeanFactoryPostProcessor addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); } protected void addPostProcessors(ConfigurableApplicationContext context) { // 将后置处理器添加到上下文中去 context.addBeanFactoryPostProcessor( new PropertySourceOrderingPostProcessor(context)); }
我们在分析Spring Boot BeanFactoryPostProcessor时提到过ConfigFileApplicationListener$PropertySourceOrderingPostProcessor这个类,就是在监听上下文准备完毕后添加进去的。在Spring启动注册BeanFacatoryPostProcessor时会调用postProcessBeanFactory方法。
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { reorderSources(this.context.getEnvironment()); } private void reorderSources(ConfigurableEnvironment environment) { // 完成和重新分配属性配置 ConfigurationPropertySources .finishAndRelocate(environment.getPropertySources()); // 从环境中删除DEFAULT_PROPERTIES // 如果存在,则放到最后,也就是说默认的属性优先级最低 PropertySource<?> defaultProperties = environment.getPropertySources() .remove(DEFAULT_PROPERTIES); if (defaultProperties != null) { environment.getPropertySources().addLast(defaultProperties); } }
关于重新分配属性我们需要细看一下,看配置资源是如何重新分配到环境中去的。下面是ConfigurationPropertySources类中的finishAndRelocate方法。
public static void finishAndRelocate(MutablePropertySources propertySources) { // 很显然这里获取的ConfigurationPropertySources就是上面添加到环境中去的 String name = APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME; ConfigurationPropertySources removed = (ConfigurationPropertySources) propertySources .get(name); // 如果存在,需要做如下处理 if (removed != null) { // 将ConfigurationPropertySources中的属性源集合逐一存放到环境中来 for (PropertySource<?> propertySource : removed.sources) { // 这里处理嵌入的情况,主要是设置name值 if (propertySource instanceof EnumerableCompositePropertySource) { EnumerableCompositePropertySource composite = (EnumerableCompositePropertySource) propertySource; for (PropertySource<?> nested : composite.getSource()) { propertySources.addAfter(name, nested); name = nested.getName(); } } else { propertySources.addAfter(name, propertySource); } } // 最后从环境中删除 propertySources.remove(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME); } }
至此,所有的属性配置资源已经加载并存放到环境中来了。
转载于:https://my.oschina.net/xiaoqiyiye/blog/1624285
- 点赞
- 收藏
- 分享
- 文章举报
- spring boot实战(第六篇)加载application资源文件源码分析
- spring boot实战(第六篇)加载application资源文件源码分析
- spring boot实战(第六篇)加载application资源文件源码分析
- 【Spring源码分析】.properties文件读取及占位符${...}替换源码解析
- SpringBoot源码分析之环境和配置文件的加载
- Spring 加载 *.properties 文件的源码分析
- Spring源码分析--Ioc容器定位解析资源文件并注册BeanDefinition
- SpringBoot-Loader源码分析系列1:启动&读取MANIFEST.MF文件
- 曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean
- SpringBoot不要使用ResourceUtils读取资源文件
- Spring对注解(Annotation)处理源码分析2——解析和注入注解配置的资源
- Spring - 资源文件properties的配置
- springboot 配置文件 .properties和.yml的写法区别
- Spring Boot Properties文件读取
- Spring Boot四种读取properties文件的方式
- SpringBoot (五) :YAML和PROPERTIES配置文件详解_一点课堂(多岸学院)
- Spring3.x 获取properties资源文件的值
- springboot2.x +kafka使用和源码分析四(kafka事务)
- SpringBoot内部配置:“application.properties配置”和”使用XML配置”,读取属性文件中的内容,日志配置,Profile配置(学习:SpringBoot实战)
- springboot @value("${sj.name}") 或者@ConfigurationProperties("jwt.config") 配置文件参数读取