spring之环境和属性分析

1:PropertyResolver

在底层数据源之上定义一系列属性的操作,这些数据源可以是yaml,properties,甚至是nosql(如redis,也是基于key-value结构的),来看下源码:

// 基于任何的底层数据源来解析属性值
public interface PropertyResolver {

	// 返回是否包含key,不包含返回false,包含但是值为null,也是false
	boolean containsProperty(String key);

	// 获取key对应的属性值,如果无法解析,返回null
	@Nullable
	String getProperty(String key);

	// 返回key对应的属性值,如果无法解析则返回默认值defaultValue
	String getProperty(String key, String defaultValue);

	// 获取key对应的属性值,类型是Class<T> targetType中的T,如果是
	// 不存在则返回null
	@Nullable
	<T> T getProperty(String key, Class<T> targetType);
	
	// 获取key对应的属性值,类型是Class<T> targetType中的T,如果是
	// 不存在则返回T defaultValue
	<T> T getProperty(String key, Class<T> targetType, T defaultValue);

	// 获取key对应的属性值,如果是不存在则抛出IllegalStateException
	String getRequiredProperty(String key) throws IllegalStateException;
	
	// 获取key对应的属性值,并指定类型,如果是不存在则抛出IllegalStateException
	<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

	// 解析${...}占位符,如text为${spring.profiles.active},当spring.profiles.active=dev
	// 则结果就是dev,如果是不包含则结果不变,依然是${spring.profiles.active}
	String resolvePlaceholders(String text);

	// 解析${...}占位符,如text为${spring.profiles.active},当spring.profiles.active=dev
	// 则结果就是dev,如果是不包含则抛出IllegalArgumentException
	String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}

2:ConfigurablePropertyResolver

这是PropertyResolver的子类,从类名中的Configurable可以看出这是可配置的,比如配置属性转换使用的ConversionService,配置占位符的前缀后缀等,下面来看下源码:

// 提供了配置ConversionService,占位符前后缀,分隔符等方法的接口
public interface ConfigurablePropertyResolver extends PropertyResolver {

	// 返回执行属性转换的ConfigurableConversionService,可以add或者是remove自定义的Converter
	ConfigurableConversionService getConversionService();

	// 设置自己的ConfigurableConversionService,不常用,一般使用内部提供的,如果需要使用自定义的
	// Converter可以通过addConverter方法添加
	void setConversionService(ConfigurableConversionService conversionService);
	
	// 设置占位符的前缀
	void setPlaceholderPrefix(String placeholderPrefix);
	// 设置占位符的后缀
	void setPlaceholderSuffix(String placeholderSuffix);

	// 设置占位符的值和默认值之间的分割符,一般默认是“:”,当然也可以设置为其他
	void setValueSeparator(@Nullable String valueSeparator);

	// 当遇到解析不了的占位符时,是否抛出异常,为false时代表使用严格模式,抛出异常,为true时代表
	// 不使用严格模式,直接返回传入的${...},getProperty方法以及其变种方法必须检测这里的
	// 的值,以便在遇到无法解析的占位符时知道是抛出异常还是直接原样返回
	void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);

	// 设置必须提供的属性,可以通过validateRequiredProperties方法来进行验证
	void setRequiredProperties(String... requiredProperties);

	// 验证通过setRequiredProperties方法设置必须提供的属性是否都提供了,如果是没有提供
	// 或者是提供的值是null,则抛出MissingRequiredPropertiesException
	void validateRequiredProperties() throws MissingRequiredPropertiesException;

}

接下来我们来看该接口的抽象子类org.springframework.core.env.AbstractPropertyResolver,提供了基于任何底层数据源来解析属性的抽象基类,我们继续来这个类。

3:AbstractPropertyResolver

该抽象基类仅仅是定义了解析文件所需要的配置,如占位符的前后缀,ConvensionService等。

3.1:主要属性

// ConversionService
@Nullable
private volatile ConfigurableConversionService conversionService;
// 执行属性占位符替换的帮助类
@Nullable
private PropertyPlaceholderHelper nonStrictHelper;
@Nullable
private PropertyPlaceholderHelper strictHelper;
private boolean ignoreUnresolvableNestedPlaceholders = false;
// 占位符前缀
// public static final String PLACEHOLDER_PREFIX = "${";
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
// 占位符后缀
// public static final String PLACEHOLDER_SUFFIX = "}";
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
// 分隔符
// public static final String VALUE_SEPARATOR = ":";
@Nullable
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
// 必须存在的属性
private final Set<String> requiredProperties = new LinkedHashSet<>();

3.2:主要方法

// 获取ConversionService
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;
}

// 设置ConversionService
@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);
	}
}

具体的获取属性的操作子类org.springframework.core.env.PropertySourcesPropertyResolver中,接下来看下该类。

4:PropertySourcesPropertyResolver

该类中有一个重要的属性private final PropertySources propertySources;该类维护了一组PropertySource,我们先来看下该类:

// 维护一个或者是多个PropertySource的接口,PropertySource代表一个数据源
public interface PropertySources extends Iterable<PropertySource<?>> {
	default Stream<PropertySource<?>> stream() {
		return StreamSupport.stream(spliterator(), false);
	}
	// 是否包含指定名称的PropertySource
	boolean contains(String name);

	// 根据名称获取PropertySource
	@Nullable
	PropertySource<?> get(String name);
}

再来看下PropertySource接口,源码如下:

// 泛型T代表数据源类型,可以是java.util.Properties,也可以是java.util.HashMap,也可以是ServletContext,ServletConfig等
public abstract class PropertySource<T> {

	protected final Log logger = LogFactory.getLog(getClass());
	// 属性源的名称
	protected final String name;
	// 属性源
	protected final T source;


	// 通过给定的属性源名称和属性源创建PropertySource实例
	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;
	}

	/**
	 * Create a new {@code PropertySource} with the given name and with a new
	 * {@code Object} instance as the underlying source.
	 * <p>Often useful in testing scenarios when creating anonymous implementations
	 * that never query an actual source but rather return hard-coded values.
	 */
	// 使用给定的名称,并使用Object作为底层属性源创建PropertySource实例,一般用于测试场景中,
	// 比如直接硬编码一些值作为返回值
	@SuppressWarnings("unchecked")
	public PropertySource(String name) {
		this(name, (T) new Object());
	}

	// 返回本PropertySource的名称
	public String getName() {
		return this.name;
	}

	// 返回本PropertySource的底层数据源
	public T getSource() {
		return this.source;
	}

	// 返回PropertySource是否包含指定name的数据,子类可酌情重载具体实现
	public boolean containsProperty(String name) {
		return (getProperty(name) != null);
	}

	// 返回和给定的name关联的value
	@Nullable
	public abstract Object getProperty(String name);

}

所有的getProperty方法都是委托给getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders)这个三个参数的,所以我们重点来看下这个方法。

4.1:getProperty

org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>, boolean)
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
	// 存在propertySources才继续,否则直接返回null
	if (this.propertySources != null) {
		// 循环遍历PropertySources
		for (PropertySource<?> propertySource : this.propertySources) {
			if (logger.isTraceEnabled()) {
				logger.trace("Searching for key '" + key + "' in PropertySource '" +
						propertySource.getName() + "'");
			}
			// 调用当前propertySource的getProperty方法获取值
			Object value = propertySource.getProperty(key);
			// 如果值不为空
			if (value != null) {
				// 如果是需要解析占位符,并且值的类型是String,则解析占位符
				if (resolveNestedPlaceholders && value instanceof String) {
					// <20210428 1754>
					// 解析占位符
					value = resolveNestedPlaceholders((String) value);
				}
				logKeyFound(key, propertySource, value);
				// <20210428 1755>
				return convertValueIfNecessary(value, targetValueType);
			}
		}
	}
	if (logger.isTraceEnabled()) {
		logger.trace("Could not find key '" + key + "' in any property source");
	}
	// 没有propertySources或者是没有从propertySources中获取到值,则这里返回null
	return null;
}

<20210428 1754>处是解析占位符,具体参考4.2:resolveNestedPlaceholders,<20210428 1755>处是根据要求的目标类型对值进行类型转换,具体参考4.3:convertValueIfNecessary

4.2:resolveNestedPlaceholders

org.springframework.core.env.AbstractPropertyResolver#resolveNestedPlaceholders
protected String resolveNestedPlaceholders(String value) {
	// 根据属性不存在时是否抛出异常的布尔变量ignoreUnresolvableNestedPlaceholders
	// 决定调用resolvePlaceholders方法还是resolveRequiredPlaceholders方法,但是
	// 最终都会调用doResolvePlaceholders
	return (this.ignoreUnresolvableNestedPlaceholders ?
			resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
}

继续看doResolvePlaceholders

org.springframework.core.env.AbstractPropertyResolver#doResolvePlaceholders
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
	// <20210428 1812>
	return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

<20210428 1812>处是通过属性替换帮助类来完成占位符的替换,如配置的server.port=8888,则${server.port}最终结果就是8888,关于PropertyPlaceholderHelper,可以参考这里这里

4.3:convertValueIfNecessary

org.springframework.core.env.AbstractPropertyResolver#convertValueIfNecessary
protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
	// 如果是没有要求的目标类型,则直接强转返回
	if (targetType == null) {
		return (T) value;
	}
	ConversionService conversionServiceToUse = this.conversionService;
	if (conversionServiceToUse == null) {
		// 如果是满足强转的条件,则直接强转返回
		if (ClassUtils.isAssignableValue(targetType, value)) {
			return (T) value;
		}
		conversionServiceToUse = DefaultConversionService.getSharedInstance();
	}
	// 使用ConversionService的类型转换体系执行转换
	return conversionServiceToUse.convert(value, targetType);
}

5:简单总结

上面主要讲解了PropertyResolver,以及其实现类,PropertyResolver的作用就是从数据源中根据指定的key获取值,并在必要的条件下执行属性类型的转换,这里数据源来自PropertySource的API,类型转换来自ConversionService的API,因此可以用下图来说明这几者之间的关系:
在这里插入图片描述

6:Environment

全限定名称是org.springframework.core.env.Environment,是对应用程序运行环境的抽象的接口,表示当前程序正在运行的环境。在应用程序运行环境中首先由很多的配置文件,我们可以称之为properties,但是这些properties并非都是同时生效的,到底哪些生效通过叫做profile的配置项确定,而properties和profile也正是Environment的主要内容,总结如下:

properties:代表所有的配置信息,通过ProertyResolver来承载。
profile:决定properties中哪些配置项加载生效。

另外,如JVM系统属性,系统环境变量,servlet上下文参数,也都包含在Properties,所以一切和属性配置相关的获取等操作都可以通过Environment来完成,来看下接口源码:

// 代表当前正在运行的应用程序的环境的接口。为应用程序环境中的"profiles"和"properties"两个重要的
// 方面建立了模型。属性相关的访问方法通过ProeprtyResolver接口来提供。
public interface Environment extends PropertyResolver {

	// 返回environment中明确设置激活的profiles的集合。profiles是用来对bean定义进行逻辑分组,
	// 只有激活的profiels的分组的bean定义才会被进一步初始化为bean。Profiles可以通过
	// AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME"spring.profiles.active"作为
	// 系统属性值设置,或者是通过调用ConfigurableEnvironment#setActiveProfiles()方法设置。
	// 如果是没有明确激活的profiles设置,那么方法getDefaultProfiles()方法的返回值将会自动
	// 激活
	String[] getActiveProfiles();

	// 返回当没有明确的profiles设置时,默认激活的一组profiles
	String[] getDefaultProfiles();
	
	// 返回传入的profiles是否包含在明确设置的激活profiles,如果是没有明确设置激活的,则通过
	// 默认激活的来判断,这里多个profile是或的关系,一个在,则返回true,取反的话可以通过"!"
	@Deprecated
	boolean acceptsProfiles(String... profiles);
	
	// 返回getActiveProfiles是否匹配给定的Profiles
	boolean acceptsProfiles(Profiles profiles);
}

接着来看下Environment的类图:
在这里插入图片描述

7:ConfigurableEnvironment

可配置的Environment接口类,提供了针对激活的profiles,默认的profiles的设置的能力,以及操作Properties的能力。源码如下:

// 提供了设置激活的profiles和默认激活的profiles,操纵底层的proerptysource的机制。允许
// 用户设置和验证必须的属性,定制conversionservice,也可以基于ConfigurablePropertyResolver进行
// 其他的设置
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {

	// 设置Environment的激活的profiles集合。profiles用来在容器的启动期间决定是否将bean 定义注册到
	// 容器中。注意该方法是覆盖的方式来设置的,另外如果传入0个参数,则清空当前,想要添加新的profile
	// 可以通过调用addActiveProfile方法来完成
	void setActiveProfiles(String... profiles);

	// 添加信息的profile到当前的激活profiles集合中
	void addActiveProfile(String profile);

	// 设置默认激活的profiles,当没有通过方法setActiveProfiles明确设置激活的profiles时
	// 这里设置的默认的profiles生效
	void setDefaultProfiles(String... profiles);

	// 以可修改的方式返回当前Enviroment的PropertySources,允许操作当获取属性时用来搜索的PropertySource
	// 操作常用的API,如addFirst,addLast,addBefore,addAfter,可以根据实际情况的属性获取
	// 的优先级来选择合适的API添加PropertySource
	MutablePropertySources getPropertySources();

	// 返回通过System.getProperties方法返回的map。大部分的Environment的实现都会将system properties
	// 作为一个默认的PropertySource来搜索,因此,该方法不建议直接使用,而是直接通过Environment
	// 来直接获取属性
	Map<String, Object> getSystemProperties();

	// 获取System.getEnv的结果
	Map<String, Object> getSystemEnvironment();

	// 追加parent的active profiels,default profiles,以及property sources到当前的(子)Environment中
	// 对于重名的PropertySource,保留子的,废弃父的,即保留当前类的
	void merge(ConfigurableEnvironment parent);

}

接下来看抽象实现类org.springframework.core.env.AbstractEnvironment

8:AbstractEnvironment

8.1:properties相关

定义了钩子方法org.springframework.core.env.AbstractEnvironment#customizePropertySources,该方法是个空实现,子类,可以通过重载该方法,设置自定义的属性源PropertySource,源码如下:

// 创建Environment实例,会通过回调customizePropertySources的方法
// ,因此子类可以复写该方法来设置自定义的属性源
public AbstractEnvironment() {
	// 回调钩子方法customizePropertySources
	customizePropertySources(this.propertySources);
}

// 设置通过getProperty(xxx)等方法搜索属性时,所查找的PropertySource的集合信息
protected void customizePropertySources(MutablePropertySources propertySources) {
}

获取可修改数据源方法:

org.springframework.core.env.AbstractEnvironment#getPropertySources
@Override
public MutablePropertySources getPropertySources() {
	return this.propertySources;
}

获取后就可以通过org.springframework.core.env.MutablePropertySourcesaddXxx的相关API来进行属性源的动态添加了。

8.2:profile相关

// 定义设计激活profiles的key
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
// 激活的profiles的集合
private final Set<String> activeProfiles = new LinkedHashSet<>();
// 定义默认激活的profiles的key
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
// 默认激活的profiles的集合
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());

获取profiles:

org.springframework.core.env.AbstractEnvironment#getActiveProfiles
@Override
public String[] getActiveProfiles() {
	return StringUtils.toStringArray(doGetActiveProfiles());
}
protected Set<String> doGetActiveProfiles() {
	synchronized (this.activeProfiles) {
		// 为空
		if (this.activeProfiles.isEmpty()) {
			// 从属性源中获取属性名称为ACTIVE_PROFILES_PROPERTY_NAME
			// 的激活profiles(,分割)
			String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
			// 如果是在属性源中设置了
			if (StringUtils.hasText(profiles)) {
				// 去除空格,并转换为数组,设置到激活的profiles中
				// 这里的代码说明,我们只需要设置到环境中如"-Dspring.profiles.active=aa,bb" 
				// 这里就会自动set了
				setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
						StringUtils.trimAllWhitespace(profiles)));
			}
		}
		return this.activeProfiles;
	}
}

设置profile:

org.springframework.core.env.AbstractEnvironment#setActiveProfiles
public void setActiveProfiles(String... profiles) {
	Assert.notNull(profiles, "Profile array must not be null");
	if (logger.isDebugEnabled()) {
		logger.debug("Activating profiles " + Arrays.asList(profiles));
	}
	synchronized (this.activeProfiles) {
		// 清空已存在的
		this.activeProfiles.clear();
		// 新的
		for (String profile : profiles) {
			// 校验,主要校验不为空,不以!开头,子类
			// 可以提供更加强大和全面的校验
			validateProfile(profile);
			// 添加到集合中
			this.activeProfiles.add(profile);
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值