Spring源码之配置文件解析以及Environment


前言

  这篇文章主要讲的是Spring对配置文件的解析,Environment详解以及@Value注解的相关应用。


一、Spring配置文件解析

假如Spring要通过占位符的方式进行属性赋值的话,那么需要在配置文件里面配置以下标签

<context:property-placeholder location="classpath:application.properties"/>

那么就需要解析这个标签,context标签的解析在以前的博客中有讲,通过spring.handles文件中,找到context标签对应的解析类ContextNamespaceHandler

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

}

找到占位符的解析类PropertyPlaceholderBeanDefinitionParser,这个类中有个getBeanClass方法,这个方法返回的就是将占位符替换为具体值的类

protected Class<?> getBeanClass(Element element) {
	(SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
		//将占位符替换为具体值的类,先记住这个类
		return PropertySourcesPlaceholderConfigurer.class;
	}
	return org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.class;
}

我们回到解析自定义标签的时候,需要调用解析类的parse方法

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
//这个getBeanClass子类重写了,返回的PropertySourcesPlaceholderConfigurer.class
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
	builder.getRawBeanDefinition().setBeanClass(beanClass);
}

由此可见,context:property-placeholder标签的解析就是创建一个PropertySourcesPlaceholderConfigurer的BeanDefinition并注册到bean共产中。

Spring启动过程中会将bean的相关属性封装到BeanDefinition中,我找到GenericBeanDefinition
里面有个private MutablePropertyValues propertyValues;这个属性放置的就是bean的属性值
在这里插入图片描述
这里有个List集合,里面放置着PropertyValue,name和value分别对应着属性的key和value

public PropertyValue(String name, @Nullable Object value) {
	Assert.notNull(name, "Name must not be null");
	this.name = name;
	this.value = value;
}

在未进行占位符替换的时候,value的值应该是占位符${}的形式,那么又是什么时候进行替换的呢,我们继续看前面的这个类PropertySourcesPlaceholderConfigurer,这个类实现了BeanFactoryPostProcessor接口,那么里面一定有postProcessBeanFactory这个方法,那么这个方法做了些什么呢

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	if (this.propertySources == null) {
		this.propertySources = new MutablePropertySources();
		if (this.environment != null) {
			//添加Environment
			this.propertySources.addLast(
				new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
					@Override
					@Nullable
					public String getProperty(String key) {
						return this.source.getProperty(key);
					}
				}
			);
		}
		try {
			//添加本地配置文件的属性源
			PropertySource<?> localPropertySource =
					new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
			if (this.localOverride) {
				this.propertySources.addFirst(localPropertySource);
			}
			else {
				this.propertySources.addLast(localPropertySource);
			}
		}
		catch (IOException ex) {
			throw new BeanInitializationException("Could not load properties", ex);
		}
	}
	//替换占位符
	processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
	this.appliedPropertySources = this.propertySources;
}

进入到processProperties方法

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
		final ConfigurablePropertyResolver propertyResolver) throws BeansException {

	//设置占位符的前缀后缀
	propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
	propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
	//占位符的分隔符
	propertyResolver.setValueSeparator(this.valueSeparator);

	//重点是这个匿名对象,@Value的依赖会注入进来
	StringValueResolver valueResolver = strVal -> {
		String resolved = (this.ignoreUnresolvablePlaceholders ?
				propertyResolver.resolvePlaceholders(strVal) :
				propertyResolver.resolveRequiredPlaceholders(strVal));
		if (this.trimValues) {
			resolved = resolved.trim();
		}
		return (resolved.equals(this.nullValue) ? null : resolved);
	};
	//核心流程,把占位符${}替换成真正的值
	doProcessProperties(beanFactoryToProcess, valueResolver);
}

进入到核心流程doProcessProperties

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
		StringValueResolver valueResolver) {

	//BeanDefinition的修改者
	BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

	String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
	for (String curName : beanNames) {
		// Check that we're not parsing our own bean definition,
		// to avoid failing on unresolvable placeholders in properties file locations.
		if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
			BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
			try {
				//修改BeanDefinition中的MutablePropertyValues中的每一个属性值,把属性值有${}替换成真正的值
				visitor.visitBeanDefinition(bd);
			}
			catch (Exception ex) {
				throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
			}
		}
	}

	// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
	beanFactoryToProcess.resolveAliases(valueResolver);

	// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
	//用来解决嵌入值(例如注释属性)中的占位符
	beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

接下来进入到visitor.visitBeanDefinition(bd);

public void visitBeanDefinition(BeanDefinition beanDefinition) {
	visitParentName(beanDefinition);
	visitBeanClassName(beanDefinition);
	visitFactoryBeanName(beanDefinition);
	visitFactoryMethodName(beanDefinition);
	visitScope(beanDefinition);
	//如果BeanDefinition存在属性值,则把占位符替换成真正的值
	if (beanDefinition.hasPropertyValues()) {
		visitPropertyValues(beanDefinition.getPropertyValues());
	}
	if (beanDefinition.hasConstructorArgumentValues()) {
		ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
		visitIndexedArgumentValues(cas.getIndexedArgumentValues());
		visitGenericArgumentValues(cas.getGenericArgumentValues());
	}
}

从这个方法可以知道,不仅仅可以替换属性的占位符,还可以替换parentName,beanClassName,factoryBeanName,factoryMethodName,我们直接看属性的占位符替换visitPropertyValues

protected void visitPropertyValues(MutablePropertyValues pvs) {
	PropertyValue[] pvArray = pvs.getPropertyValues();
	for (PropertyValue pv : pvArray) {
		Object newVal = resolveValue(pv.getValue());
		if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
			pvs.add(pv.getName(), newVal);
		}
	}
}

直接以字符串为例,忽略中间过程,直接找到解析方法

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
	return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

这里的text就是我们占位符字符串,例如我们要处理的属性${},第二个参数PlaceholderResolver,这是一个函数式接口,所以参数this::getPropertyAsRawString,等价于下面:

return new PlaceholderResolver(){
	@Override
	String resolvePlaceholder(String placeholderName){
	    //这里调用到的是PropertySourcesPropertyResolver#getPropertyAsRawString
	    return getPropertyAsRawString(placeholderName);
	}
}       

最终会调用到String propVal = placeholderResolver.resolvePlaceholder(placeholder);这里,也就是上面的函数式接口

protected String getPropertyAsRawString(String key) {
	return getProperty(key, String.class, false);
}

调用到getProperty

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
	//其实就是从MutablePropertySources中的list中获取每一个PropertySource对象然后调用getProperty方法
	if (this.propertySources != null) {
		for (PropertySource<?> propertySource : this.propertySources) {
			if (logger.isTraceEnabled()) {
				logger.trace("Searching for key '" + key + "' in PropertySource '" +
						propertySource.getName() + "'");
			}
			//掉用getProperty方法,属性来源有environment和配置文件
			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.isTraceEnabled()) {
		logger.trace("Could not find key '" + key + "' in any property source");
	}
	return null;
}

从属性源中得到占位符对应的实际值,将占位符的值进行替换typedStringValue.setValue(visitedString);

二、Environment解析

Environment是一个接口,继承PropertyResolver接口,里面有String getProperty(String key)的抽象方法,我们先有一个大体的印象。
在Spring启用时,会先设置配置文件setConfigLocations(configLocations);,在这个实际会实例化Environment

public ConfigurableEnvironment getEnvironment() {
	if (this.environment == null) {
		this.environment = createEnvironment();
	}
	return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
	return new StandardEnvironment();
}

我们进入到StandardEnvironment

protected void customizePropertySources(MutablePropertySources propertySources) {
	propertySources.addLast(
			new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
	propertySources.addLast(
			new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

仔细看customizePropertySources这个方法,里面是不是和获取本地配置文件的属性源的结构类似,这里我们重点关注getSystemPropertiesgetSystemEnvironment,点到方法实现中去看,可以看出它们分别返回

//getSystemProperties
return (Map) System.getProperties();
//getSystemEnvironment
return (Map) System.getenv();

看到这里真想大白了,Environment封装了系统变量和JVM变量,实际上就是一个Map,这里也可以验证getProperty(String key),实际上就是从Map中拿到对应的属性值。

三、@Value注解解析

@Value的用法和@Autowired的用法类似,不同的是@Value注解的属性没有默认值

public @interface Value {

	/**
	 * The actual value expression such as <code>#{systemProperties.myProp}</code>
	 * or property placeholder such as <code>${my.app.myProp}</code>.
	 */
	String value();

}

value不仅仅可以使用真实的值,还可以使用占位符的形式,这里又是怎么进行解析的呢
调用PropertySourcesPlaceholderConfigurer#postProcessBeanFactory的时候,会执行

//把占位符解析器放到内嵌的Value解析器中
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);

在进行属性依赖注入的时候,执行下面的操作

value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
...
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
..
//获取@Value中的值
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
...
String strVal = resolveEmbeddedValue((String) value);

进入到resolveEmbeddedValue方法中

public String resolveEmbeddedValue(@Nullable String value) {
	if (value == null) {
		return null;
	}
	String result = value;
	for (StringValueResolver resolver : this.embeddedValueResolvers) {
		//解析字符串格式的方法
		result = resolver.resolveStringValue(result);
		if (result == null) {
			return null;
		}
	}
	return result;
}

embeddedValueResolvers这个就是内嵌Value解析容器,所以这里用的解析器就是上文添加进来的占位符解析器,在这里就和上文中描述的配置文件解析流程殊途同归了。


总结

配置文件解析需要解析类PropertySourcesPlaceholderConfigurer实例化后对占位符进行替换

@Component
public class BeanConfig implements ResourceLoaderAware {

    private ResourceLoader resourceLoader;

    @Bean
    public PropertySourcesPlaceholderConfigurer getPropertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertySourcesPlaceholderConfigurer.setLocation(resourceLoader.getResource("application.properties"));
        return propertySourcesPlaceholderConfigurer;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

占位符替换的数据源有系统变量、JVM变量封装成Environment,以及本地配置文件,数据源以键值对的形式保存着名称与值之间的映射关系,解析占位符的时候从数据源中找到对应的值,替换到占位符。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值