前提

org.springframework.core.env.Environment是当前应用运行环境的公开接口,主要包括应用程序运行环境的两个关键方面:配置文件(profiles)和属性。Environment继承自接口PropertyResolver,而PropertyResolver提供了属性访问的相关方法。这篇文章从源码的角度分析Environment的存储容器和加载流程,然后基于源码的理解给出一个生产级别的扩展。

本文较长,请用一个舒服的姿势阅读。

本文已经转移到个人博客中维护,因为维护多个地方的内容太麻烦

Environment类体系

  • PropertyResolver:提供属性访问功能。
  • ConfigurablePropertyResolver:继承自PropertyResolver,主要提供属性类型转换(基于org.springframework.core.convert.ConversionService)功能。
  • Environment:继承自PropertyResolver,提供访问和判断profiles的功能。
  • ConfigurableEnvironment:继承自ConfigurablePropertyResolver和Environment,并且提供设置激活的profile和默认的profile的功能。
  • ConfigurableWebEnvironment:继承自ConfigurableEnvironment,并且提供配置Servlet上下文和Servlet参数的功能。
  • AbstractEnvironment:实现了ConfigurableEnvironment接口,默认属性和存储容器的定义,并且实现了ConfigurableEnvironment种的方法,并且为子类预留可覆盖了扩展方法。
  • StandardEnvironment:继承自AbstractEnvironment,非Servlet(Web)环境下的标准Environment实现。
  • StandardServletEnvironment:继承自StandardEnvironment,Servlet(Web)环境下的标准Environment实现。

reactive相关的暂时不研究。

Environment提供的方法

一般情况下,我们在SpringMVC项目中启用到的是StandardServletEnvironment,它的父接口问ConfigurableWebEnvironment,我们可以查看此接口提供的方法:

Environment的存储容器

Environment的静态属性和存储容器都是在AbstractEnvironment中定义的,ConfigurableWebEnvironment接口提供的getPropertySources()方法可以获取到返回的MutablePropertySources实例,然后添加额外的PropertySource。实际上,Environment的存储容器就是org.springframework.core.env.PropertySource的子类集合,AbstractEnvironment中使用的实例是org.springframework.core.env.MutablePropertySources,下面看下PropertySource的源码:

  1. public abstract class PropertySource<T> {
  2. protected final Log logger = LogFactory.getLog(getClass());
  3. protected final String name;
  4. protected final T source;
  5. public PropertySource(String name, T source) {
  6. Assert.hasText(name, "Property source name must contain at least one character");
  7. Assert.notNull(source, "Property source must not be null");
  8. this.name = name;
  9. this.source = source;
  10. }
  11. @SuppressWarnings("unchecked")
  12. public PropertySource(String name) {
  13. this(name, (T) new Object());
  14. }
  15. public String getName() {
  16. return this.name;
  17. }
  18. public T getSource() {
  19. return this.source;
  20. }
  21. public boolean containsProperty(String name) {
  22. return (getProperty(name) != null);
  23. }
  24. @Nullable
  25. public abstract Object getProperty(String name);
  26. @Override
  27. public boolean equals(Object obj) {
  28. return (this == obj || (obj instanceof PropertySource &&
  29. ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name)));
  30. }
  31. @Override
  32. public int hashCode() {
  33. return ObjectUtils.nullSafeHashCode(this.name);
  34. }
  35. //省略其他方法和内部类的源码
  36. }

源码相对简单,预留了一个getProperty抽象方法给子类实现,重点需要关注的是覆写了的equalshashCode方法,实际上只和name属性相关,这一点很重要,说明一个PropertySource实例绑定到一个唯一的name,这个name有点像HashMap里面的key,部分移除、判断方法都是基于name属性。PropertySource的最常用子类是MapPropertySource、PropertiesPropertySource、ResourcePropertySource、StubPropertySource、ComparisonPropertySource:

  • MapPropertySource:source指定为Map实例的PropertySource实现。
  • PropertiesPropertySource:source指定为Map实例的PropertySource实现,内部的Map实例由Properties实例转换而来。
  • ResourcePropertySource:继承自PropertiesPropertySource,source指定为通过Resource实例转化为Properties再转换为Map实例。
  • StubPropertySource:PropertySource的一个内部类,source设置为null,实际上就是空实现。
  • ComparisonPropertySource:继承自ComparisonPropertySource,所有属性访问方法强制抛出异常,作用就是一个不可访问属性的空实现。

AbstractEnvironment中的属性定义:

  1. public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
  2. public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
  3. public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
  4. protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
  5. private final Set<String> activeProfiles = new LinkedHashSet<>();
  6. private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
  7. private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);
  8. private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);

上面的propertySources(MutablePropertySources类型)属性就是用来存放PropertySource列表的,PropertySourcesPropertyResolver是ConfigurablePropertyResolver的实现,默认的profile就是字符串default。MutablePropertySources的内部属性如下:

  1. private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

没错,这个就是最底层的存储容器,也就是环境属性都是存放在一个CopyOnWriteArrayList<PropertySource<?>>实例中。MutablePropertySources是PropertySources的子类,它提供了get(String name)addFirstaddLastaddBeforeaddAfterremovereplace等便捷方法,方便操作propertySourceList集合的元素,这里挑选addBefore的源码分析:

  1. public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
  2. if (logger.isDebugEnabled()) {
  3. logger.debug("Adding PropertySource '" + propertySource.getName() +
  4. "' with search precedence immediately higher than '" + relativePropertySourceName + "'");
  5. }
  6. //前一个PropertySource的name指定为relativePropertySourceName时候必须和添加的PropertySource的name属性不相同
  7. assertLegalRelativeAddition(relativePropertySourceName, propertySource);
  8. //尝试移除同名的PropertySource
  9. removeIfPresent(propertySource);
  10. //获取前一个PropertySource在CopyOnWriteArrayList中的索引
  11. int index = assertPresentAndGetIndex(relativePropertySourceName);
  12. //添加当前传入的PropertySource到指定前一个PropertySource的索引,相当于relativePropertySourceName对应的PropertySource后移到原来索引值+1的位置
  13. addAtIndex(index, propertySource);
  14. }
  15. protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) {
  16. String newPropertySourceName = propertySource.getName();
  17. if (relativePropertySourceName.equals(newPropertySourceName)) {
  18. throw new IllegalArgumentException(
  19. "PropertySource named '" + newPropertySourceName + "' cannot be added relative to itself");
  20. }
  21. }
  22. protected void removeIfPresent(PropertySource<?> propertySource) {
  23. this.propertySourceList.remove(propertySource);
  24. }
  25. private int assertPresentAndGetIndex(String name) {
  26. int index = this.propertySourceList.indexOf(PropertySource.named(name));
  27. if (index == -1) {
  28. throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist");
  29. }
  30. return index;
  31. }
  32. private void addAtIndex(int index, PropertySource<?> propertySource) {
  33. //注意,这里会再次尝试移除同名的PropertySource
  34. removeIfPresent(propertySource);
  35. this.propertySourceList.add(index, propertySource);
  36. }

大多数PropertySource子类的修饰符都是public,可以直接使用,这里写个小demo:

  1. MutablePropertySources mutablePropertySources = new MutablePropertySources();
  2. Map<String, Object> map = new HashMap<>(8);
  3. map.put("name", "throwable");
  4. map.put("age", 25);
  5. MapPropertySource mapPropertySource = new MapPropertySource("map", map);
  6. mutablePropertySources.addLast(mapPropertySource);
  7. Properties properties = new Properties();
  8. PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("prop", properties);
  9. properties.put("name", "doge");
  10. properties.put("gourp", "group-a");
  11. mutablePropertySources.addBefore("map", propertiesPropertySource);
  12. System.out.println(mutablePropertySources);

Environment加载过程源码分析

Environment加载的源码位于SpringApplication#prepareEnvironment

  1. private ConfigurableEnvironment prepareEnvironment(
  2. SpringApplicationRunListeners listeners,
  3. ApplicationArguments applicationArguments) {
  4. // Create and configure the environment
  5. //创建ConfigurableEnvironment实例
  6. ConfigurableEnvironment environment = getOrCreateEnvironment();
  7. //启动参数绑定到ConfigurableEnvironment中
  8. configureEnvironment(environment, applicationArguments.getSourceArgs());
  9. //发布ConfigurableEnvironment准备完毕事件
  10. listeners.environmentPrepared(environment);
  11. //绑定ConfigurableEnvironment到当前的SpringApplication实例中
  12. bindToSpringApplication(environment);
  13. //这一步是非SpringMVC项目的处理,暂时忽略
  14. if (this.webApplicationType == WebApplicationType.NONE) {
  15. environment = new EnvironmentConverter(getClassLoader())
  16. .convertToStandardEnvironmentIfNecessary(environment);
  17. }
  18. //绑定ConfigurationPropertySourcesPropertySource到ConfigurableEnvironment中,name为configurationProperties,实例是SpringConfigurationPropertySources,属性实际是ConfigurableEnvironment中的MutablePropertySources
  19. ConfigurationPropertySources.attach(environment);
  20. return environment;
  21. }

这里重点看下getOrCreateEnvironment方法:

  1. private ConfigurableEnvironment getOrCreateEnvironment() {
  2. if (this.environment != null) {
  3. return this.environment;
  4. }
  5. //在SpringMVC项目,ConfigurableEnvironment接口的实例就是新建的StandardServletEnvironment实例
  6. if (this.webApplicationType == WebApplicationType.SERVLET) {
  7. return new StandardServletEnvironment();
  8. }
  9. return new StandardEnvironment();
  10. }
  11. //REACTIVE_WEB_ENVIRONMENT_CLASS=org.springframework.web.reactive.DispatcherHandler
  12. //MVC_WEB_ENVIRONMENT_CLASS=org.springframework.web.servlet.DispatcherServlet
  13. //MVC_WEB_ENVIRONMENT_CLASS={"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"}
  14. //这里,默认就是WebApplicationType.SERVLET
  15. private WebApplicationType deduceWebApplicationType() {
  16. if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
  17. && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
  18. return WebApplicationType.REACTIVE;
  19. }
  20. for (String className : WEB_ENVIRONMENT_CLASSES) {
  21. if (!ClassUtils.isPresent(className, null)) {
  22. return WebApplicationType.NONE;
  23. }
  24. }
  25. return WebApplicationType.SERVLET;
  26. }

还有一个地方要重点关注:发布ConfigurableEnvironment准备完毕事件listeners.environmentPrepared(environment),实际上这里用到了同步的EventBus,事件的监听者是ConfigFileApplicationListener,具体处理逻辑是onApplicationEnvironmentPreparedEvent方法:

  1. private void onApplicationEnvironmentPreparedEvent(
  2. ApplicationEnvironmentPreparedEvent event) {
  3. List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
  4. postProcessors.add(this);
  5. AnnotationAwareOrderComparator.sort(postProcessors);
  6. //遍历所有的EnvironmentPostProcessor对Environment实例进行处理
  7. for (EnvironmentPostProcessor postProcessor : postProcessors) {
  8. postProcessor.postProcessEnvironment(event.getEnvironment(),
  9. event.getSpringApplication());
  10. }
  11. }
  12. //从spring.factories文件中加载,一共有四个实例
  13. //ConfigFileApplicationListener
  14. //CloudFoundryVcapEnvironmentPostProcessor
  15. //SpringApplicationJsonEnvironmentPostProcessor
  16. //SystemEnvironmentPropertySourceEnvironmentPostProcessor
  17. List<EnvironmentPostProcessor> loadPostProcessors() {
  18. return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
  19. getClass().getClassLoader());
  20. }

实际上,处理工作大部分都在ConfigFileApplicationListener中,见它的postProcessEnvironment方法:

  1. public void postProcessEnvironment(ConfigurableEnvironment environment,
  2. SpringApplication application) {
  3. addPropertySources(environment, application.getResourceLoader());
  4. }
  5. protected void addPropertySources(ConfigurableEnvironment environment,
  6. ResourceLoader resourceLoader) {
  7. RandomValuePropertySource.addToEnvironment(environment);
  8. new Loader(environment, resourceLoader).load();
  9. }

主要的配置环境加载逻辑在内部类Loader,Loader会匹配多个路径下的文件把属性加载到ConfigurableEnvironment中,加载器主要是PropertySourceLoader的实例,例如我们用到application-${profile}.yaml文件做应用主配置文件,使用的是YamlPropertySourceLoader,这个时候activeProfiles也会被设置到ConfigurableEnvironment中。加载完毕之后,ConfigurableEnvironment中基本包含了所有需要加载的属性(activeProfiles是这个时候被写入ConfigurableEnvironment)。值得注意的是,几乎所有属性都是key-value形式存储,如xxx.yyyy.zzzzz=value、xxx.yyyy[0].zzzzz=value-1、xxx.yyyy[1].zzzzz=value-2。Loader中的逻辑相对复杂,有比较多的遍历和过滤条件,这里不做展开。

Environment属性访问源码分析

上文提到过,都是委托到PropertySourcesPropertyResolver,先看它的构造函数:

  1. @Nullable
  2. private final PropertySources propertySources;
  3. public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
  4. this.propertySources = propertySources;
  5. }

只依赖于一个PropertySources实例,在SpringBoot的SpringMVC项目中就是MutablePropertySources的实例。重点分析一下最复杂的一个方法:

  1. protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
  2. if (this.propertySources != null) {
  3. //遍历所有的PropertySource
  4. for (PropertySource<?> propertySource : this.propertySources) {
  5. if (logger.isTraceEnabled()) {
  6. logger.trace("Searching for key '" + key + "' in PropertySource '" +
  7. propertySource.getName() + "'");
  8. }
  9. Object value = propertySource.getProperty(key);
  10. //选用第一个不为null的匹配key的属性值
  11. if (value != null) {
  12. if (resolveNestedPlaceholders && value instanceof String) {
  13. //处理属性占位符,如${server.port},底层委托到PropertyPlaceholderHelper完成
  14. value = resolveNestedPlaceholders((String) value);
  15. }
  16. logKeyFound(key, propertySource, value);
  17. //如果需要的话,进行一次类型转换,底层委托到DefaultConversionService完成
  18. return convertValueIfNecessary(value, targetValueType);
  19. }
  20. }
  21. }
  22. if (logger.isDebugEnabled()) {
  23. logger.debug("Could not find key '" + key + "' in any property source");
  24. }
  25. return null;
  26. }

这里的源码告诉我们,如果出现多个PropertySource中存在同名的key,返回的是第一个PropertySource对应key的属性值的处理结果,因此我们如果需要自定义一些环境属性,需要十分清楚各个PropertySource的顺序。

扩展-实现分散配置

在不使用SpringCloud配置中心的情况下,一般的SpringBoot项目的配置文件如下:

  1. - src
  2. - main
  3. - resources
  4. - application-prod.yaml
  5. - application-dev.yaml
  6. - application-test.yaml

随着项目发展,配置项越来越多,导致了application-${profile}.yaml迅速膨胀,大的配置文件甚至超过一千行,为了简化和划分不同功能的配置,可以考虑把配置文件拆分如下:

  1. - src
  2. - main
  3. - resources
  4. - profiles
  5. - dev
  6. - business.yaml
  7. - mq.json
  8. - datasource.properties
  9. - prod
  10. - business.yaml
  11. - mq.json
  12. - datasource.properties
  13. - test
  14. - business.yaml
  15. - mq.json
  16. - datasource.properties
  17. - application-prod.yaml
  18. - application-dev.yaml
  19. - application-test.yaml

外层的application-${profile}.yaml只留下项目的核心配置如server.port等,其他配置打散放在/profiles/${profile}/各自的配置文件中。实现方式是:依据当前配置的spring.profiles.active属性,读取类路径中指定文件夹下的配置文件中,加载到Environment中,需要注意这一个加载步骤必须在Spring刷新上下文方法最后一步finishRefresh之前完成(这一点原因可以参考之前在个人博客写过的SpringBoot刷新上下文源码的分析),否则有可能会影响到占位符属性的自动装配(例如使用了@Value("${filed}"))。

先定义一个属性探索者接口:

  1. public interface PropertySourceDetector {
  2. /**
  3. * 获取支持的文件后缀数组
  4. *
  5. * @return String[]
  6. */
  7. String[] getFileExtensions();
  8. /**
  9. * 加载目标文件属性到环境中
  10. *
  11. * @param environment environment
  12. * @param name name
  13. * @param resource resource
  14. * @throws IOException IOException
  15. */
  16. void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException;
  17. }

然后需要一个抽象属性探索者把Resource转换为字符串,额外提供Map的缩进、添加PropertySource到Environment等方法:

  1. public abstract class AbstractPropertySourceDetector implements PropertySourceDetector {
  2. private static final String SERVLET_ENVIRONMENT_CLASS = "org.springframework.web."
  3. + "context.support.StandardServletEnvironment";
  4. public boolean support(String fileExtension) {
  5. String[] fileExtensions = getFileExtensions();
  6. return null != fileExtensions &&
  7. Arrays.stream(fileExtensions).anyMatch(extension -> extension.equals(fileExtension));
  8. }
  9. private String findPropertySource(MutablePropertySources sources) {
  10. if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null) && sources
  11. .contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) {
  12. return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME;
  13. }
  14. return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME;
  15. }
  16. protected void addPropertySource(ConfigurableEnvironment environment, PropertySource<?> source) {
  17. MutablePropertySources sources = environment.getPropertySources();
  18. String name = findPropertySource(sources);
  19. if (sources.contains(name)) {
  20. sources.addBefore(name, source);
  21. } else {
  22. sources.addFirst(source);
  23. }
  24. }
  25. protected Map<String, Object> flatten(Map<String, Object> map) {
  26. Map<String, Object> result = new LinkedHashMap<>();
  27. flatten(null, result, map);
  28. return result;
  29. }
  30. private void flatten(String prefix, Map<String, Object> result, Map<String, Object> map) {
  31. String namePrefix = (prefix != null ? prefix + "." : "");
  32. map.forEach((key, value) -> extract(namePrefix + key, result, value));
  33. }
  34. @SuppressWarnings("unchecked")
  35. private void extract(String name, Map<String, Object> result, Object value) {
  36. if (value instanceof Map) {
  37. flatten(name, result, (Map<String, Object>) value);
  38. } else if (value instanceof Collection) {
  39. int index = 0;
  40. for (Object object : (Collection<Object>) value) {
  41. extract(name + "[" + index + "]", result, object);
  42. index++;
  43. }
  44. } else {
  45. result.put(name, value);
  46. }
  47. }
  48. protected String getContentStringFromResource(Resource resource) throws IOException {
  49. return StreamUtils.copyToString(resource.getInputStream(), Charset.forName("UTF-8"));
  50. }
  51. }

上面的方法参考SpringApplicationJsonEnvironmentPostProcessor,然后编写各种类型配置属性探索者的实现:

  1. //Json
  2. @Slf4j
  3. public class JsonPropertySourceDetector extends AbstractPropertySourceDetector {
  4. private static final JsonParser JSON_PARSER = JsonParserFactory.getJsonParser();
  5. @Override
  6. public String[] getFileExtensions() {
  7. return new String[]{"json"};
  8. }
  9. @Override
  10. public void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException {
  11. try {
  12. Map<String, Object> map = JSON_PARSER.parseMap(getContentStringFromResource(resource));
  13. Map<String, Object> target = flatten(map);
  14. addPropertySource(environment, new MapPropertySource(name, target));
  15. } catch (Exception e) {
  16. log.warn("加载Json文件属性到环境变量失败,name = {},resource = {}", name, resource);
  17. }
  18. }
  19. }
  20. //Properties
  21. public class PropertiesPropertySourceDetector extends AbstractPropertySourceDetector {
  22. @Override
  23. public String[] getFileExtensions() {
  24. return new String[]{"properties", "conf"};
  25. }
  26. @SuppressWarnings("unchecked")
  27. @Override
  28. public void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException {
  29. Map map = PropertiesLoaderUtils.loadProperties(resource);
  30. addPropertySource(environment, new MapPropertySource(name, map));
  31. }
  32. }
  33. //Yaml
  34. @Slf4j
  35. public class YamlPropertySourceDetector extends AbstractPropertySourceDetector {
  36. private static final JsonParser YAML_PARSER = new YamlJsonParser();
  37. @Override
  38. public String[] getFileExtensions() {
  39. return new String[]{"yaml", "yml"};
  40. }
  41. @Override
  42. public void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException {
  43. try {
  44. Map<String, Object> map = YAML_PARSER.parseMap(getContentStringFromResource(resource));
  45. Map<String, Object> target = flatten(map);
  46. addPropertySource(environment, new MapPropertySource(name, target));
  47. } catch (Exception e) {
  48. log.warn("加载Yaml文件属性到环境变量失败,name = {},resource = {}", name, resource);
  49. }
  50. }
  51. }

子类的全部PropertySource都是MapPropertySource,name为文件的名称,所有PropertySource都用addBefore方法插入到systemProperties的前面,主要是为了提高匹配属性的优先级。接着需要定义一个属性探索者的合成类用来装载所有的子类:

  1. public class PropertySourceDetectorComposite implements PropertySourceDetector {
  2. private static final String DEFAULT_SUFFIX = "properties";
  3. private final List<AbstractPropertySourceDetector> propertySourceDetectors = new ArrayList<>();
  4. public void addPropertySourceDetector(AbstractPropertySourceDetector sourceDetector) {
  5. propertySourceDetectors.add(sourceDetector);
  6. }
  7. public void addPropertySourceDetectors(List<AbstractPropertySourceDetector> sourceDetectors) {
  8. propertySourceDetectors.addAll(sourceDetectors);
  9. }
  10. public List<AbstractPropertySourceDetector> getPropertySourceDetectors() {
  11. return Collections.unmodifiableList(propertySourceDetectors);
  12. }
  13. @Override
  14. public String[] getFileExtensions() {
  15. List<String> fileExtensions = new ArrayList<>(8);
  16. for (AbstractPropertySourceDetector propertySourceDetector : propertySourceDetectors) {
  17. fileExtensions.addAll(Arrays.asList(propertySourceDetector.getFileExtensions()));
  18. }
  19. return fileExtensions.toArray(new String[0]);
  20. }
  21. @Override
  22. public void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException {
  23. if (resource.isFile()) {
  24. String fileName = resource.getFile().getName();
  25. int index = fileName.lastIndexOf(".");
  26. String suffix;
  27. if (-1 == index) {
  28. //如果文件没有后缀,当作properties处理
  29. suffix = DEFAULT_SUFFIX;
  30. } else {
  31. suffix = fileName.substring(index + 1);
  32. }
  33. for (AbstractPropertySourceDetector propertySourceDetector : propertySourceDetectors) {
  34. if (propertySourceDetector.support(suffix)) {
  35. propertySourceDetector.load(environment, name, resource);
  36. return;
  37. }
  38. }
  39. }
  40. }
  41. }

最后添加一个配置类作为入口:

  1. public class PropertySourceDetectorConfiguration implements ImportBeanDefinitionRegistrar {
  2. private static final String PATH_PREFIX = "profiles";
  3. @Override
  4. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  5. DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) registry;
  6. ConfigurableEnvironment environment = beanFactory.getBean(ConfigurableEnvironment.class);
  7. List<AbstractPropertySourceDetector> propertySourceDetectors = new ArrayList<>();
  8. configurePropertySourceDetectors(propertySourceDetectors, beanFactory);
  9. PropertySourceDetectorComposite propertySourceDetectorComposite = new PropertySourceDetectorComposite();
  10. propertySourceDetectorComposite.addPropertySourceDetectors(propertySourceDetectors);
  11. String[] activeProfiles = environment.getActiveProfiles();
  12. ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
  13. try {
  14. for (String profile : activeProfiles) {
  15. String location = PATH_PREFIX + File.separator + profile + File.separator + "*";
  16. Resource[] resources = resourcePatternResolver.getResources(location);
  17. for (Resource resource : resources) {
  18. propertySourceDetectorComposite.load(environment, resource.getFilename(), resource);
  19. }
  20. }
  21. } catch (IOException e) {
  22. throw new IllegalStateException(e);
  23. }
  24. }
  25. private void configurePropertySourceDetectors(List<AbstractPropertySourceDetector> propertySourceDetectors,
  26. DefaultListableBeanFactory beanFactory) {
  27. Map<String, AbstractPropertySourceDetector> beansOfType = beanFactory.getBeansOfType(AbstractPropertySourceDetector.class);
  28. for (Map.Entry<String, AbstractPropertySourceDetector> entry : beansOfType.entrySet()) {
  29. propertySourceDetectors.add(entry.getValue());
  30. }
  31. propertySourceDetectors.add(new JsonPropertySourceDetector());
  32. propertySourceDetectors.add(new YamlPropertySourceDetector());
  33. propertySourceDetectors.add(new PropertiesPropertySourceDetector());
  34. }
  35. }

准备就绪,在/resources/profiles/dev下面添加两个文件app.json和conf:

  1. //app.json
  2. {
  3. "app": {
  4. "name": "throwable",
  5. "age": 25
  6. }
  7. }
  8. //conf
  9. name=doge

项目的application.yaml添加属性spring.profiles.active: dev,最后添加一个CommandLineRunner的实现用来观察数据:

  1. @Slf4j
  2. @Component
  3. public class CustomCommandLineRunner implements CommandLineRunner {
  4. @Value("${app.name}")
  5. String name;
  6. @Value("${app.age}")
  7. Integer age;
  8. @Autowired
  9. ConfigurableEnvironment configurableEnvironment;
  10. @Override
  11. public void run(String... args) throws Exception {
  12. log.info("name = {},age = {}", name, age);
  13. }
  14. }

自动装配的属性值和Environment实例中的属性和预期一样,改造是成功的。

小结

Spring中的环境属性管理的源码个人认为是最清晰和简单的:从文件中读取数据转化为key-value结构,key-value结构存放在一个PropertySource实例中,然后得到的多个PropertySource实例存放在一个CopyOnWriteArrayList中,属性访问的时候总是遍历CopyOnWriteArrayList中的PropertySource进行匹配。可能相对复杂的就是占位符的解析和参数类型的转换,后者牵连到Converter体系,这些不在本文的讨论范围内。最后附上一张Environment存储容器的示例图:

参考资料:

  • spring-boot-starter-web:2.0.3.RELEASE源码。

示例项目:https://github.com/zjcscut/spring-boot-environment

(本文完)

基于SpringBoot的Environment源码理解实现分散配置的更多相关文章

  1. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  2. SpringBoot读取配置文件源码探究

    1. SpringBoot读取配置文件源码探究 1.1. 概览 springboot的源码是再原来的Spring源码上又包了一层,看过spring源码都知道,当我们从入口debug进去的时候,原来的S ...

  3. jedis的源码理解-基础篇

    [jedis的源码理解-基础篇][http://my.oschina.net/u/944165/blog/127998] (关注实现关键功能的类)   基于jedis 2.2.0-SNAPSHOT   ...

  4. .NET Core 3.0之深入源码理解Startup的注册及运行

    原文:.NET Core 3.0之深入源码理解Startup的注册及运行   写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...

  5. 深入源码理解Spring整合MyBatis原理

    写在前面 聊一聊MyBatis的核心概念.Spring相关的核心内容,主要结合源码理解Spring是如何整合MyBatis的.(结合右侧目录了解吧) MyBatis相关核心概念粗略回顾 SqlSess ...

  6. 基于Eclipse搭建Hadoop源码环境

    Hadoop使用ant+ivy组织工程,无法直接导入Eclipse中.本文将介绍如何基于Eclipse搭建Hadoop源码环境. 准备工作 本文使用的操作系统为CentOS.需要的软件版本:hadoo ...

  7. Springboot security cas源码陶冶-ExceptionTranslationFilter

    拦截关键的两个异常,对异常进行处理.主要应用异常则跳转至cas服务端登录页面 ExceptionTranslationFilter#doFilter-逻辑入口 具体操作逻辑如下 public void ...

  8. Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步

    目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...

  9. VUEJS2.0源码理解--优

    VUEJS2.0源码理解 http://jiongks.name/blog/vue-code-review/#pingback-112428

随机推荐

  1. Mindjet Mindmanager复制文件打不开

    概述 使用Mindjet软件画思维导图,保存后得到一个后缀为mmap的文件.复制到一个新的位置,却发现新的文件打不开,导致Mindjet崩溃.这里提供一个解决方案. 解决方案 复制的文件打不开 先打开 ...

  2. HDU 6065 RXD, tree and sequence (LCA DP)

    RXD, tree and sequence Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java ...

  3. Sqli-labs less 3

    Less-3 我们使用?id=' 注入代码后,我们得到像这样的一个错误: MySQL server version for the right syntax to use near "&qu ...

  4. Linux命令之rlogin

    rlogin [-8EKLdx] [-e char] [-l username] host rlogin在远程主机host上开始一个终端会话. (1).选项 -8 选项允许进行8位的输入数据传送:否则 ...

  5. 【Kubernetes】在K8s中创建StatefulSet

    在K8s中创建StatefulSet 遇到的问题: 使用Deployment创建的Pod是无状态的,当挂在Volume之后,如果该Pod挂了,Replication Controller会再run一个 ...

  6. UGUI的优点新UI系统三效率高效果好

    UGUI的优点新UI系统三效率高效果好 通过对批处理(batching).纹理图集(texture atlasing)和新的canvas组件的支持,新UI系统提供了一个经过优化的解决方案,使得开发者添 ...

  7. Arduino可穿戴开发入门教程LilyPad介绍

    Arduino可穿戴开发入门教程LilyPad介绍 Arduino输出模块 LilyPad官方共提供了4种输出模块,他们分别是单色LED模块(图1.5).三色LED模块(图1.6).蜂鸣器模块(图1. ...

  8. Codeforces 551 D. GukiZ and Binary Operations

    \(>Codeforces \space 551 D. GukiZ and Binary Operations<\) 题目大意 :给出 \(n, \ k\) 求有多少个长度为 \(n\) ...

  9. POJ 2960 S-Nim 博弈论 sg函数

    http://poj.org/problem?id=2960 sg函数几乎是模板题. 调试代码的最大障碍仍然是手残在循环里打错变量名,是时候换个hydra产的机械臂了[超想要.jpg] #includ ...

  10. Luogu P3960 列队 线段树

    题面 线段树入门题. 我们考虑线段树来维护这个矩阵. 首先我们先定n+1棵线段树前n棵维护每行前m-1个同学中没有离队过的同学,还有一棵维护第m列中没有离队过的同学.再定n+1棵线段树前n棵线段树维护 ...