前言
这篇文章主要讲的是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
这个方法,里面是不是和获取本地配置文件的属性源的结构类似,这里我们重点关注getSystemProperties
,getSystemEnvironment
,点到方法实现中去看,可以看出它们分别返回
//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,以及本地配置文件,数据源以键值对的形式保存着名称与值之间的映射关系,解析占位符的时候从数据源中找到对应的值,替换到占位符。