目录
- 主要相关类介绍
- PropertySource
- PropertySources
- PropertySourceLoader
- PropertySourcesLoader
- PropertyResolver
- Environment
- StandardServletEnvironment
- ConfigFileApplicationListener
- ConfigFileApplicationListener.Loader
主要相关类介绍
PropertySource
是Spring对name/value键值对的封装接口。该定义了getSource()方法,这个方法会返回得到属性源的源头。比如MapPropertySource的源头就是一个Map,PropertiesPropertySource的源头就是一个Properties。
public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
protected final String name;
protected final T source;
/**
* Create a new {@code PropertySource} with the given name and source object.
*/
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;
}
/**
* Return the name of this {@code PropertySource}
*/
public String getName() {
return this.name;
}
/**
* Return the underlying source object for this {@code PropertySource}.
*/
public T getSource() {
return this.source;
}
/**
* Return whether this {@code PropertySource} contains the given name.
*/
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
/**
* Return the value associated with the given name,
*/
public abstract Object getProperty(String name);
}
PropertySource目前的实现类有不少,比如上面提到的MapPropertySource和PropertiesPropertySource,还有RandomValuePropertySource(source是Random)、SimpleCommandLinePropertySource(source是CommandLineArgs,命令行参数)、ServletConfigPropertySource(source是ServletConfig)等等。
PropertySources
/**
* Holder containing one or more {@link PropertySource} objects.
*/
public interface PropertySources extends Iterable<PropertySource<?>> {
/**
* Return whether a property source with the given name is contained.
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
boolean contains(String name);
/**
* Return the property source with the given name, {@code null} if not found.
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
PropertySource<?> get(String name);
}
PropertySource的管理器,具体的实现有MutablePropertySources
PropertySourceLoader
- 用于将一个文件资源(Resource)生成配置属性资源(PropertySource)
- SpringBoot把配置文件的加载封装成了PropertySourceLoader接口,该接口的定义如下
/** * Strategy interface located via {@link SpringFactoriesLoader} and used to load a * {@link PropertySource}. */ public interface PropertySourceLoader { /** * Returns the file extensions that the loader supports (excluding the '.'). */ String[] getFileExtensions(); /** * Load the resource into a property source. */ PropertySource<?> load(String name, Resource resource, String profile) throws IOException; }
- getFileExtensions: 该加载器支持的文件类型
- load(String name, Resource resource, String profile): 加载该resource为PropertySource的某个实现类
- SpringBoot 的配置文件内置支持 properties、xml、yml、yaml 几种格式,其中 properties和xml 对应的Loader类为 PropertiesPropertySourceLoader ,yml和yaml 对应的Loader类为 YamlPropertySourceLoader。观察这2个类可以发现,都实现自接口 PropertySourceLoader
下面实现了一个 json 格式的配置文件 Loader类:
package com.shanhy.sboot.property;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
/**
* JSON格式配置文件加载器
*
* @create 2017年4月20日
*/
public class JsonPropertySourceLoader implements PropertySourceLoader {
public String[] getFileExtensions() {
// 配置文件格式(扩展名)
return new String[] { "json" };
}
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException {
// 处理机制参考PropertiesPropertySourceLoader
// 无论profile有没有值,底层都会尝试先执行 load(String name, Resource resource, null),所以这个地方之间判断等于null即可。
// 当前版本springboot-1.5.2(后续版本未知)详见 ConfigFileApplicationListener 的 445 行
if (profile == null) {
Map<String, Object> result = mapPropertySource(resource);
return new MapPropertySource(name, result);
}
return null;
}
/**
* 解析Resource为Map
*
* @param resource
* @return
* @throws IOException
*
* @create 2017年4月20日
*/
private Map<String, Object> mapPropertySource(Resource resource) throws IOException {
if (resource == null) {
return null;
}
Map<String, Object> result = new HashMap<String, Object>();
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, Object> map = parser.parseMap(readFile(resource));
nestMap("", result, map);
return result;
}
/**
* 读取Resource文件内容为字符串
*
* @param resource
* @return
* @throws IOException
*
* @create 2017年4月20日
*/
private String readFile(Resource resource) throws IOException {
InputStream inputStream = resource.getInputStream();
List<Byte> byteList = new LinkedList<Byte>();
byte[] readByte = new byte[1024];
int length;
while ((length = inputStream.read(readByte)) > 0) {
for (int i = 0; i < length; i++) {
byteList.add(readByte[i]);
}
}
byte[] allBytes = new byte[byteList.size()];
int index = 0;
for (Byte soloByte : byteList) {
allBytes[index] = soloByte;
index += 1;
}
return new String(allBytes, "UTF-8");
}
/**
* 处理map(map中可能还嵌套map,递归处理),最终输出一个非嵌套的map
*
* @param prefix
* 前缀
* @param result
* 处理后的map
* @param map
* 处理前的map
*
* @create 2017年4月20日
*/
@SuppressWarnings("unchecked")
private void nestMap(String prefix, Map<String, Object> result, Map<String, Object> map) {
if (prefix.length() > 0) {
prefix += ".";
}
for (Map.Entry<String, Object> entrySet : map.entrySet()) {
if (entrySet.getValue() instanceof Map) {
nestMap(prefix + entrySet.getKey(), result, (Map<String, Object>) entrySet.getValue());
} else {
result.put(prefix + entrySet.getKey().toString(), entrySet.getValue());
}
}
}
}
然后在 src/main/resources 中创建 META-INF/spring.factories 文件,内容为:
org.springframework.boot.env.PropertySourceLoader=\
com.shanhy.sboot.property.JsonPropertySourceLoader
创建测试的配置文件 application.json
{
"custom": {
"property": {
"message": "测试数据"
}
}
}
创建验证结果的 HelloController.java
@RestController
public class HelloController {
@Value("${custom.property.message:}")
private String customProperty;
@RequestMapping("/test")
public String test() {
return customProperty;
}
}
PropertySourcesLoader
- PropertySourcesLoader内部有2个属性,分别是PropertySourceLoader集合和MutablePropertySources(内部有PropertySource的集合)。最终加载完毕之后MutablePropertySources属性中的PropertySource会被添加到环境Environment中的属性源列表中。PropertySourcesLoader被构造的时候会使用工厂加载机制获得PropertySourceLoader集合(默认就2个:PropertiesPropertySourceLoader和YamlPropertySourceLoader;可以自己扩展),然后设置到属性中
- 该类存在四个多态load()方法,最终会循环所有的PropertySourceLoader,判断是否支持该文件类型,如果支持则通过该loader进行加载
- 在加载后会通过addPropertySource()方法,将生成 的PropertySource存入MutablePropertySources
- PropertySourcesLoader.getAllFileExtensions():获取所有支持的配置文件类型,实际为循环所有的PropertySourcesLoader的子加载器,获取每个支持的配置文件类型(loader.getFileExtensions())
PropertyResolver
/**
* Interface for resolving properties against any underlying source.
* 针对所有的source提供properties的接口
*/
public interface PropertyResolver {
/**
* Return whether the given property key is available for resolution,
* i.e. if the value for the given key is not {@code null}.
*/
boolean containsProperty(String key);
/**
* Return the property value associated with the given key,
* or {@code null} if the key cannot be resolved.
*/
String getProperty(String key);
/**
* Return the property value associated with the given key, or
* {@code defaultValue} if the key cannot be resolved.
*/
String getProperty(String key, String defaultValue);
/**
* Return the property value associated with the given key,
* or {@code null} if the key cannot be resolved.
*/
<T> T getProperty(String key, Class<T> targetType);
/**
* Return the property value associated with the given key,
* or {@code defaultValue} if the key cannot be resolved.
*/
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
/**
* Convert the property value associated with the given key to a {@code Class}
* of type {@code T} or {@code null} if the key cannot be resolved.
* @throws org.springframework.core.convert.ConversionException if class specified
* by property value cannot be found or loaded or if targetType is not assignable
* from class specified by property value
*/
@Deprecated
<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
/**
* Return the property value associated with the given key (never {@code null}).
* @throws IllegalStateException if the key cannot be resolved
*/
String getRequiredProperty(String key) throws IllegalStateException;
/**
* Return the property value associated with the given key, converted to the given
* targetType (never {@code null}).
* @throws IllegalStateException if the given key cannot be resolved
*/
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
* property values as resolved by {@link #getProperty}. Unresolvable placeholders with
* no default value are ignored and passed through unchanged.
*/
String resolvePlaceholders(String text);
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
* property values as resolved by {@link #getProperty}. Unresolvable placeholders with
* no default value will cause an IllegalArgumentException to be thrown.
* @return the resolved String (never {@code null})
* @throws IllegalArgumentException if given text is {@code null}
* or if any placeholders are unresolvable
*/
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
PropertyResolver属性解决器,主要具有两个功能:
- 通过propertyName属性名获取与之对应的propertValue属性值(getProperty)。
- 把${propertyName:defaultValue}格式的属性占位符,替换为实际的值(resolvePlaceholders)。
注意:getProperty获取的属性值,全都是调用resolvePlaceholders进行占位符替换后的值。
组件体系图如下:
Environment
/**
* Interface representing the environment in which the current application is running.
* Models two key aspects of the application environment: <em>profiles</em> and
* <em>properties</em>. Methods related to property access are exposed via the
* {@link PropertyResolver} superinterface.
*/
public interface Environment extends PropertyResolver {
/**
* Return the set of profiles explicitly made active for this environment. Profiles
* are used for creating logical groupings of bean definitions to be registered
* conditionally, for example based on deployment environment. Profiles can be
* activated by setting {@linkplain AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
* "spring.profiles.active"} as a system property or by calling
* {@link ConfigurableEnvironment#setActiveProfiles(String...)}.
* <p>If no profiles have explicitly been specified as active, then any
* {@linkplain #getDefaultProfiles() default profiles} will automatically be activated.
*/
String[] getActiveProfiles();
/**
* Return the set of profiles to be active by default when no active profiles have
* been set explicitly.
*/
String[] getDefaultProfiles();
/**
* Return whether one or more of the given profiles is active or, in the case of no
* explicit active profiles, whether one or more of the given profiles is included in
* the set of default profiles. If a profile begins with '!' the logic is inverted,
* i.e. the method will return true if the given profile is <em>not</em> active.
* For example, <pre class="code">env.acceptsProfiles("p1", "!p2")</pre> will
* return {@code true} if profile 'p1' is active or 'p2' is not active.
* @throws IllegalArgumentException if called with zero arguments
* or if any profile is {@code null}, empty or whitespace-only
*/
boolean acceptsProfiles(String... profiles);
}
- Environment接口是Spring对当前程序运行期间的环境的封装。主要提供了两大功能:profile和property(父接口PropertyResolver提供)。
- 目前主要有StandardEnvironment、StandardServletEnvironment和MockEnvironment3种实现,分别代表普通程序、Web程序以及测试程序的环境。
StandardServletEnvironment
关系图
/**
* {@link Environment} implementation to be used by {@code Servlet}-based web
* applications. All web-related (servlet-based) {@code ApplicationContext} classes
* initialize an instance by default.
*/
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
/** Servlet context init parameters property source name: {@value} */
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
/** Servlet config init parameters property source name: {@value} */
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
/** JNDI property source name: {@value} */
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
/**
* Customize the set of property sources with those contributed by superclasses as
* well as those appropriate for standard servlet-based environments:
* 通过父类提供的source及适用于标准servlet的环境自定义property sources 集合,
*/
@Override
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);
}
@Override
public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
}
ConfigFileApplicationListener
SpringApplication启动的时候会使用工厂加载机制初始化一些初始化器和监听器。其中org.springframework.boot.context.config.ConfigFileApplicationListener这个监听器会被加载:
// spring-boot-version.release/META-INF/spring.factories
org.springframework.context.ApplicationListener=\
...
org.springframework.boot.context.config.ConfigFileApplicationListener,\
...
ConfigFileApplicationListener会监听SpringApplication启动的时候发生的事件,它的监听代码:
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 应用环境信息准备好的时候对应的事件。此时Spring容器尚未创建,但是环境已经创建
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
// Spring容器创建完成并在refresh方法调用之前对应的事件
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
// 使用工厂加载机制读取key为org.springframework.boot.env.EnvironmentPostProcessor的实现类
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 加上自己。ConfigFileApplicationListener也是一个EnvironmentPostProcessor接口的实现类
postProcessors.add(this);
// 排序
AnnotationAwareOrderComparator.sort(postProcessors);
// 遍历这些EnvironmentPostProcessor,并调用postProcessEnvironment方法
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
ConfigFileApplicationListener也是一个EnvironmentPostProcessor接口的实现类,在这里会被调用:
// ConfigFileApplicationListener的postProcessEnvironment方法
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// 添加属性源到环境中
addPropertySources(environment, application.getResourceLoader());
// 配置需要ignore的beaninfo
configureIgnoreBeanInfo(environment);
// 从环境中绑定一些参数到SpringApplication中
bindToSpringApplication(environment, application);
}
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
// 添加一个RandomValuePropertySource到环境中
// RandomValuePropertySource是一个用于处理随机数的PropertySource,内部存储一个Random类的实例
RandomValuePropertySource.addToEnvironment(environment);
try {
// 构造一个内部类Loader,并调用它的load方法
new Loader(environment, resourceLoader).load();
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load configuration files", ex);
}
}
如上,在执行postProcessEnvironment时,Loader.load()方法将会调用
ConfigFileApplicationListener.Loader
加载候选属性源并配置活动配置文件
- load()方法会在
file:./config/
,file:./
,classpath:/config/
,classpath:/
目录(location)下查找某个文件 - load(String location, String name, Profile profile)会查找properties、xml、yml、yaml中某个类型的配置文件,如resources/bootstrap.yml
- 最终会调用PropertySourcesLoader.load(Resource resource, String group, String name,String profile),在该方法中,会通过对应的PropertySourceLoader的实现类加载该配置文件
- 加载完成后,会将加载到的配置添加至EnumerableCompositePropertySource,EnumerableCompositePropertySource中维护了一个集合LinkedHashSet<PropertySource<?>>()
- 然后将EnumerableCompositePropertySource添加到MutablePropertySources中,该类中维护了一个集合List<PropertySource<?>>
值得注意的是EnumerableCompositePropertySource是一个PropertySource而MutablePropertySources是一个PropertySources
application.yml加载过程
在特定的目录下扫描配置文件,然后由于是.yml文件,故通过YamlPropertySourceLoader加载该配置文件
consul排序乱了,但是application.yml是有序的原因
是由于加载application.yml是通过YamlPropertySourceLoader加载的,该类中调用YamlProcessor.process(MatchCallback callback)时,result为LinkedHashMap<String, Object>(),是有序的
而consul是通过YamlPropertiesFactoryBean加载的,该类调用YamlProcessor.process(MatchCallback callback)时,result为Properties,是无序的,故consul是无序的
consul为什么使用YamlPropertiesFactoryBean而不使用YamlPropertySourceLoader
因为consul是从consul服务器拉取文件,文件是二进制,并不是File形式,无法直接使用YamlPropertySourceLoader,故需要通过YamlPropertiesFactoryBean以二进制的形式生成Properties,而Properties是继承HashTable实现的,故是无序的;
参考ConsulPropertySourceLocator.locator()方法
扩展
- 开发者可通过开发PropertySourceLocator的实现类,加载配置文件
- 如 从数据库,mongo库等加载配置文件
- 如 从数据库读取zuul的路由,权限管理等