Spring的property-placeholder原理分析
一、解析封裝property-placeholder相關的BeanDefinition
有兩種方式: (1)在XML配置格式如下
<context:property-placeholder location="classpath*:xxxx.properties"/>
(2)註解配置如下:
@Configuration
@PropertySource("classpath:xxx.properties")
public class PropertiesWithJavaConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer () {
return new PropertySourcesPlaceholderConfigurer();
}
}
java類中
@Value( "${jdbc.url}" )
private String jdbcUrl;
檢視spring-context原始碼模組下的spring.handlers檔案可以得知其解析的類為如下
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
進入ContextNamespaceHandler類中可以看到解析property-placeholder標籤委託給了PropertyPlaceholderBeanDefinitionParser類
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
PropertyPlaceholderBeanDefinitionParser類繼承圖如下:
解析自定義標籤最終是呼叫BeanDefinitionParser的parse方法的,其在AbstractBeanDefinitionParser中有實現
public final BeanDefinition parse(Element element, ParserContext parserContext) {
//呼叫parseInternal進行解析
AbstractBeanDefinition definition = parseInternal(element, parserContext);
...
return definition;
}
parseInternal方法在AbstractSingleBeanDefinitionParser類中實現
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
//拿到父節點名稱
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
//【標記1】拿到bean的型別
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
//拿到類名
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
...
//【標記2】解析標籤
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
Spring會把XML配置檔案中配置的bean封裝成BeanDefinition,用於後面的例項化。而上面是通過BeanDefinitionBuilder來構建一個BeanDefinition進行返回。
【標記1】拿到bean的型別
先看看上面【標記1】的地方,那裡決定了返回的BeanDefinition具體型別是什麼。PropertyPlaceholderBeanDefinitionParser類覆寫了該方法。
protected Class<?> getBeanClass(Element element) {
//3.1及之後版本的xsd檔案預設值是這個
if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
return PropertySourcesPlaceholderConfigurer.class;
}
//3.0或之前版本的xsd返回這個
return PropertyPlaceholderConfigurer.class;
}
返回結果和spring-context.xsd有關係,3.0或之前版本返回PropertyPlaceholderConfigurer.class,3.1及之後版本返回PropertySourcesPlaceholderConfigurer.class。
【標記2】解析標籤 PropertyPlaceholderBeanDefinitionParser類覆寫了該方法
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
//呼叫父類解析一部分標籤屬性
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)) {
//3.1及之後的版本進這裡
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"));
}
}
doParse方法主要解析配置的相關屬性
二、PropertySourcesPlaceholderConfigurer類分析
上面介紹了Spring如何解析XML配置中的context:property-placeholder標籤封裝成BeanDefinition放入容器中。本人寫的時候看的是Spring5的原始碼,所以最後解析該標籤得到的BeanDefinition的類的型別是PropertySourcesPlaceholderConfigurer。接下來分析這個類究竟做了什麼。
可以看到PropertySourcesPlaceholderConfigurer間接實現了BeanFactoryPostProcessor介面,該介面是在Spring解析完所有bean並封裝成BeanDefinition的方法在PropertySourcesPlaceholderConfigurer類中實現了
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//propertySources為空,就會去載入environment中的屬性、和XML中指定的檔案中的屬性
if (this.propertySources == null) {
this.propertySources = new MutablePropertySources();
//新增environment中的屬性
if (this.environment != null) {//因為實現了EnvironmentAware介面,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 {
//【標記3】進行載入XML中的location屬性的檔案,併合並props屬性
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);
}
}
//【標記4】處理容器中的BeanDefinition
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
//賦值成員變數
this.appliedPropertySources = this.propertySources;
}
上面可以看到,如果propertySources沒有被賦值,那就會去載入environment中的屬性、和XML中指定的檔案中的屬性,最後就把propertySources傳到PropertySourcesPropertyResolver物件,開始處理容器中的BeanDefinition中需要替換的屬性
【標記3】進行載入XML中的location屬性的檔案,併合並props屬性
protected Properties mergeProperties() throws IOException {
Properties result = new Properties();
//localOverride預設為false,表示本地屬性的不能覆蓋從外部載入屬性
if (this.localOverride) {
// 載入location屬性的表示的檔案,本質是通過java的Properties類進行載入
loadProperties(result);
}
//把本地的屬性合併,localProperties可在xml檔案中通過props屬性進行設定
if (this.localProperties != null) {
for (Properties localProp : this.localProperties) {
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}
if (!this.localOverride) {
// 載入location屬性的表示的檔案,本質是通過java的Properties類進行載入
loadProperties(result);
}
return result;
}
邏輯比較簡單,localOverride變數決定是否本地的屬性可以覆蓋另外載入的屬性
【標記4】處理容器中的BeanDefinition
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
//新增替換的字首,預設是 ${
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
//新增替換的字尾,預設是 }
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
//新增預設值分隔符,預設是 :
propertyResolver.setValueSeparator(this.valueSeparator);
//新建一個處理器,替換邏輯就在這
StringValueResolver valueResolver = strVal -> {
//ignoreUnresolvablePlaceholders預設為false
String resolved = (this.ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal));
//trimValues預設為false
if (this.trimValues) {
resolved = resolved.trim();
}
//如果設定了nullValue,則如果和nullValue相等,返回null
return (resolved.equals(this.nullValue) ? null : resolved);
};
//開始進行處理
doProcessProperties(beanFactoryToProcess, valueResolver);
}
上面的StringValueResolver是文字替換處理器,實質就是它來把${xx}這些經過處理返回真正的值。
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
//把處理器封裝到BeanDefinitionVisitor
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
//拿到所有bean名字
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
//處理除了自身以外的bean
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
//開始處理
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}
//處理別名
beanFactoryToProcess.resolveAliases(valueResolver);
//把valueResolver傳遞給beanFactoryToProcess,供其用於處理註解的解析等
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
遍歷容器中的所有BeanDefinition進行處理,把文字處理器又封裝進了BeanDefinitionVisitor,所以其visitBeanDefinition方法是真正開始處理BeanDefinition的屬性。在方法最後還對別名進行了處理,然後把valueResolver傳遞給beanFactoryToProcess,供其用於處理註解的解析等
public void visitBeanDefinition(BeanDefinition beanDefinition) {
//處理父類名
visitParentName(beanDefinition);
//處理類名
visitBeanClassName(beanDefinition);
//處理工廠bean名
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());
}
}
上面可以看到:visitBeanDefinition對beanDefinition的一些屬性進行了處理
上面對beanDefinition的處理最終還是會呼叫StringValueResolver的resolveStringValue方法(上面用了java8新增的語法),而最終呼叫的是PropertySourcesPropertyResolver的resolvePlaceholders或resolveRequiredPlaceholders方法(取決於ignoreUnresolvablePlaceholders變數)。下面來挑選resolveRequiredPlaceholders方法來分析
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
//建立一個PropertyPlaceholderHelper,引數傳false
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
PropertyPlaceholderHelper是個工具類
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet<>());
}
上面可以看到傳遞了getPropertyAsRawString方法(java8語法),而該方法是返回key對應的屬性值
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
int startIndex = value.indexOf(this.placeholderPrefix);
while (startIndex != -1) {//存在字首才進入迴圈
//找到字尾的下標
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {//是否有後綴包裹
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
//這裡新增不進去Set集合,說明存在迴圈引用
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
//遞迴解析,拿到最終值?
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
//呼叫處理器拿到屬性值,其實呼叫的是getPropertyAsRawString方法
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
//為空,可能格式是 xxxx:xxx(冒號後面是預設值)
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
//拿到真正的要替換的值
String actualPlaceholder = placeholder.substring(0, separatorIndex);
//預設值
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
//呼叫處理器進行處理
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;//為空就複製為預設值
}
}
}
if (propVal != null) {
//遞迴執行,拿到最終的值
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
//進行入下次迴圈
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
//如果ignoreUnresolvablePlaceholders為true,說明忽略無法解析的${xxx}
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
//報錯了
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
//處理成功過了,移除
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;//沒有後綴包裹,不用解析了
}
}
return result.toString();
}
程式碼雖然有點長,但邏輯還是很清晰
- 先拿到要替換的值(即:${xx}中的xx)
- 遞迴解析,拿到真正要替換的值(即有可能是KaTeX parse error: Expected '}', got 'EOF' at end of input: {{}/xx}這樣的)
- 呼叫placeholderResolver的resolvePlaceholder方法拿到屬性值(其實就會呼叫上面傳遞的getPropertyAsRawString方法)
- 如果屬性值為空,可能是有預設值分隔符(即有:),一番處理後拿到屬性值
- 對屬性值進行遞迴解析,拿到最終屬性值(即可能是在屬性檔案中寫法:xxx=${xxx},一般是多個屬性檔案的情況)
- 最終屬性值不為空就替換,否則根據 ignoreUnresolvablePlaceholders判斷是否拋異常還是繼續解析
- 把解析成功的要替換的值從visitedPlaceholders中移除
- 繼續迴圈或返回結果