Spring占位符Placeholder的實(shí)現(xiàn)原理解析
占位符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>
實(shí)現(xiàn)原理
前面在Spring中自定義標(biāo)簽的解析中分析到context:property-placeholder這種自定義標(biāo)簽的解析流程如下:
- 基于SPI機(jī)制,掃描所有類路徑下jar中/META-INFO/spring.handlers文件,并將這些文件讀取為一個(gè)key為namespace,value為具體NameSpaceHandler的Map結(jié)構(gòu)。
- 根據(jù)bean標(biāo)簽名獲得xml上方的namespace,然后根據(jù)namespace從第一步中的map中獲得具體的NameSpaceHandler。
- 調(diào)用NameSpaceHandler的init()方法進(jìn)行初始化,此方法一般會(huì)將負(fù)責(zé)解析各種localName的BeanDefinitionParser解析器注冊(cè)到一個(gè)map中。
- 根據(jù)localName=property-placeholder從上一步中獲得具體的BeanDefinitionParser解析器,并調(diào)用其parse()方法進(jìn)行解析。
在這里NameSpaceHandler為ContextNamespaceHandler,而B(niǎo)eanDefinitionParser解析器為PropertyPlaceholderBeanDefinitionParser,所以我們觀察的重點(diǎn)為PropertyPlaceholderBeanDefinitionParser的parse()方法。
注冊(cè)PropertySourcesPlaceholderConfigurer
parse()方法位于父類AbstractBeanDefinitionParser,先來(lái)看下繼承關(guān)系,后面的代碼使用了大量的模板方法模式,將會(huì)在這幾個(gè)類中來(lái)回切換:

org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse
public final BeanDefinition parse(Element element, ParserContext parserContext) {
// 調(diào)用子類org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser.parseInternal
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
// 生成一個(gè)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);
// 注冊(cè)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);
}
// 又是一個(gè)模板方法模式
/**
* @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; // 新版返回這個(gè)
}
return org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.class;
}
org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#doParse
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
// 調(diào)用父類的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>標(biāo)簽的各種屬性
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);
}
總結(jié)一下,其實(shí)上面這么多代碼調(diào)來(lái)調(diào)去,只有一個(gè)目的,就是向spring容器中注入一個(gè)BeanDefinition,這個(gè)BeanDefinition有兩個(gè)最重要的屬性:
- BeanClass為PropertySourcesPlaceholderConfigurer。
- 有一個(gè)屬性為location,對(duì)應(yīng)properties文件的位置。
PropertySourcesPlaceholderConfigurer的調(diào)用
上面向spring容器中注入一個(gè)PropertySourcesPlaceholderConfigurer類型BeanDefinition,先來(lái)看下這個(gè)類的繼承關(guān)系:

從上圖的繼承關(guān)系可以看出PropertySourcesPlaceholderConfigurer實(shí)現(xiàn)了BeanFactoryPostProcessor,所以這個(gè)類的核心方法為postProcessBeanFactory()。
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertySources == null) { // null
this.propertySources = new MutablePropertySources();
if (this.environment != null) {
// environment中存儲(chǔ)的是系統(tǒng)屬性和環(huán)境變量
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;
}
上面的方法的前面一大截的主要作用為將系統(tǒng)屬性、環(huán)境變量以及properties文件中的屬性整合到MutablePropertySources中,這樣就可以直接調(diào)用MutablePropertySources.getProperties()方法根據(jù)屬性名拿到對(duì)應(yīng)的屬性值了。MutablePropertySources里面其實(shí)是一個(gè)Map的鏈表,這樣就可以先遍歷鏈表,然后再根據(jù)屬性名從Map中找到對(duì)應(yīng)的屬性值。
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); // ${
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); // }
propertyResolver.setValueSeparator(this.valueSeparator); // :
// 下面的doProcessProperties會(huì)回調(diào)這個(gè)lambda表達(dá)式
// 真正的解析邏輯在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);
};
// 這里會(huì)遍歷所有的BD,挨個(gè)處理占位符
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()方法中會(huì)回調(diào)到之前的lambda表達(dá)式StringValueResolv真正開(kāi)始解析,也就是根據(jù)屬性名從PropertySources中取值。
總結(jié)一下PropertySourcesPlaceholderConfigurer#postProcessBeanFactory()方法:這個(gè)方法會(huì)在Bean實(shí)例化之前完成對(duì)Spring容器中所有BeanDefinition中帶有占位符的屬性進(jìn)行解析,這樣在Bean實(shí)例化后就能被賦予正確的屬性了。
到此這篇關(guān)于Spring占位符Placeholder的實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Spring占位符Placeholder內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析SpringBoot2.4 靜態(tài)資源加載問(wèn)題
這篇文章主要介紹了SpringBoot2.4 靜態(tài)資源加載問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Java深入學(xué)習(xí)圖形用戶界面GUI之布局管理器
本文章向大家介紹Java GUI布局管理器,主要包括布局管理器使用實(shí)例、應(yīng)用技巧、基本知識(shí)點(diǎn)總結(jié)和需要注意事項(xiàng),具有一定的參考價(jià)值,需要的朋友可以參考一下2022-05-05
Java實(shí)現(xiàn)多任務(wù)執(zhí)行助手
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)多任務(wù)執(zhí)行助手,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
利用Java實(shí)現(xiàn)玩家打怪小游戲的完整過(guò)程
這篇文章主要介紹了如何使用Java創(chuàng)建一個(gè)簡(jiǎn)單的“打怪小游戲”,游戲中的角色分為法師、戰(zhàn)士、BOSS和一個(gè)Team類,代碼展示了如何通過(guò)面向?qū)ο缶幊虂?lái)實(shí)現(xiàn)一個(gè)基本的戰(zhàn)斗系統(tǒng),需要的朋友可以參考下2024-12-12
java實(shí)現(xiàn)刪除某條信息并刷新當(dāng)前頁(yè)操作
這篇文章主要介紹了java實(shí)現(xiàn)刪除某條信息并刷新當(dāng)前頁(yè)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Java編程實(shí)現(xiàn)漢字按字母順序排序的方法示例
這篇文章主要介紹了Java編程實(shí)現(xiàn)漢字按字母順序排序的方法,結(jié)合具體實(shí)例形式分析了java編碼轉(zhuǎn)換及字母排序相關(guān)操作技巧,需要的朋友可以參考下2017-07-07

