Environment与占位符解析

导读:本篇文章讲解 Environment与占位符解析,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

示例:

@Data
public class StudentA {
    private String username;
    private String password;
}

<!--spring配置文件-->
<context:property-placeholder location="classpath:application.properties"/>
    <bean id = "studentA" class="com.study.dongsq.bean.StudentA">
        <property name="username" value="${test.name}"/>
        <property name="password" value="${test.password}"/>
    </bean>

在这里插入图片描述

在这里插入图片描述
源码解析:
在spring源码之标签解析中总结过标签解析过程:
1、根据当前解析标签的头信息找到对应的namespaceUri
2、加载spring所有jar中spring.handlers文件,并建立映射关系
3、根据namespaceUri从映射关系中找到对应的实现了NameSpaceHandler接口的类
4、调用init()方法并返回NamespaceHandler(注:init()注册了各种自定义标签的解析类)
5、调用NamespaceHandler(和namespaceUri进行了映射)根据标签注册的解析类的parse()方法

所以根据标签<context:property-placeholder >会找到这个解析类:
在这里插入图片描述
调用PropertyPlaceholderBeanDefinitionParser的父类AbstractBeanDefinitionParser的parse()方法

public final BeanDefinition parse(Element element, ParserContext parserContext) {
		AbstractBeanDefinition definition = parseInternal(element, parserContext);

@Override
	protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
		...
		...
		Class<?> beanClass = getBeanClass(element);
		if (beanClass != null) {
			builder.getRawBeanDefinition().setBeanClass(beanClass);
		}
		...
		...
		doParse(element, parserContext, builder);
		return builder.getBeanDefinition();
	}

getBeanClass返回PropertySourcesPlaceholderConfigurer
在这里插入图片描述
所以在这里把PropertySourcesPlaceholderConfigurer变成BeanDefinition,
并调用super.doParse把标签 <context:property-placeholder> 的 location属性放入locations数组保存到PropertySourcesPlaceholderConfigurer的父类PropertiesLoaderSupport中
在这里插入图片描述
标签解析完了之后是调用postProcessBeanFactory方法

因为PropertySourcesPlaceholderConfigurer实现了BeanFactoryPostProcessor接口
所以会调用到postProcessBeanFactory()方法中,但是在调这个方法之前,先将
PropertySourcesPlaceholderConfigurer进行实例化,再调用postProcessBeanFactory()方法
在这里插入图片描述
因为PropertySourcesPlaceholderConfigurer实现了EnvironmentAware接口,所以实例化的时候,会调用到ApplicationContextAwareProcessor中的postProcessBeforeInitialization的方法,对实现各种aware接口的进行方法调用(具体过程看spring源码之生命周期)

@Override
	@Nullable
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
				bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
				bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){
			return bean;
		}

		...
		...
		else {
			invokeAwareInterfaces(bean);
		}

		return bean;
	}

在这里插入图片描述
可以看到上图中是在上下文中获取的Environment,并给PropertySourcesPlaceholderConfigurer中的Environment变量赋值了。那上下文中的Environment又是怎么来的?

上下文在启动的时候,会调用setConfigLocation()方法,如图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在上下文AbstractApplicationContext中创建environment
在这里插入图片描述
在这里插入图片描述
在new StandardEnvironment()的时候,会调用customizePropertySources()方法,如图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

将PropertySourcesPlaceholderConfigurer实例化、调用了setEnvironment方法之后,会调用其postProcessBeanFactory方法,这时候environment就不为null了

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	if (this.propertySources == null) {
		this.propertySources = new MutablePropertySources();
		if (this.environment != null) {
			//把environment对象封装成的PropertySource对象加入到
			this.propertySources.addLast(
					//把environment对象封装成PropertySource对象MutablePropertySources中的list中
				new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
					@Override
					@Nullable
					//source就是environment对象
					public String getProperty(String key) {
						return this.source.getProperty(key);
					}
				}
			);
		}
		try {
			//加载本地配置文件中的属性值包装成properties对象后,最终包装成PropertySource对象
			PropertySource<?> localPropertySource =
					new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
			//加入到MutablePropertySources中的list中
			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;
}



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

	//设置占位符的前缀后缀  ${}
	propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
	propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
	//设分割符 :
	propertyResolver.setValueSeparator(this.valueSeparator);
	//这里是一个函数式接口
	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);
	};
	//核心流程。把占位符${xxx}替换成真正的值
	doProcessProperties(beanFactoryToProcess, valueResolver);
}

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
			StringValueResolver valueResolver) {

		BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
		...
		这里会调到BeanDefinitionVistor的resolveStringValue方法,如下图所示
		...
		...
	//把内嵌的Value解析器设置到BeanFactory中.为@Value的依赖注入做准备
	beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

在这里插入图片描述

然后跳转到
在这里插入图片描述
在这里插入图片描述
doResolvePlaceholders方法会调到parseStringValue方法

protected String parseStringValue(
			String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

		...
		...
		StringBuilder result = new StringBuilder(value);
		while (startIndex != -1) {
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
				...
				...
				//递归解析 ${${}}
				// Recursive invocation, parsing placeholders contained in the placeholder key.
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				// Now obtain the value for the fully resolved key...
				//这里会调用到 this::getPropertyAsRawString 也是函数式接口
				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);
						//获取 ":"号后面的默认值
						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
						//":"前面的参数解析
						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
						//如果解析不到,则用默认值
						if (propVal == null) {
							propVal = defaultValue;
						}
					}
				}
			...
			...
	}

函数式接口
在这里插入图片描述
@Value中占位符的解析

原理和标签中的解析差不多,AutowiredAnnotationBeanPostProcessor完成对@Value注解的收集,会对有@Value注解的属性依赖注入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
这样就走到和标签占位符解析相同的代码。

大致流程总结:
1、spring在启动的时候会在上下文中创建Environment,并将系统变量放入environment对象中。
2、通过aware接口调用,将environment对象传给PropertySourcesPlaceholderConfigurer。
3、将environment对象放入MutablePropertySources中,并重写getProperty方法。
4、收集本地配置文件中的属性值包装成properties对象后,最终包装成PropertySource对象。
5、遍历所有的BeanDefinition,修改BeanDefinition中的PropertyValues中的每一个属性值,把属性值有${XXX}修改成真正的参数值
6、如果是@Value注解,则先收集注解,再进行属性的依赖注入,解析过程和标签的类似

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13826.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!