spring是如何解析xml配置文件中的占位符
前言
我们在配置SpringXml配置文件的时候,可以在文件路径字符串中加入${}占位符,Spring会自动帮我们解析占位符,这么神奇的操作Spring是怎么帮我们完成的呢?这篇文章我们就来一步步揭秘。
1.示例
ClassPathXmlApplicationContextapplicationContext=newClassPathXmlApplicationContext(); applicationContext.setConfigLocation("${java.version}.xml"); applicationContext.refresh(); String[]beanNames=applicationContext.getBeanDefinitionNames(); for(StringbeanName:beanNames){ System.out.println(beanName); }
这段代码在我工程里是会报错的,如下:
Causedby:java.io.FileNotFoundException:classpathresource[1.8.0_144.xml]cannotbeopenedbecauseitdoesnotexist atorg.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:190) atorg.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) ...11more
可以看到报错里面的文件路径变成了1.8.0_144.xml,也就是说Spring帮我们把${java.version}解析成了实际值。
2.原理
AbstractRefreshableConfigApplicationContext
我们在之前的文章里提到过这个类的resolve方法,我们再来瞧一眼:
/** *Resolvethegivenpath,replacingplaceholderswithcorresponding *environmentpropertyvaluesifnecessary.Appliedtoconfiglocations. *@parampaththeoriginalfilepath *@returntheresolvedfilepath *@seeorg.springframework.core.env.Environment#resolveRequiredPlaceholders(String) */ protectedStringresolvePath(Stringpath){ //通过当前环境去解析必要的占位符 returngetEnvironment().resolveRequiredPlaceholders(path); }
获取当前环境,这个环境在示例代码中就是StandardEnvironment,并且根据当前环境去解析占位符,这个占位符解析不到还会报错。
resolveRequiredPlaceHolders由StandardEnvironment的父类AbstractEnvironment实现。
AbstractEnvironment
//把propertySources放入Resolver中 privatefinalConfigurablePropertyResolverpropertyResolver= newPropertySourcesPropertyResolver(this.propertySources); @Override publicStringresolveRequiredPlaceholders(Stringtext)throwsIllegalArgumentException{ returnthis.propertyResolver.resolveRequiredPlaceholders(text); }
这里的propertySources很重要了,从命名也可以看出我们解析占位符的来源就是从这个集合中来的。这个集合是在我们StandardEnvironment实例化的时候去自定义的。
StandardEnvironment
/** *Createanew{@codeEnvironment}instance,callingbackto *{@link#customizePropertySources(MutablePropertySources)}duringconstructionto *allowsubclassestocontributeormanipulate(操作){@linkPropertySource}instancesas *appropriate. *@see#customizePropertySources(MutablePropertySources) */ //StandardEnvironment实例化调用 publicAbstractEnvironment(){ customizePropertySources(this.propertySources); } @Override protectedvoidcustomizePropertySources(MutablePropertySourcespropertySources){ //todoJava提供了System类的静态方法getenv()和getProperty()用于返回系统相关的变量与属性, //todogetenv方法返回的变量大多于系统相关, //todogetProperty方法返回的变量大多与java程序有关。 //https://www.cnblogs.com/Baronboy/p/6030443.html propertySources.addLast(newMapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,getSystemProperties())); //SystemEnvironmentPropertySource是System.getenv() propertySources.addLast(newSystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,getSystemEnvironment())); }
最重要的肯定是我们的propertyResolver.resolveRequiredPlaceholders方法了,propertyResolver.resolveRequiredPlaceholders其实是PropertySourcesPropertyResolver的父类AbstractPropertyResolver来实现。
AbstractPropertyResolver
//创建一个占位符的helper去解析 @Override publicStringresolveRequiredPlaceholders(Stringtext)throwsIllegalArgumentException{ if(this.strictHelper==null){ //不忽略 this.strictHelper=createPlaceholderHelper(false); } returndoResolvePlaceholders(text,this.strictHelper); } //私有方法 //是否忽略无法解决的占位符 privatePropertyPlaceholderHelpercreatePlaceholderHelper(booleanignoreUnresolvablePlaceholders){ //默认使用${placeholderPrefix returnnewPropertyPlaceholderHelper(this.placeholderPrefix,this.placeholderSuffix, this.valueSeparator,ignoreUnresolvablePlaceholders); } privateStringdoResolvePlaceholders(Stringtext,PropertyPlaceholderHelperhelper){ //PlaceholderResolverfunctioninterface //todoimportant重要的是这个getPropertyAsRawString returnhelper.replacePlaceholders(text,this::getPropertyAsRawString); }
这里的this::getPropertyAsRawString很重要,利用了java8的函数式接口来实现。它的定义在AbstractPropertyResolver里
/** *RetrievethespecifiedpropertyasarawString, *i.e.withoutresolutionofnestedplaceholders. *@paramkeythepropertynametoresolve *@returnthepropertyvalueor{@codenull}ifnonefound */ @Nullable protectedabstractStringgetPropertyAsRawString(Stringkey);
但是我们在doResolvePlaceholders里指向的this,所以还得看PropertySourcesPropertyResolver类。
PropertySourcesPropertyResolver
//提供给函数接口PlaceholderResolver //todo解析xml配置文件路径占位符的时候调用的是这个2020-09-11 @Override @Nullable protectedStringgetPropertyAsRawString(Stringkey){ returngetProperty(key,String.class,false); } @Nullable protectedTgetProperty(Stringkey,Class targetValueType,booleanresolveNestedPlaceholders){ if(this.propertySources!=null){ //例如遍历的是MutablePropertySources的propertySourceList for(PropertySource>propertySource:this.propertySources){ if(logger.isTraceEnabled()){ logger.trace("Searchingforkey'"+key+"'inPropertySource'"+ propertySource.getName()+"'"); } Objectvalue=propertySource.getProperty(key); if(value!=null){ //todo解析profile变量的时候会去解析变量中的占位符2020-09-11 //TODO解析xml配置文件路径字符串的时候如果占位符变量的值包含占位符在这里不会去解析通过Helper去解析PropertyPlaceholderHelper if(resolveNestedPlaceholders&&valueinstanceofString){ value=resolveNestedPlaceholders((String)value); } logKeyFound(key,propertySource,value); //跳出for循环 returnconvertValueIfNecessary(value,targetValueType); } } } if(logger.isTraceEnabled()){ logger.trace("Couldnotfindkey'"+key+"'inanypropertysource"); } returnnull; }
看到没有,我们是遍历this.propertySources集合,然后根据key调用它的getProperty方法获取value。我们从上面的StandardEnvrionment中看到我们定义的是MapPropertySource和SystemEnvironmentPropertySource.
MapPropertySource
//从source中取得属性 @Override @Nullable publicObjectgetProperty(Stringname){ returnthis.source.get(name); }
这里的source就是getSystemProperties(),也就是AbstractEnvironment中的方法:
@Override @SuppressWarnings({"unchecked","rawtypes"}) publicMapgetSystemProperties(){ try{ //Hashtable return(Map)System.getProperties(); } catch(AccessControlExceptionex){ return(Map)newReadOnlySystemAttributesMap(){ @Override @Nullable protectedStringgetSystemAttribute(StringattributeName){ try{ returnSystem.getProperty(attributeName); } catch(AccessControlExceptionex){ if(logger.isInfoEnabled()){ logger.info("CaughtAccessControlExceptionwhenaccessingsystemproperty'"+ attributeName+"';itsvaluewillbereturned[null].Reason:"+ex.getMessage()); } returnnull; } } }; } }
我们还忘了很重要的一步,就是PropertyPlaceholderHelper的replacePlaceholders方法。
PropertyPlaceholderHelper
//protected范围 protectedStringparseStringValue( Stringvalue,PlaceholderResolverplaceholderResolver,SetvisitedPlaceholders){ StringBuilderresult=newStringBuilder(value); //如果value中没有占位符前缀那直接返回result intstartIndex=value.indexOf(this.placeholderPrefix); while(startIndex!=-1){ //找到占位符的最后一个索引 intendIndex=findPlaceholderEndIndex(result,startIndex); if(endIndex!=-1){ Stringplaceholder=result.substring(startIndex+this.placeholderPrefix.length(),endIndex); StringoriginalPlaceholder=placeholder; if(!visitedPlaceholders.add(originalPlaceholder)){ thrownewIllegalArgumentException( "Circularplaceholderreference'"+originalPlaceholder+"'inpropertydefinitions"); } //1.todo2020-09-01解析出来占位符,比如java.version //解析内嵌占位符 //Recursiveinvocation,parsingplaceholderscontainedintheplaceholderkey. placeholder=parseStringValue(placeholder,placeholderResolver,visitedPlaceholders); //Nowobtainthevalueforthefullyresolvedkey... //2.todo2020-09-01获取实际值 StringpropVal=placeholderResolver.resolvePlaceholder(placeholder); if(propVal==null&&this.valueSeparator!=null){ intseparatorIndex=placeholder.indexOf(this.valueSeparator); if(separatorIndex!=-1){ StringactualPlaceholder=placeholder.substring(0,separatorIndex); StringdefaultValue=placeholder.substring(separatorIndex+this.valueSeparator.length()); //这里就是实际获取占位符中值得地方。 propVal=placeholderResolver.resolvePlaceholder(actualPlaceholder); } } if(propVal!=null){ //从占位符里获取的值也有可能包含占位符这里可能会报Circularplaceholderreference propVal=parseStringValue(propVal,placeholderResolver,visitedPlaceholders); //替换占位符为实际值 result.replace(startIndex,endIndex+this.placeholderSuffix.length(),propVal); if(logger.isTraceEnabled()){ logger.trace("Resolvedplaceholder'"+placeholder+"'"); } startIndex=result.indexOf(this.placeholderPrefix,startIndex+propVal.length()); } //省略部分代码 } else{ startIndex=-1; } } returnresult.toString(); }
到这里我们就可以看到Spring在处理一个小小的占位符就做了这么多设计。可见这个架构是如此严谨。下篇文章我们就来探讨下Spring是如何加载这个Xml文件的。
以上就是spring是如何解析xml配置文件中的占位符的详细内容,更多关于spring解析xml占位符的资料请关注毛票票其它相关文章!