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內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Spring XML Schema擴展機制的使用示例
- 如何使用Spring自定義Xml標簽
- Java自定義Spring配置標簽
- Spring啟動過程源碼分析及簡介
- 關於Spring Bean實例過程中使用反射和遞歸處理的Bean屬性填充問題