Spring占位符Placeholder的实现原理解析

作者:morris131 时间:2023-03-14 18:14:00 

占位符Placeholder的使用

xml中的配置:


<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd"
 default-lazy-init="false">

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

<bean id="user" class="com.morris.spring.entity.Author">
<property name="name" value="${author.name}" />
</bean>
</beans>

实现原理

前面在Spring中自定义标签的解析中分析到context:property-placeholder这种自定义标签的解析流程如下:

  • 基于SPI机制,扫描所有类路径下jar中/META-INFO/spring.handlers文件,并将这些文件读取为一个key为namespace,value为具体NameSpaceHandler的Map结构。

  • 根据bean标签名获得xml上方的namespace,然后根据namespace从第一步中的map中获得具体的NameSpaceHandler。

  • 调用NameSpaceHandler的init()方法进行初始化,此方法一般会将负责解析各种localName的BeanDefinitionParser解析器注册到一个map中。

  • 根据localName=property-placeholder从上一步中获得具体的BeanDefinitionParser解析器,并调用其parse()方法进行解析。

在这里NameSpaceHandler为ContextNamespaceHandler,而BeanDefinitionParser解析器为PropertyPlaceholderBeanDefinitionParser,所以我们观察的重点为PropertyPlaceholderBeanDefinitionParser的parse()方法。

注册PropertySourcesPlaceholderConfigurer

parse()方法位于父类AbstractBeanDefinitionParser,先来看下继承关系,后面的代码使用了大量的模板方法模式,将会在这几个类中来回切换:

Spring占位符Placeholder的实现原理解析

org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse


public final BeanDefinition parse(Element element, ParserContext parserContext) {
// 调用子类org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser.parseInternal
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
// 生成一个ID
String id = resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
parserContext.getReaderContext().error(
"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
+ "' when used as a top-level tag", element);
}
String[] aliases = null;
if (shouldParseNameAsAliases()) {
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
// 注册BD
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents()) {
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
}
catch (BeanDefinitionStoreException ex) {
String msg = ex.getMessage();
parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
return null;
}
}
return definition;
}

org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#parseInternal


protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
// 获取子类PropertyPlaceholderBeanDefinitionParser返回的PropertySourcesPlaceholderConfigurer
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
if (containingBd != null) {
// Inner bean definition must receive same scope as containing bean.
builder.setScope(containingBd.getScope());
}
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
builder.setLazyInit(true);
}
// 又是一个模板方法模式
/**
* @see org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder)
*/
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}

org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#getBeanClass


protected Class<?> getBeanClass(Element element) {
if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
return PropertySourcesPlaceholderConfigurer.class; // 新版返回这个
}

return org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.class;
}

org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#doParse


protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
// 调用父类的doParse
super.doParse(element, parserContext, builder);

builder.addPropertyValue("ignoreUnresolvablePlaceholders",
Boolean.valueOf(element.getAttribute("ignore-unresolvable")));

String systemPropertiesModeName = element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE);
if (StringUtils.hasLength(systemPropertiesModeName) &&
!systemPropertiesModeName.equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) {
builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_" + systemPropertiesModeName);
}

if (element.hasAttribute("value-separator")) {
builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
}
if (element.hasAttribute("trim-values")) {
builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
}
if (element.hasAttribute("null-value")) {
builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
}
}

org.springframework.context.config.AbstractPropertyLoadingBeanDefinitionParser#doParse


protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
// 解析<context:property-placeholder>标签的各种属性
String location = element.getAttribute("location");
if (StringUtils.hasLength(location)) {
location = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(location);
String[] locations = StringUtils.commaDelimitedListToStringArray(location);
builder.addPropertyValue("locations", locations);
}

String propertiesRef = element.getAttribute("properties-ref");
if (StringUtils.hasLength(propertiesRef)) {
builder.addPropertyReference("properties", propertiesRef);
}

String fileEncoding = element.getAttribute("file-encoding");
if (StringUtils.hasLength(fileEncoding)) {
builder.addPropertyValue("fileEncoding", fileEncoding);
}

String order = element.getAttribute("order");
if (StringUtils.hasLength(order)) {
builder.addPropertyValue("order", Integer.valueOf(order));
}

builder.addPropertyValue("ignoreResourceNotFound",
Boolean.valueOf(element.getAttribute("ignore-resource-not-found")));

builder.addPropertyValue("localOverride",
Boolean.valueOf(element.getAttribute("local-override")));

builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
}

总结一下,其实上面这么多代码调来调去,只有一个目的,就是向spring容器中注入一个BeanDefinition,这个BeanDefinition有两个最重要的属性:

  • BeanClass为PropertySourcesPlaceholderConfigurer。

  • 有一个属性为location,对应properties文件的位置。

PropertySourcesPlaceholderConfigurer的调用

上面向spring容器中注入一个PropertySourcesPlaceholderConfigurer类型BeanDefinition,先来看下这个类的继承关系:

Spring占位符Placeholder的实现原理解析

从上图的继承关系可以看出PropertySourcesPlaceholderConfigurer实现了BeanFactoryPostProcessor,所以这个类的核心方法为postProcessBeanFactory()。


public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertySources == null) { // 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 {
// 加载application.properties为Properties,包装为PropertySource
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;
}

上面的方法的前面一大截的主要作用为将系统属性、环境变量以及properties文件中的属性整合到MutablePropertySources中,这样就可以直接调用MutablePropertySources.getProperties()方法根据属性名拿到对应的属性值了。MutablePropertySources里面其实是一个Map的链表,这样就可以先遍历链表,然后再根据属性名从Map中找到对应的属性值。


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

propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); // ${
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); // }
propertyResolver.setValueSeparator(this.valueSeparator); // :

// 下面的doProcessProperties会回调这个lambda表达式
// 真正的解析逻辑在resolveRequiredPlaceholders
/**
* @see AbstractPropertyResolver#resolveRequiredPlaceholders(java.lang.String)
*/
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);
};

// 这里会遍历所有的BD,挨个处理占位符
doProcessProperties(beanFactoryToProcess, valueResolver);
}

org.springframework.beans.factory.config.PlaceholderConfigurerSupport#doProcessProperties


protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {

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 {
// 遍历BD
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);
}

org.springframework.beans.factory.config.BeanDefinitionVisitor#visitBeanDefinition


public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
if (beanDefinition.hasPropertyValues()) {
// 遍历所有的属性
visitPropertyValues(beanDefinition.getPropertyValues());
}
if (beanDefinition.hasConstructorArgumentValues()) {
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
}

org.springframework.beans.factory.config.BeanDefinitionVisitor#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())) {
// 将新的value替换BD中旧的
pvs.add(pv.getName(), newVal);
}
}
}

resolveValue()方法中会回调到之前的lambda表达式StringValueResolv真正开始解析,也就是根据属性名从PropertySources中取值。

总结一下PropertySourcesPlaceholderConfigurer#postProcessBeanFactory()方法:这个方法会在Bean实例化之前完成对Spring容器中所有BeanDefinition中带有占位符的属性进行解析,这样在Bean实例化后就能被赋予正确的属性了。

来源:https://blog.csdn.net/u022812849/article/details/114319908

标签:Spring,占位符,Placeholder
0
投稿

猜你喜欢

  • c# wpf如何附加依赖项属性

    2023-01-09 05:05:56
  • Android Handle原理(Looper,Handler和Message)三者关系案例详解

    2023-08-25 22:51:47
  • SpringBoot使用AOP记录接口操作日志的方法

    2022-01-19 08:56:47
  • tk.mybatis实现uuid主键生成的示例代码

    2022-01-22 00:17:13
  • Spring Security自定义认证逻辑实例详解

    2023-02-28 19:19:18
  • java 发送邮件的实例代码(可移植)

    2022-09-23 15:53:44
  • Android实现显示和隐藏密码功能的示例代码

    2022-12-14 23:40:09
  • jvm crash的崩溃日志详细分析及注意点

    2022-12-18 07:49:00
  • Java利用自定义注解、反射实现简单BaseDao实例

    2022-09-06 05:29:30
  • spring cloud将spring boot服务注册到Eureka Server上的方法

    2023-12-08 19:42:09
  • Android RecyclerView 实现快速滚动的示例代码

    2023-02-17 05:32:50
  • 详解SpringBoot 快速整合MyBatis(去XML化)

    2022-08-19 16:42:54
  • SpringBoot + SpringSecurity 短信验证码登录功能实现

    2022-10-16 10:26:25
  • MyBatis分页插件PageHelper的使用与原理

    2021-06-15 09:24:35
  • Java递归实现迷宫游戏

    2023-08-26 07:33:16
  • MyBatis官方代码生成工具给力(解放双手)

    2023-12-14 17:01:17
  • Mybatis RowBounds 限制查询条数的实现代码

    2022-11-18 17:26:03
  • Java SWT中常见弹出框实例总结

    2023-08-22 00:42:22
  • C#实现下载网页HTML源码的方法

    2023-06-10 01:07:24
  • Android用Canvas绘制贝塞尔曲线

    2022-11-22 00:23:11
  • asp之家 软件编程 m.aspxhome.com