基于SpringBoot的Environment源碼理解實(shí)現(xiàn)分散配置
前提
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的源码:
public abstract class PropertySource<T> { protected final Log logger = LogFactory.getLog(getClass()); protected final String name; protected final T source; public PropertySource(String name, T source) { Assert.hasText(name, "Property source name must contain at least one character"); Assert.notNull(source, "Property source must not be null"); this.name = name; this.source = source; } @SuppressWarnings("unchecked") public PropertySource(String name) { this(name, (T) new Object()); } public String getName() { return this.name; } public T getSource() { return this.source; } public boolean containsProperty(String name) { return (getProperty(name) != null); } @Nullable public abstract Object getProperty(String name); @Override public boolean equals(Object obj) { return (this == obj || (obj instanceof PropertySource && ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name))); } @Override public int hashCode() { return ObjectUtils.nullSafeHashCode(this.name); } //省略其他方法和内部类的源码 }
源码相对简单,预留了一个getProperty
抽象方法给子类实现,重点需要关注的是覆写了的equals
和hashCode
方法,实际上只和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中的属性定义:
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";private final Set<String> activeProfiles = new LinkedHashSet<>();private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
上面的propertySources(MutablePropertySources类型)属性就是用来存放PropertySource列表的,PropertySourcesPropertyResolver是ConfigurablePropertyResolver的实现,默认的profile就是字符串default。MutablePropertySources的内部属性如下:
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
没错,这个就是最底层的存储容器,也就是环境属性都是存放在一个CopyOnWriteArrayList<PropertySource<?>>实例中。MutablePropertySources是PropertySources的子类,它提供了get(String name)
、addFirst
、addLast
、addBefore
、addAfter
、remove
、replace
等便捷方法,方便操作propertySourceList集合的元素,这里挑选addBefore
的源码分析:
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) { if (logger.isDebugEnabled()) { logger.debug("Adding PropertySource '" + propertySource.getName() + "' with search precedence immediately higher than '" + relativePropertySourceName + "'"); } //前一个PropertySource的name指定为relativePropertySourceName时候必须和添加的PropertySource的name属性不相同 assertLegalRelativeAddition(relativePropertySourceName, propertySource); //尝试移除同名的PropertySource removeIfPresent(propertySource); //获取前一个PropertySource在CopyOnWriteArrayList中的索引 int index = assertPresentAndGetIndex(relativePropertySourceName); //添加当前传入的PropertySource到指定前一个PropertySource的索引,相当于relativePropertySourceName对应的PropertySource后移到原来索引值+1的位置 addAtIndex(index, propertySource); }protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) { String newPropertySourceName = propertySource.getName(); if (relativePropertySourceName.equals(newPropertySourceName)) { throw new IllegalArgumentException( "PropertySource named '" + newPropertySourceName + "' cannot be added relative to itself"); } }protected void removeIfPresent(PropertySource<?> propertySource) { this.propertySourceList.remove(propertySource); }private int assertPresentAndGetIndex(String name) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); if (index == -1) { throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist"); } return index; }private void addAtIndex(int index, PropertySource<?> propertySource) { //注意,这里会再次尝试移除同名的PropertySource removeIfPresent(propertySource); this.propertySourceList.add(index, propertySource); }
大多数PropertySource子类的修饰符都是public,可以直接使用,这里写个小demo:
MutablePropertySources mutablePropertySources = new MutablePropertySources(); Map<String, Object> map = new HashMap<>(8); map.put("name", "throwable"); map.put("age", 25); MapPropertySource mapPropertySource = new MapPropertySource("map", map); mutablePropertySources.addLast(mapPropertySource); Properties properties = new Properties(); PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("prop", properties); properties.put("name", "doge"); properties.put("gourp", "group-a"); mutablePropertySources.addBefore("map", propertiesPropertySource); System.out.println(mutablePropertySources);
Environment加载过程源码分析
Environment加载的源码位于SpringApplication#prepareEnvironment
:
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment //创建ConfigurableEnvironment实例 ConfigurableEnvironment environment = getOrCreateEnvironment(); //启动参数绑定到ConfigurableEnvironment中 configureEnvironment(environment, applicationArguments.getSourceArgs()); //发布ConfigurableEnvironment准备完毕事件 listeners.environmentPrepared(environment); //绑定ConfigurableEnvironment到当前的SpringApplication实例中 bindToSpringApplication(environment); //这一步是非SpringMVC项目的处理,暂时忽略 if (this.webApplicationType == WebApplicationType.NONE) { environment = new EnvironmentConverter(getClassLoader()) .convertToStandardEnvironmentIfNecessary(environment); } //绑定ConfigurationPropertySourcesPropertySource到ConfigurableEnvironment中,name为configurationProperties,实例是SpringConfigurationPropertySources,属性实际是ConfigurableEnvironment中的MutablePropertySources ConfigurationPropertySources.attach(environment); return environment; }
这里重点看下getOrCreateEnvironment
方法:
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } //在SpringMVC项目,ConfigurableEnvironment接口的实例就是新建的StandardServletEnvironment实例 if (this.webApplicationType == WebApplicationType.SERVLET) { return new StandardServletEnvironment(); } return new StandardEnvironment(); }//REACTIVE_WEB_ENVIRONMENT_CLASS=org.springframework.web.reactive.DispatcherHandler//MVC_WEB_ENVIRONMENT_CLASS=org.springframework.web.servlet.DispatcherServlet//MVC_WEB_ENVIRONMENT_CLASS={"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"}//这里,默认就是WebApplicationType.SERVLETprivate WebApplicationType deduceWebApplicationType() { if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }
还有一个地方要重点关注:发布ConfigurableEnvironment准备完毕事件listeners.environmentPrepared(environment)
,实际上这里用到了同步的EventBus,事件的监听者是ConfigFileApplicationListener,具体处理逻辑是onApplicationEnvironmentPreparedEvent
方法:
private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); //遍历所有的EnvironmentPostProcessor对Environment实例进行处理 for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } }//从spring.factories文件中加载,一共有四个实例//ConfigFileApplicationListener//CloudFoundryVcapEnvironmentPostProcessor//SpringApplicationJsonEnvironmentPostProcessor//SystemEnvironmentPropertySourceEnvironmentPostProcessorList<EnvironmentPostProcessor> loadPostProcessors() { return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader()); }
实际上,处理工作大部分都在ConfigFileApplicationListener中,见它的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,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,先看它的构造函数:
@Nullableprivate final PropertySources propertySources;public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) { this.propertySources = propertySources; }
只依赖于一个PropertySources实例,在SpringBoot的SpringMVC项目中就是MutablePropertySources的实例。重点分析一下最复杂的一个方法:
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { if (this.propertySources != null) { //遍历所有的PropertySource for (PropertySource<?> propertySource : this.propertySources) { if (logger.isTraceEnabled()) { logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'"); } Object value = propertySource.getProperty(key); //选用第一个不为null的匹配key的属性值 if (value != null) { if (resolveNestedPlaceholders && value instanceof String) { //处理属性占位符,如${server.port},底层委托到PropertyPlaceholderHelper完成 value = resolveNestedPlaceholders((String) value); } logKeyFound(key, propertySource, value); //如果需要的话,进行一次类型转换,底层委托到DefaultConversionService完成 return convertValueIfNecessary(value, targetValueType); } } } if (logger.isDebugEnabled()) { logger.debug("Could not find key '" + key + "' in any property source"); } return null; }
这里的源码告诉我们,如果出现多个PropertySource中存在同名的key,返回的是第一个PropertySource对应key的属性值的处理结果,因此我们如果需要自定义一些环境属性,需要十分清楚各个PropertySource的顺序。
扩展-实现分散配置
在不使用SpringCloud配置中心的情况下,一般的SpringBoot项目的配置文件如下:
- src - main - resources - application-prod.yaml - application-dev.yaml - application-test.yaml
随着项目发展,配置项越来越多,导致了application-${profile}.yaml迅速膨胀,大的配置文件甚至超过一千行,为了简化和划分不同功能的配置,可以考虑把配置文件拆分如下:
- src - main - resources - profiles - dev - business.yaml - mq.json - datasource.properties - prod - business.yaml - mq.json - datasource.properties - test - business.yaml - mq.json - datasource.properties - application-prod.yaml - application-dev.yaml - application-test.yaml
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章