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

占位符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,先来看下继承关系,后面的代码使用了大量的模板方法模式,将会在这几个类中来回切换:

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,先来看下这个类的继承关系:

从上图的继承关系可以看出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实例化后就能被赋予正确的属性了。

到此这篇关于Spring占位符Placeholder的实现原理的文章就介绍到这了,更多相关Spring占位符Placeholder内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring实战之属性占位符配置器用法示例

    本文实例讲述了Spring实战之属性占位符配置器用法.分享给大家供大家参考,具体如下: 一 配置文件 <?xml version="1.0" encoding="GBK"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns

  • spring是如何解析xml配置文件中的占位符

    前言 我们在配置Spring Xml配置文件的时候,可以在文件路径字符串中加入 ${} 占位符,Spring会自动帮我们解析占位符,这么神奇的操作Spring是怎么帮我们完成的呢?这篇文章我们就来一步步揭秘. 1.示例 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(); applicationContext.setConfigLocation("${java.versi

  • Spring及Mybatis整合占位符解析失败问题解决

    问题:写了一个新的dao接口,进行单元测试时提示: Initialization of bean failed; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.lang.String' to required type 'int' for property 'maxActive'; nested exceptio

  • Spring整合Mybatis使用<context:property-placeholder>时的坑

    背景 最近项目要上线,需要开发一个数据迁移程序.程序的主要功能就是将一个数据库里的数据,查询出来经过一系列处理后导入另一个数据库.考虑到开发的方便快捷.自然想到用spring和mybatis整合一下.甚至用mybatis的自动代码生成,可以省下大量dao层的开发. 整合的坑 之前的项目:以前也有过这种类似的程序,就把spring和mybatis整合的配置直接拿来修改下用.之前的整合配置是这样子的: 1.考虑到数据库url.用户名密码的可配置性,将这些信息放入properties文件.在sprin

  • Spring Boot环境属性占位符解析及类型转换详解

    前提 前面写过一篇关于Environment属性加载的源码分析和扩展,里面提到属性的占位符解析和类型转换是相对复杂的,这篇文章就是要分析和解读这两个复杂的问题.关于这两个问题,选用一个比较复杂的参数处理方法PropertySourcesPropertyResolver#getProperty,解析占位符的时候依赖到 PropertySourcesPropertyResolver#getPropertyAsRawString: protected String getPropertyAsRawSt

  • 解决Spring国际化文案占位符失效问题的方法

    写在前面:接下来很长一段时间的文章主要会记录一些项目中实际遇到的问题及对应的解决方案,在相应代码分析时会直指问题所在,不会将无关的流程代码贴出,感兴趣的读者可以自行跟踪.同时希望大家能够将心得体会在评论区分享出来,让大家共同进步! 环境或版本:Spring 3.2.3 现象:利用Spring自带的MessageSource来处理国际化文案,us状态下的文案有部分占位符未被替换,cn状态下的正常.文案如下: tms.pallet.order.box.qty=The total palletized

  • Spring中property-placeholder的使用与解析详解

    我们在基于spring开发应用的时候,一般都会将数据库的配置放置在properties文件中. 代码分析的时候,涉及的知识点概要: 1.NamespaceHandler 解析xml配置文件中的自定义命名空间 2.ContextNamespaceHandler 上下文相关的解析器,这边定义了具体如何解析property-placeholder的解析器 3.BeanDefinitionParser 解析bean definition的接口 4.BeanFactoryPostProcessor 加载好

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

    占位符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:/

  • Spring整合Dubbo框架过程及原理解析

    这篇文章主要介绍了Spring整合Dubbo框架过程及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Dubbo作为一个RPC框架,其最核心的功能就是要实现跨网络的远程调用.演示过程创建两个小工程,一个作为服务的提供者,一个作为服务的消费者.通过Dubbo来实现服务消费者远程调用服务提供者的方法. dubbo 的使用需要一个注册中心,这里以Zookeeper为例来演示 1.Dubbo架构 Dubbo架构图(Dubbo官方提供)如下: 节

  • WindowsForm实现TextBox占位符Placeholder提示功能

    在WinForm程序中,实现TextBox文本输入框占位符的方式也很多,最常用的是方式基于Windows Api SendMessage函数发送EM_SETCUEBANNER消息,或者通过TextBox自带的焦点事件处理. SendMessage函数实现 创建一个继承TextBox的ZhmTextBox输入框控件,新增Placeholder属性,在Placeholder的set方法中发送EM_SETCUEBANNER消息 public class ZhmTextBox: TextBox { pr

  • Spring 事务事件监控及实现原理解析

    前面我们讲到了Spring在进行事务逻辑织入的时候,无论是事务开始,提交或者回滚,都会触发相应的事务事件.本文首先会使用实例进行讲解Spring事务事件是如何使用的,然后会讲解这种使用方式的实现原理. 1. 示例 对于事务事件,Spring提供了一个注解@TransactionEventListener,将这个注解标注在某个方法上,那么就将这个方法声明为了一个事务事件处理器,而具体的事件类型则是由TransactionalEventListener.phase属性进行定义的.如下是Transac

  • Spring Cloud负载均衡组件Ribbon原理解析

    目录 前言 一个问题引发的思考 Ribbon的简单使用 Ribbon 原理分析 LoadBalancerAutoConfiguration 自动装配 RestTemplateCustomizer LoadBalancerInterceptor RibbonLoadBalancerClient#execute ZoneAwareLoadBalancer 负载均衡器 如何获取所有服务 如何判断服务是否可用 Ribbon 的负载均衡算法 总结 微服务体系下的 Spring Cloud Netflix

  • Spring mvc JSON数据交换格式原理解析

    什么是JSON JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛. 采用完全独立于编程语言的文本格式来存储和表示数据. 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言. 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率. 在 JavaScript 语言中,一切都是对象.因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串.数字.对象.数组等.看看他的要求和

  • Spring Data JPA分页复合查询原理解析

    Spring Data JPA是Spring Data家族的一部分,可以轻松实现基于JPA的存储库. 此模块处理对基于JPA的数据访问层的增强支持. 它使构建使用数据访问技术的Spring驱动应用程序变得更加容易. 在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦. 必须编写太多样板代码来执行简单查询以及执行分页和审计. Spring Data JPA旨在通过减少实际需要的工作量来显著改善数据访问层的实现. 作为开发人员,您编写repository接口,包括自定义查找器方法,Spring

  • .properties文件读取及占位符${...}替换源码解析

    前言 我们在开发中常遇到一种场景,Bean里面有一些参数是比较固定的,这种时候通常会采用配置的方式,将这些参数配置在.properties文件中,然后在Bean实例化的时候通过Spring将这些.properties文件中配置的参数使用占位符"${}"替换的方式读入并设置到Bean的相应参数中. 这种做法最典型的就是JDBC的配置,本文就来研究一下.properties文件读取及占位符"${}"替换的源码,首先从代码入手,定义一个DataSource,模拟一下JDB

随机推荐