我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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);
	}
	// 又是一个模板方法模式
	
	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
	
	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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

浅析Spring的事务实现原理

这篇文章主要为大家详细介绍了Spring中事务实现的原理,文中的示例代码讲解详细,对我们学习Spring有一定的帮助,需要的可以参考一下
2022-11-13

Spring AOP实现原理的示例分析

这篇文章将为大家详细讲解有关Spring AOP实现原理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。什么是AOPAOP(Aspect-OrientedProgramming,面向方面编程),可
2023-05-30

Spring注解Autowired的底层实现原理详解

从当前springboot的火热程度来看,java config的应用是越来越广泛了,在使用java config的过程当中,我们不可避免的会有各种各样的注解打交道,其中,我们使用最多的注解应该就是@Autowired注解了。本文就来聊聊Autowired的底层实现原理
2022-11-13

Java Spring AOP源码解析中的事务实现原理是什么

这篇文章将为大家详细讲解有关Java Spring AOP源码解析中的事务实现原理是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。不用Spring管理事务?让我们先来看一下不用sprin
2023-06-22

解析Vue2实现composition API的原理

自从 Vue3 发布之后,composition API 这个词走入写 Vue 同学的视野之中,相信大家也一直听到 composition API 比之前的 options API 有多好多强,如今由于 @vue/composition-api 插件的发布,Vue2 的同学也可以上车咯,接下来我们主要以响应式的 ref 和 reactive 来深入分析一下,这个插件是怎么实现此
2023-05-14

spring注解的底层实现原理是什么

Spring注解的底层实现原理主要依赖于Java的反射机制。在Spring中,通过使用注解来标识类、方法或字段,从而告诉Spring容器如何处理它们。当Spring容器启动时,它会扫描应用程序中的注解,并根据注解的信息生成相应的对象和配置。
2023-10-09

一文解析MySQL的MVCC实现原理

目录1. 什么是MVCC2. 事务的隔离级别3. Undo Log(回滚日志)4. MVCC的实现原理4.1 当前读和快照读4.2 隐藏字段4.3 版本链4.4 Read View(读视图)5. 不同隔离级别下可见性分析5.1 READ C
2022-08-16

解析 PHP Session 跨域的实现原理

引言:随着互联网的发展,越来越多的网站使用了跨域技术来实现不同域名之间的数据交互。跨域是指在一个域名下的网页获取其他域名下的资源,这样的请求是受浏览器的同源策略限制的。在PHP中,session是一种非常常用的机制,用于在服务器端存储用户的
2023-10-21

Java注解机制之Spring自动装配实现原理的示例分析

小编给大家分享一下Java注解机制之Spring自动装配实现原理的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧! Java中使用注解的情况主要在SpringMVC(Spring Boot等),注解实际上相当于一种标
2023-05-31

Synchronized的底层实现原理(原理解析,面试必备)

synchronized 一. synchronized解读 1.1 简单描述 synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是同步,也称之为同步锁。 synchronize
2023-08-19

解析android 流量监测的实现原理

Linux 系统下所有的信息都是以文件的形式存在的,所以应用程序的流量信息也会被保存在操作系统的文件中。Android 2.2 版本以前的系统的流量信息都存放在 proc/net/dev(或者 proc/self/net/dev)文件下,读
2022-06-06

PHP 字符串处理技术分享:解析去除右侧第一个字符的实现原理

PHP 字符串处理技术分享:解析去除右侧第一个字符的实现原理在PHP开发中,经常会遇到需要对字符串进行处理的情况,其中有一种常见的需求是去除字符串右侧的第一个字符。这一篇文章将为大家分享如何实现这一功能的具体原理和代码示例。首先,我们来
PHP 字符串处理技术分享:解析去除右侧第一个字符的实现原理
2024-03-02

JavaScript中new操作符的原理与实现详解

你知道new吗?你知道new的实现原理吗?你能手写new方法吗?不要担心,这篇文件就来带大家深入了解一下JavaScript中的new操作符,感兴趣的小伙伴可以学习一下
2022-11-13

Redisson分布式限流的实现原理解析

目录正文RRateLimiter使用RRateLimiter的实现RRateLimiter使用时注意事项RRateLimiter是非公平限流器Rate不要设置太大限流的上限取决于Redis单实例的性能分布式限流的本质正文我们目前在工作中遇到
2023-02-12

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录