SpringBoot的遷移過程當中碰到的奇葩坑java
原Spring項目遷移成SpringBoot項目,早前使用 PropertyPlaceholderConfigurer 配置properties引入,在使用properties中的配置項時報錯,如 ${user.name} 配置項找不到,有時又能夠但 application.properties 中配置項找不到。spring
要找到問題關鍵先要知道Spring處理配置項注入是怎麼實現的。apache
<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>
@Value("${user.name}") private String username;
PropertyPlaceholderConfigurer 爲例,實現 BeanFactoryPostProcessor 接口因此bean Definition 載入完畢後會被調用app
postProcessBeanFactory()
在該方法中主要是遍歷全部的BeanDefinition,找到那些 ${} 的配置項,而後替換掉post
visitor.visitBeanDefinition(bd); // 遍歷BeanDefinition
最後,將 StringValueResolver 加到 BeanFactory 中留做他用(如 AutowiredAnnotationBeanPostProcessor 有用,下面就分析)this
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
全部 @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)使用SpringBoot的最佳實踐