spring boot实战(第六篇)加载application资源文件源码分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言

在上一篇中了解了spring配置资源的加载过程,本篇在此基础上学习spring boot如何默认加载application.xml等文件信息的。

ConfigFileApplicationListener

spring boot实战(第三篇)事件监听源码分析中可知在构造SpringApplication时加载相关的监听器,其中存在一个监听器ConfigFileApplicationListener,其定义如下:

[html] view plain copy

  1. public class ConfigFileApplicationListener implements
  2. ApplicationListener<**ApplicationEvent**>, Ordered {
  3. @Override
  4. public void onApplicationEvent(ApplicationEvent event) {
  5. if (event instanceof ApplicationEnvironmentPreparedEvent) {
  6. onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
  7. }
  8. if (event instanceof ApplicationPreparedEvent) {
  9. onApplicationPreparedEvent((ApplicationPreparedEvent) event);
  10. }
  11. }
  12. }

监听ApplicationEvent事件,在触发所有其子类以及本身事件时会执行其onApplicationEvent方法。在执行

[html] view plain copy

  1. for (SpringApplicationRunListener runListener : runListeners) {
  2. runListener.environmentPrepared(environment);
  3. }

时会触发到

[html] view plain copy

  1. if (event instanceof ApplicationEnvironmentPreparedEvent) {
  2. onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
  3. }

中;

[html] view plain copy

  1. private void onApplicationEnvironmentPreparedEvent(
  2. ApplicationEnvironmentPreparedEvent event) {
  3. Environment environment = event.getEnvironment();
  4. if (environment instanceof ConfigurableEnvironment) {
  5. onApplicationEnvironmentPreparedEvent((ConfigurableEnvironment) environment,
  6. event.getSpringApplication());
  7. }
  8. }

在上一篇中可以知道enviroment为StandardServletEnvironment实例,因此执行onApplicationEnvironmentPreparedEvent方法

[html] view plain copy

  1. private void onApplicationEnvironmentPreparedEvent(
  2. ConfigurableEnvironment environment, SpringApplication application) {
  3. addPropertySources(environment, application.getResourceLoader());
  4. bindToSpringApplication(environment, application);
  5. }

首先来看addPropertySources相关信息

[html] view plain copy

  1. protected void addPropertySources(ConfigurableEnvironment environment,
  2. ResourceLoader resourceLoader) {
  3. RandomValuePropertySource.addToEnvironment(environment);
  4. try {
  5. new Loader(environment, resourceLoader).load();
  6. }
  7. catch (IOException ex) {
  8. throw new IllegalStateException(“Unable to load configuration files”, ex);
  9. }
  10. }

RandomValuePropertySource.addToEnvironment(environment)将随机方法放入到PropertySources中

[html] view plain copy

  1. public static void addToEnvironment(ConfigurableEnvironment environment) {
  2. environment.getPropertySources().addAfter(
  3. StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
  4. new RandomValuePropertySource(“random”));
  5. logger.trace(“RandomValuePropertySource add to Environment”);
  6. }

如何从Random中获取值是需要看getProperty方法:

[html] view plain copy

  1. public Object getProperty(String name) {
  2. if (!name.startsWith(“random.”)) {
  3. return null;
  4. }
  5. if (logger.isTraceEnabled()) {
  6. logger.trace(“Generating random property for ‘” + name + “‘”);
  7. }
  8. if (name.endsWith(“int”)) {
  9. return getSource().nextInt();
  10. }
  11. if (name.startsWith(“random.long”)) {
  12. return getSource().nextLong();
  13. }
  14. if (name.startsWith(“random.int”) && name.length() > “random.int”.length() + 1) {
  15. String range = name.substring(“random.int”.length() + 1);
  16. range = range.substring(0, range.length() – 1);
  17. return getNextInRange(range);
  18. }
  19. byte[] bytes = new byte[32];
  20. getSource().nextBytes(bytes);
  21. return DigestUtils.md5DigestAsHex(bytes);
  22. }

其中的getSource()表示Random类。

接下来看

[html] view plain copy

  1. new Loader(environment, resourceLoader).load()

看load方法

[html] view plain copy

  1. public void load() throws IOException {
  2. …//处理profiles信息
  3. while (!this.profiles.isEmpty()) {
  4. String profile = this.profiles.poll();
  5. for (String location : getSearchLocations()) {
  6. if (!location.endsWith(“/”)) {
  7. // location is a filename already, so don’t search for more
  8. // filenames
  9. load(location, null, profile);
  10. }
  11. else {
  12. for (String name : getSearchNames()) {
  13. load(location, name, profile);
  14. }
  15. }
  16. }
  17. }
  18. addConfigurationProperties(this.propertiesLoader.getPropertySources());
  19. }

看getSearchLocations()方法

[html] view plain copy

  1. private Set<**String**> getSearchLocations() {
  2. Set<**String**> locations = new LinkedHashSet<**String**>();
  3. // User-configured settings take precedence, so we do them first
  4. if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
  5. for (String path : asResolvedSet(
  6. this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
  7. if (!path.contains(“$”)) {
  8. if (!path.contains(“:”)) {
  9. path = “file:” + path;
  10. }
  11. path = StringUtils.cleanPath(path);
  12. }
  13. locations.add(path);
  14. }
  15. }
  16. locations.addAll(asResolvedSet(
  17. ConfigFileApplicationListener.this.searchLocations,
  18. DEFAULT_SEARCH_LOCATIONS));
  19. return locations;
  20. }

首先看CONFIG_LOCATION_PROPERTY(spring.config.location)是否存在配置,无则走默认配置路径DEFAULT_SEARCH_LOCATIONS(classpath:/,classpath:/config/,file:./,file:./config/)

继续来看getSearchNames()方法

[html] view plain copy

  1. private Set<**String**> getSearchNames() {
  2. if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
  3. return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
  4. null);
  5. }
  6. return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
  7. }

优先看CONFIG_NAME_PROPERTY(spring.config.name)配置,否则走DEFAULT_NAMES(application)

解析完路径和配置文件名以后,将开始判断路径+名称组合是否存在 执行load(…)方法

[html] view plain copy

  1. private void load(String location, String name, String profile)
  2. throws IOException {
  3. String group = “profile=” + (profile == null ? “” : profile);
  4. if (!StringUtils.hasText(name)) {
  5. // Try to load directly from the location
  6. loadIntoGroup(group, location, profile);
  7. }
  8. else {
  9. // Search for a file with the given name
  10. for (String ext : this.propertiesLoader.getAllFileExtensions()) {
  11. if (profile != null) {
  12. // Try the profile specific file
  13. loadIntoGroup(group, location + name + “-” + profile + “.” + ext,
  14. null);
  15. // Sometimes people put “spring.profiles: dev” in
  16. // application-dev.yml (gh-340). Arguably we should try and error
  17. // out on that, but we can be kind and load it anyway.
  18. loadIntoGroup(group, location + name + “-” + profile + “.” + ext,
  19. profile);
  20. }
  21. // Also try the profile specific section (if any) of the normal file
  22. loadIntoGroup(group, location + name + “.” + ext, profile);
  23. }
  24. }
  25. }

this.propertiesLoader.getAllFileExtensions()方法获取文件后缀

[html] view plain copy

  1. public Set<**String**> getAllFileExtensions() {
  2. Set<**String**> fileExtensions = new HashSet<**String**>();
  3. for (PropertySourceLoader loader : this.loaders) {
  4. fileExtensions.addAll(Arrays.asList(loader.getFileExtensions()));
  5. }
  6. return fileExtensions;
  7. }

loader.getFileExtensions()获取所有支持的文件后缀,其中loader在执行load方法时实例化

[html] view plain copy

  1. public void load() throws IOException {
  2. this.propertiesLoader = new PropertySourcesLoader();
  3. …}

调用其构造方法

[html] view plain copy

  1. public PropertySourcesLoader(MutablePropertySources propertySources) {
  2. Assert.notNull(propertySources, “PropertySources must not be null”);
  3. this.propertySources = propertySources;
  4. this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
  5. null);
  6. }

可以看出this.loaders是由SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,null)得到

[html] view plain copy

  1. public static <**T**> List<**T**> loadFactories(Class<**T**> factoryClass, ClassLoader classLoader) {
  2. Assert.notNull(factoryClass, “‘factoryClass’ must not be null”);
  3. ClassLoader classLoaderToUse = classLoader;
  4. if (classLoaderToUse == null) {
  5. classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
  6. }
  7. List<**String**> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
  8. if (logger.isTraceEnabled()) {
  9. logger.trace(“Loaded [” + factoryClass.getName() + “] names: ” + factoryNames);
  10. }
  11. List<**T**> result = new ArrayList<**T**>(factoryNames.size());
  12. for (String factoryName : factoryNames) {
  13. result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
  14. }
  15. AnnotationAwareOrderComparator.sort(result);
  16. return result;
  17. }

加载META-INF/spring.factories文件下对应内容

[html] view plain copy

  1. # PropertySource Loaders
  2. org.springframework.boot.env.PropertySourceLoader=\
  3. org.springframework.boot.env.PropertiesPropertySourceLoader,\
  4. org.springframework.boot.env.YamlPropertySourceLoader

因此加载了PropertiesPropertySourceLoader以及YamlPropertySourceLoader类实例;

  • PropertiesPropertySourceLoader 支持文件后缀格式 “properties”,”xml”

[html] view plain copy

  1. @Override
  2. public String[] getFileExtensions() {
  3. return new String[] { “properties”, “xml” };
  4. }

    • YamlPropertySourceLoader 支持文件后缀格式 “yml”,”yaml”

[html] view plain copy

  1. @Override
  2. public String[] getFileExtensions() {
  3. return new String[] { “yml”, “yaml” };
  4. }

两者覆写的load方法实现如何处理资源为PropertySource对象。

获取完文件后缀后调用loadIntoGroup方法将资源信息转化为PropertySource,其实质为调用PropertySourcesLoader中load方法

[html] view plain copy

  1. private PropertySource<?**>** loadIntoGroup(String identifier, String location,
  2. String profile) throws IOException {
  3. Resource resource = this.resourceLoader.getResource(location);
  4. PropertySource<?**>** propertySource = null;
  5. if (resource != null) {
  6. String name = “applicationConfig: [“ + location + “]”;
  7. String group = “applicationConfig: [“ + identifier + “]”;
  8. propertySource = this.propertiesLoader.load(resource, group, name,
  9. profile);
  10. if (propertySource != null) {
  11. maybeActivateProfiles(propertySource
  12. .getProperty(ACTIVE_PROFILES_PROPERTY));
  13. addIncludeProfiles(propertySource
  14. .getProperty(INCLUDE_PROFILES_PROPERTY));
  15. }
  16. }
  17. StringBuilder msg = new StringBuilder();
  18. msg.append(propertySource == null ? “Skipped ” : “Loaded “);
  19. msg.append(“config file “);
  20. msg.append(“‘”).append(location).append(“‘”);
  21. if (StringUtils.hasLength(profile)) {
  22. msg.append(” for profile” + profile);
  23. }
  24. if (resource == null || !resource.exists()) {
  25. msg.append(” resource not found”);
  26. }
  27. this.debug.add(msg);
  28. return propertySource;
  29. }

最后调用addConfigurationProperties(this.propertiesLoader.getPropertySources())方法将解析过后的资源信息放置进Enviroment中propertySources属性集合中

[html] view plain copy

  1. private void addConfigurationProperties(MutablePropertySources sources) {
  2. List<**PropertySource<?>**> reorderedSources = new ArrayList<**PropertySource<?>**>();
  3. for (PropertySource<?**>** item : sources) {
  4. reorderedSources.add(item);
  5. }
  6. // Maybe we should add before the DEFAULT_PROPERTIES if it exists?
  7. this.environment.getPropertySources().addLast(
  8. new ConfigurationPropertySources(reorderedSources));
  9. }

至此 application.xml等文件的加载分析结束。

时序图

简单的画了一下时序图,可能和实际调用存在出入,仅作参考使用

2019101710065\_1.png


来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot实战(第六篇)加载application资源文件源码分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏