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的最佳实践