Springboot 隨筆(2)-- Properties 配置一坑

SpringBoot的遷移過程當中碰到的奇葩坑java

什麼坑?

原Spring項目遷移成SpringBoot項目,早前使用 PropertyPlaceholderConfigurer  配置properties引入,在使用properties中的配置項時報錯,如 ${user.name} 配置項找不到,有時又能夠但 application.properties 中配置項找不到。spring

要找到問題關鍵先要知道Spring處理配置項注入是怎麼實現的。apache

Spring 配置項注入

1. Spring注入方式

  1. XML注入
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
        <property name="driverClassName" value="${spring.datasource.driver}"/>
        <property name="url" value="${spring.datasource.url}"/>
        <property name="username" value="${spring.datasource.username}"/>
        <property name="password" value="${spring.datasource.password}"/>
        <property name="initialSize" value="${spring.datasource.initialSize}"/>
        <property name="maxActive" value="${spring.datasource.maxActive}"/>
        <property name="maxIdle" value="${spring.datasource.maxIdle}"/>
        <property name="minIdle" value="${spring.datasource.minIdle}"/>
        <property name="maxWait" value="${spring.datasource.maxWait}"/>
    </bean>

     

  2. @Value Java代碼中注入
    @Value("${user.name}")
    private String username;

     

2. 實現原理

2.1 XML

PropertyPlaceholderConfigurer 爲例,實現 BeanFactoryPostProcessor  接口因此bean Definition 載入完畢後會被調用app

postProcessBeanFactory()

在該方法中主要是遍歷全部的BeanDefinition,找到那些 ${} 的配置項,而後替換掉post

visitor.visitBeanDefinition(bd); // 遍歷BeanDefinition

最後,將 StringValueResolver 加到 BeanFactory 中留做他用(如 AutowiredAnnotationBeanPostProcessor 有用,下面就分析)this

beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);

2.2 @Value

全部 @Value@Autowired  註解都是由 AutowiredAnnotationBeanPostProcessor 來實現處理的,因爲實現了接口 InstantiationAwareBeanPostProcessor ,因此會自動在實例完後調用url

PropertyValues postProcessPropertyValues(PropertyValues var1, PropertyDescriptor[] var2, Object var3, String var4)


public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
    InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs);

    try {
        metadata.inject(bean, beanName, pvs); // 注入的主邏輯
        return pvs;
    } catch (BeanCreationException var7) {
        throw var7;
    } catch (Throwable var8) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var8);
    }
}

在 metadata.inject 中主要是調用 BeanFactory 的spa

value = AutowiredAnnotationBeanPostProcessor.this.beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

...

field.set(bean, value);
beanFactory.resolveDependency
  -> beanFactory.doResolveDependency
    -> beanFactory.resolveEmbeddedValue

public String resolveEmbeddedValue(String value) {
    String result = value;

    StringValueResolver resolver;
    for(Iterator var3 = this.embeddedValueResolvers.iterator(); var3.hasNext(); result = resolver.resolveStringValue(result)) {
        resolver = (StringValueResolver)var3.next();
        if(result == null) {
            return null;
        }
    }

    return result;
}

最終仍是獲取 embeddedValueResolvers 來處理.net

爲何會報錯?

SpringBoot 默認就會註冊一個 PropertySourcesPlaceholderConfigurer,當再配置一個 PropertyPlaceholderConfigurer 時就會存在兩個,一部分properties在前者、一部分在後者,那麼確定會執行其中一個時報錯。code

在執行 resolver.resolveStringValue(result) 時,最終 PlaceholderResolvingStringValueResolver 的 helper 中

protected String parseStringValue(String strVal, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

  ...
  if (value != null) {
     ...
  } else {
    if(!this.ignoreUnresolvablePlaceholders) {
      throw new IllegalArgumentException("Could not resolve placeholder \'" + placeholder + "\'" + " in string value \"" + strVal + "\"");
    }
  }
}

就是解析不到${}時就會報錯。

解決方法

  1. 將原來的 PropertySourcesPlaceholderConfigurer 去掉
  2. 改寫 PropertySourcesPlaceholderConfigurer,將 Environment 加入
  3. 將兩個Configurer的 ignoreUnresolvablePlaceholders 都配置成true

三種方式任選,建議(1)使用SpringBoot的最佳實踐 

相關文章
相關標籤/搜索