给网友们整理相关的编程文章,网友习俊彦根据主题投稿了本篇教程内容,涉及到Springboot、PropertySource数据结构、Springboot、PropertySource加载过程、SpringBoot源码 PropertySource、Springboot 源码 PropertySource相关内容,已被491网友关注,涉猎到的知识点内容可以在下方电子书获得。
Springboot 源码 PropertySource
SpringBoot源码 PropertySource以及解析
介绍
PropertySource是spring中对于键值属性的一种抽象,主要是name和source。source是任意对象,可以是对象,可以是Map,可以是List等等。但是一般常用的却不是source,而是抽象的方法getProperty。根据name,获取source中的值,也就是说由继承类来实现getProperty然后返回数据。例如如果souce是Map,那就从map中获取value值,如果Source是List,那可能就是找到遍历找到值,如果是对象就直接get获得值等等。接下来看下源码。
源码
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 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); } public static PropertySource<?> named(String name) { return new ComparisonPropertySource(name); } public static class StubPropertySource extends PropertySource<Object> { public StubPropertySource(String name) { super(name, new Object()); } /** * Always returns {@code null}. */ @Override public String getProperty(String name) { return null; } } static class ComparisonPropertySource extends StubPropertySource { private static final String USAGE_ERROR = "ComparisonPropertySource instances are for use with collection comparison only"; public ComparisonPropertySource(String name) { super(name); } @Override public Object getSource() { throw new UnsupportedOperationException(USAGE_ERROR); } @Override public boolean containsProperty(String name) { throw new UnsupportedOperationException(USAGE_ERROR); } @Override public String getProperty(String name) { throw new UnsupportedOperationException(USAGE_ERROR); } } }
在PropertySource中有两个内部类需要了解下,一个是StubPropertySource还有一个是ComparisonPropertySource;这两个内部类有什么作用呢?
StubPropertySource:其实一般PropertySource在使用的时候都是使用PropertySource的集合,所以在集合中就会有先后顺序的作用,这个StubPropertySource就是用作占位用的。
ComparisonPropertySource:既然PropertySource一般用作集合中,那如果要查找某个名称的PropertySource的位置(indexOf),那必须要是PropertySource才行,所以这时候就 new ComparisonPropertySource(name),就像named那个方法一样。 仅仅是用作在集合中的比较而已,通过源码中看到重写的equals和hashCode都是对于名称来计算的。
PropertyResolver
但是由于继承PropertySource的类很多,为了统一封装对外出口,所以spring就提供了一个PropertyResolver接口,实现PropertyResolver统一获取PropertySource的信息。
PropertyResolver是对于PropertySource对外提供数据的统一封装,并且其中还提供了类型转换的功能。由spring内部提供ConversionService对取得的数据进行相应的转换,转换成期望的数据类型。
接下来看下PropertyResolver的接口和具体实现
public interface PropertyResolver { boolean containsProperty(String key); // 获取数据 String getProperty(String key); // 获取数据,如果是空,默认使用defaultValue String getProperty(String key, String defaultValue); // 转化成指定类型的数据 <T> T getProperty(String key, Class<T> targetType); // 转化成指定类型的数据,如果是空,默认使用defaultValue <T> T getProperty(String key, Class<T> targetType, T defaultValue); @Deprecated <T> Class<T> getPropertyAsClass(String key, Class<T> targetType); String getRequiredProperty(String key) throws IllegalStateException; <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException; // 获取占位符的数据 String resolvePlaceholders(String text); // 占位符数据不允许为空,空就抛异常 String resolveRequiredPlaceholders(String text) throws IllegalArgumentException; }
PropertySourcesPropertyResolver
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver { @Nullable private final PropertySources propertySources; public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) { this.propertySources = propertySources; } @Override public boolean containsProperty(String key) { if (this.propertySources != null) { for (PropertySource<?> propertySource : this.propertySources) { if (propertySource.containsProperty(key)) { return true; } } } return false; } @Override @Nullable public String getProperty(String key) { return getProperty(key, String.class, true); } @Override @Nullable public <T> T getProperty(String key, Class<T> targetValueType) { return getProperty(key, targetValueType, true); } // 回调会用到 @Override @Nullable protected String getPropertyAsRawString(String key) { return getProperty(key, String.class, false); } @Nullable protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { if (this.propertySources != null) { // 遍历PropertySources获取PropertySource for (PropertySource<?> propertySource : this.propertySources) { if (logger.isTraceEnabled()) { logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'"); } // 从PropertySource中获取值 Object value = propertySource.getProperty(key); if (value != null) { if (resolveNestedPlaceholders && value instanceof String) { // 如果是允许解析占位符,解析占位符 value = resolveNestedPlaceholders((String) value); } logKeyFound(key, propertySource, value); // 转化成期望的类型 return convertValueIfNecessary(value, targetValueType); } } } if (logger.isDebugEnabled()) { logger.debug("Could not find key '" + key + "' in any property source"); } return null; } protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) { if (logger.isDebugEnabled()) { logger.debug("Found key '" + key + "' in PropertySource '" + propertySource.getName() + "' with value of type " + value.getClass().getSimpleName()); } } }
在PropertySourcesPropertyResolver主要是处理PropertySources,也就是PropertySource的集合,其实主要的就是getProperty的方法,从所有的PropertySource中获取资源,如果获取到了,并且允许解析占位符且值是String型,那就去解析占位符的值。解析了占位符之后,转化成期望的数据类型。 可以看到这边取到第一个满足条件的数据之后就会返回,这也是有些数据排在前面,然后优先的策略。比如命令行参数就比配置文件中参数优先。这些后面也会说到。
对于占位符的解析和数据类型的转化在在AbstractPropertyResolver方法中,继续看AbstractPropertyResolver方法。
package org.springframework.core.env; import java.util.LinkedHashSet; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.SystemPropertyUtils; public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver { protected final Log logger = LogFactory.getLog(getClass()); @Nullable private volatile ConfigurableConversionService conversionService; @Nullable private PropertyPlaceholderHelper nonStrictHelper; @Nullable private PropertyPlaceholderHelper strictHelper; private boolean ignoreUnresolvableNestedPlaceholders = false; private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX; private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; @Nullable private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR; private final Set<String> requiredProperties = new LinkedHashSet<>(); @Override public ConfigurableConversionService getConversionService() { // Need to provide an independent DefaultConversionService, not the // shared DefaultConversionService used by PropertySourcesPropertyResolver. ConfigurableConversionService cs = this.conversionService; if (cs == null) { synchronized (this) { cs = this.conversionService; if (cs == null) { cs = new DefaultConversionService(); this.conversionService = cs; } } } return cs; } @Override public void setConversionService(ConfigurableConversionService conversionService) { Assert.notNull(conversionService, "ConversionService must not be null"); this.conversionService = conversionService; } @Override public void setPlaceholderPrefix(String placeholderPrefix) { Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null"); this.placeholderPrefix = placeholderPrefix; } @Override public void setPlaceholderSuffix(String placeholderSuffix) { Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null"); this.placeholderSuffix = placeholderSuffix; } @Override public void setValueSeparator(@Nullable String valueSeparator) { this.valueSeparator = valueSeparator; } @Override public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) { this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders; } @Override public void setRequiredProperties(String... requiredProperties) { for (String key : requiredProperties) { this.requiredProperties.add(key); } } @Override public void validateRequiredProperties() { MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); for (String key : this.requiredProperties) { if (this.getProperty(key) == null) { ex.addMissingRequiredProperty(key); } } if (!ex.getMissingRequiredProperties().isEmpty()) { throw ex; } } @Override public boolean containsProperty(String key) { return (getProperty(key) != null); } @Override @Nullable public String getProperty(String key) { return getProperty(key, String.class); } @Override public String getProperty(String key, String defaultValue) { String value = getProperty(key); return (value != null ? value : defaultValue); } @Override public <T> T getProperty(String key, Class<T> targetType, T defaultValue) { T value = getProperty(key, targetType); return (value != null ? value : defaultValue); } @Override public String getRequiredProperty(String key) throws IllegalStateException { String value = getProperty(key); if (value == null) { throw new IllegalStateException("Required key '" + key + "' not found"); } return value; } @Override public <T> T getRequiredProperty(String key, Class<T> valueType) throws IllegalStateException { T value = getProperty(key, valueType); if (value == null) { throw new IllegalStateException("Required key '" + key + "' not found"); } return value; } @Override public String resolvePlaceholders(String text) { if (this.nonStrictHelper == null) { this.nonStrictHelper = createPlaceholderHelper(true); } return doResolvePlaceholders(text, this.nonStrictHelper); } @Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { if (this.strictHelper == null) { this.strictHelper = createPlaceholderHelper(false); } return doResolvePlaceholders(text, this.strictHelper); } // 是否允许占位符数据为空,创建不同的解析 protected String resolveNestedPlaceholders(String value) { return (this.ignoreUnresolvableNestedPlaceholders ? resolvePlaceholders(value) : resolveRequiredPlaceholders(value)); } private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) { return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders); } private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, this::getPropertyAsRawString); } @SuppressWarnings("unchecked") @Nullable protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) { if (targetType == null) { return (T) value; } ConversionService conversionServiceToUse = this.conversionService; if (conversionServiceToUse == null) { // Avoid initialization of shared DefaultConversionService if // no standard type conversion is needed in the first place... if (ClassUtils.isAssignableValue(targetType, value)) { return (T) value; } conversionServiceToUse = DefaultConversionService.getSharedInstance(); } return conversionServiceToUse.convert(value, targetType); } @Nullable protected abstract String getPropertyAsRawString(String key); }
上面AbstractPropertyResolver这么大一段,我们先看对于占位符的解析一块,resolveNestedPlaceholders方法中ignoreUnresolvableNestedPlaceholders默认是false,所以默认占位符是不允许为空的。所以调用resolveRequiredPlaceholders方法。
对于占位符的解析,AbstractPropertyResolver主要是调用PropertyPlaceholderHelper的通用处理方法对于占位符解析。这边再来看看PropertyPlaceholderHelper的方法对于占位符的解析;
public class PropertyPlaceholderHelper { private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class); private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4); static { wellKnownSimplePrefixes.put("}", "{"); wellKnownSimplePrefixes.put("]", "["); wellKnownSimplePrefixes.put(")", "("); } private final String placeholderPrefix; private final String placeholderSuffix; private final String simplePrefix; @Nullable private final String valueSeparator; private final boolean ignoreUnresolvablePlaceholders; public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) { this(placeholderPrefix, placeholderSuffix, null, true); } public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) { Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null"); Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null"); this.placeholderPrefix = placeholderPrefix; this.placeholderSuffix = placeholderSuffix; String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix); if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { this.simplePrefix = simplePrefixForSuffix; } else { this.simplePrefix = this.placeholderPrefix; } this.valueSeparator = valueSeparator; this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; } public String replacePlaceholders(String value, final Properties properties) { Assert.notNull(properties, "'properties' must not be null"); return replacePlaceholders(value, properties::getProperty); } public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { Assert.notNull(value, "'value' must not be null"); return parseStringValue(value, placeholderResolver, new HashSet<>()); } protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(value); // 占位符的前置字符位置 int startIndex = value.indexOf(this.placeholderPrefix); // 不存在直接返回 while (startIndex != -1) { // 查询占位符的后置位置 int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { // 去除包装的字符,得到真正占位符 //1 比如${abc}得到 abc String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; // 占位符放到集合中 if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } //2 递归调用 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); //3 获取配置中的值 String propVal = placeholderResolver.resolvePlaceholder(placeholder); if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { String actualPlaceholder = placeholder.substring(0, separatorIndex); //4 获取分隔符后的默认值 String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); if (propVal == null) { propVal = defaultValue; } } } if (propVal != null) { // 递归调用 propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); // 将要解析的值把占位符后的值替换成默认值 //5 如 ${abc} 取到的值是123 那么 ${abc}被替换成123 result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); if (logger.isTraceEnabled()) { logger.trace("Resolved placeholder '" + placeholder + "'"); } startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // 占位符的值允许为空,继续向后查找 startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { // 占位符的值不允许为空就抛出异常 throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\""); } visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return result.toString(); } private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { int index = startIndex + this.placeholderPrefix.length(); int withinNestedPlaceholder = 0; while (index < buf.length()) { // 一个字符一个字符向后查找 if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + this.placeholderSuffix.length(); } else { return index; } } else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { withinNestedPlaceholder++; index = index + this.simplePrefix.length(); } else { index++; } } return -1; } @FunctionalInterface public interface PlaceholderResolver { @Nullable String resolvePlaceholder(String placeholderName); } }
PropertyPlaceholderHelper对于占位符的解析简单说下,就是查找有没有被占位符,一般占位符需要有前后包装才可以,比如${abc}这样的。
1.去掉占位符的包装之后,得到内部的占位符,但是可能里面还嵌套占位符。
2.递归调用,直到拿到没有占位符为止。
3.根据占位符的值去配置中获取
4如果获取不到,占位符中有分隔符,那就拿分隔符后面的值,然后再去分隔符前面的值去取值。取到就用取到的值,取不到就用默认的值
5.然后把占位符换成获取到的值
这边说的可能有点绕,举个例子说下,比如要解析的是${abc:${def:gg}}这样嵌套的,
那么根据上面解析就是第一次获得abc:${def:gg},然后递归调用得到def:gg,那么就根据def:gg去PropertySource(这个是AbstractPropertyResolver之前方法中传递进来的,也就是那个getPropertyAsRawString方法)中去取数据,如果取到就用取到的值,如果取不到,就得到分隔符 : 后的gg这个默认值,然后再根据def去取,取不到就用gg这个值。然后再去把分隔符后面的值更新成取到的值如gg。那此时的值就是abc:gg。然后退出递归再次用abc:gg继续走流程。就和刚才def:gg那种一样。然后最后或得解析出来的值。
这就是PropertyPlaceholderHelper对于占位符的解析了。
总结
PropertySource是对键值的抽象,PropertyResolver是对PropertySource提供对外的统一数据处理,对于占位符的处理委托于PropertyPlaceholderHelper。介绍了这么多主要是为了继续下去springBoot源码启动里面的environment做准备的。因为environment就是包装PropertySource以及配置文件数据的地方。在spring中占有非常重要的地位。
PS:Springboot中PropertySource的数据结构及加载过程
记得之前写过一篇文章分析spring BeanFactory的时候说过的spring当中设计很经典的一个点就是 “读写分离
” 模式。使用这个模式可以很好的区分开框架与业务的使用上的侧重点。业务层不应该具有修改框架的特性。
所以讲Propertysource我们从Environment开始讲。我们知道我们平时在项目中拿到的Environment对象是只读,但是它可以被转换成可写的对象。
在springboot中当我们启动一个servlet应用的时候在prepareEnvironment 阶段实际上是new了一个StandardServletEnvironment
此时调用构造函数放了四个propertysource进去
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); } super.customizePropertySources(propertySources); }
super.customizePropertySources(propertySources)
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }
对应的名称分别为
- servletContextInitParams
- servletConfigInitParams
- jndiProperties 可选
- systemEnvironment
- systemProperties
对早期项目熟悉的同学可能,通过这几个参数能立马知道他们是如何演变过来的。
早期的servlet项目中有个web.xml配置(那么springboot是如何让它消失的呢?思考下)。这个配置中有这样的标签
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springMVC-servlet.xml</param-value> </context-param> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>home-page</param-name> <param-value>home.jsp</param-value> </init-param> </servlet>
这些参数是被servlet容器所解析的,同时也对spring进行了映射,包括jndi配置,即你在容器层面做的配置最终也会被映射到environment中。此处不是我们当前的重点不展开。
现在我们先来看看PropertySource这个类
PropertySource是个抽象类代表name/value键值对的一个资源,使用了泛型可以代表任意对象类型,例如可以是java.util.Properties,也可以是java.util.Map等
PropertySource对象通常不单独使用,而是通过对象聚合资源属性,结合PropertyResolver实现来解析资源对象,并根据优先级进行搜索。
可以使用@PropertySource 注解将对应的PropertySource 加入到Enviroment
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 other) { return (this == other || (other instanceof PropertySource && ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) other).name))); } @Override public int hashCode() { return ObjectUtils.nullSafeHashCode(this.name); } @Override public String toString() { if (logger.isDebugEnabled()) { return getClass().getSimpleName() + "@" + System.identityHashCode(this) + " {name='" + this.name + "', properties=" + this.source + "}"; } else { return getClass().getSimpleName() + " {name='" + this.name + "'}"; } } public static PropertySource<?> named(String name) { return new ComparisonPropertySource(name); } //StubPropertySource内部类,存根PropertySouece 目的是为了,延迟加载。 //即有些propertysource使用了一些占位符号,不能早于application context 加载,此时需要进行存根。 //等到对应的资源加载之后再加载当前的propertysource。占位符会在容器的refresh阶段被替换, //具体解析可以查看AbstractApplicationContext#initPropertySources() public static class StubPropertySource extends PropertySource<Object> { public StubPropertySource(String name) { super(name, new Object()); } /** * Always returns {@code null}. */ @Override @Nullable public String getProperty(String name) { return null; } } //静态内部类为了named方法使用,仅仅用户比较,调用其它方法会报异常,这里是个适配器模式。 static class ComparisonPropertySource extends StubPropertySource { private static final String USAGE_ERROR = "ComparisonPropertySource instances are for use with collection comparison only"; public ComparisonPropertySource(String name) { super(name); } @Override public Object getSource() { throw new UnsupportedOperationException(USAGE_ERROR); } @Override public boolean containsProperty(String name) { throw new UnsupportedOperationException(USAGE_ERROR); } @Override @Nullable public String getProperty(String name) { throw new UnsupportedOperationException(USAGE_ERROR); } } }
我们可以看到它预留了一个抽象方法getProperty 给子类实现,而此方法就是如何获取每个propertysource中的属性的value,此时就可以有各种各样的实现方式
- 例如:在SystemEnvironmentPropertySource 中 调用父类MapPropertySource 的getProperty方法实际是调用map.get方法获取对应的属性值
- 例如:CommandLinePropertySource中实际是调用CommandLineArgs的getNonOptionArgs()与getOptionValues(name)方法获取对应的属性值
- 例如:apollo实现的ConfigPropertySource实际上是调用System.getProperty(key);
以及Properties对象的get方法获取的属性值。
了解完这个结构之后我们后面再去看配置中心的实现,看起来就容易理解多了,此处按下不表。
其它的不多介绍,具体的类层次结构大家自行观察。大体上最后的数据结构基本上都是从hash表中获取对应的键值对。
了解完propertysource的数据结构之后,那么问题来了springboot什么时候加载了配置文件呢?又是如何解析成对应的propertysource呢?带着这个问题我们将整个流程贯穿起来看看就知道了。
所以我们先来看看ConfigFileApplicationListener这个类,如果你问我为什么看这个类,我会告诉你你可以全局内容搜索application.properties,当然最好是你有初略过了一遍springboot源码在来看会比较好。
ConfigFileApplicationListener实现了EnvironmentPostProcessor以及SmartApplicationListener这两个接口。我们知道实现了ApplicationListener接口的类会在spring启动阶段接收到各个环节的事件,所以我们直接查看onApplicationEvent方法
public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }
我们发现做了两个环节的处理,一个是环境装备完成的时候处理了一次,一个是容器准备完成时处理了一次,这两次的事件的执行时机分别如下
- ApplicationEnvironmentPreparedEvent:prepareEnvironment
- onApplicationPreparedEvent:prepareContext
在启动过程中prepareEnvironment 先执行所以这个事件的执行顺序为代码的逻辑顺序,先进第一个if条件再进第二个条件。
具体来看这两个方法
先看onApplicationEnvironmentPreparedEvent,遍历调用了一轮postProcessor.postProcessEnvironment
private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } }
当前类的postPorcessEnvironment方法添加了个RandomValuePropertySource,并并且new Loader 调用load方法在load方法中加载了application.properties文件,其它的逻辑就是如何找到这个文件以及如何加载这个文件具体细节自行研究,不多解释,加载的时候用到了PropertySourceLoader,对应的PropertySourceLoader有不同的实现,扩展名properties,xml使用PropertiesPropertySourceLoader 解析,而
“yml”, "yaml"使用YamlPropertySourceLoader加载,加载完成后就包装成MapPropertySource子类。并且将其设置给Environment。
public void load() { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); //初始化默认的profile=default initializeProfiles(); while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); } load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } resetEnvironmentProfiles(this.processedProfiles); load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); addLoadedPropertySources(); }
接着我们来看第二个事件的方法
第二个事件的方法添加了一个BeanFactoryPostProcessor 为PropertySourceOrderingPostProcessor,而BeanFactoryPostProcessor 是再refresh的InvokeBeanFactoryPostProcessor 阶段执行的。我们先看看它是如何执行的,postProcessBeanFactory调用了如下方法
private void reorderSources(ConfigurableEnvironment environment) { PropertySource<?> defaultProperties = environment.getPropertySources() .remove(DEFAULT_PROPERTIES); if (defaultProperties != null) { environment.getPropertySources().addLast(defaultProperties); } }
这个方法做了一件神奇的事情,因为默认配置是最先被放到环境容器中的,所以它在最前面,所以后续往里又添加了很多其它的propertysource之后,需要将它移动到最后,做一个兜底策略,最终就是取不到配置了再去取默认配置。
在结合开始的时候的数据结构,大概我们就可以总结出如下过程
1、环境准备阶段,广播了环境准备完成事件
2、调用listener方法onApplicationEvent去初始化了application.properties文件
3、使用PropertySourceLoader解析对应的文件并包装成propertysource
4、将propertysource设置给environment
5、容器准备阶段,广播了容器准备完成事件
6、调用listener方法onApplicationEvent去设置了一个BeanfactoryPostProcessor
7、在refresh阶段调用了这个postProcessor,调整了下默认配置文件的顺序。
具体的文件解析和占位符替换等等这些动作这里先不介绍了。
到此这篇关于Springboot中PropertySource的数据结构及加载过程的文章就介绍到这了,更多相关Springboot PropertySource数据结构内容请搜索码农之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持码农之家!