加载文件问题
1. 使用@PropertySource(“classpath:application.properties”)和@Value加载配置文件属性的时候启动报错
@Configuration
@PropertySource("classpath:application.properties")
@Data
@Component
public class CacheConfigBean {
private int retryCount;
private int elapsedTimeMs;
private String connectString;
private int sessionTimeoutMs ;
private int connectionTimeoutMs;
@Value("${spring.cache.caffeine.spec}")
private String springCacheCaffeineSpec;
@Value("${zookeeper.redis.node}")
private String zookeeperRedisNode;
@Value("${DAFULT_REDIS_TIME_OUT}")
private String dafultRedisTimeOut;
}
2. 启动的报错日志
Caused by: java.lang.IllegalArgumentException:
Could not resolve placeholder 'spring.cache.caffeine.spec' in value "${spring.cache.caffeine.spec}"
是哪里配置出问题了呢,因为所有的module的配置项都是一样的,其他的都可以读取配置文件信息,并且替换属性。为啥到这里就直接报错,启动报错直接终止了。
3. 分析原因
寻找module的不同点,在一个不起眼的角落的配置引起了我的注意,一个名为:spring-all.xml的配置文件里面配置了PropertyPlaceholderConfigurer。这个配置目的是为了把xml中的"$ {}“可以用其他的properties中的属性在beanfactorypostProcoessor容器后置处理器中进行替换BeanDefinition里面的”${}"属性。
<!-- 配置的资源文件 -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true"></property>
<property name="locations">
<list>
<value>classpath:dubbo-customer.properties</value>
<value>classpath:dubbo-provider.properties</value>
</list>
</property>
</bean>
PropertyPlaceholderConfigurer是把所有locations配置的所有配置文件的属性全部加载到Properties缓存区里面
- 加载配置文件和属性postProcessBeanFactory是通过Properties mergedProps = mergeProperties();加载合并的配置文件的属性
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
// 加载所有的locations配置文件的属性再合并到一个Properties里面
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
- 把location的配置文件属性加载并添加到props里面loadProperties
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
for (Resource location : this.locations) {
if (logger.isTraceEnabled()) {
logger.trace("Loading properties file from " + location);
}
try {
// 把location的配置文件属性加载并添加到props里面
PropertiesLoaderUtils.fillProperties(
props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
}
catch (FileNotFoundException | UnknownHostException ex) {
if (this.ignoreResourceNotFound) {
if (logger.isDebugEnabled()) {
logger.debug("Properties resource not found: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
}
- 再封装StringValueResolver解析器,然后把这个解析器存入PlaceholderConfigurerSupport的embeddedValueResolvers里面,以待后续@Value解析使用替换指定的字符串
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
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);
}
}
}
// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
- addEmbeddedValueResolver把解析器存入PlaceholderConfigurerSupport的embeddedValueResolvers里面。这里添加进入进入的顺序是系统默认读取的顺序,是不可控的
@Override
public void addEmbeddedValueResolver(StringValueResolver valueResolver) {
Assert.notNull(valueResolver, "StringValueResolver must not be null");
this.embeddedValueResolvers.add(valueResolver);
}
- 加载资源的BeanFactoryPostProcessors去解析配置文件的PropertyPlaceholderConfigurer把解析好的文件属性存入properties缓存里面,但是注解@PropertySource是使用的PropertySourcesPlaceholderConfigurer去load加载配置文件的属性,然后存入它自己的localproperties缓存。
相关连PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer信息 - 因为这里添加的顺序的不可控,导致出现了Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder ‘spring.cache.caffeine.spec’ in value “${spring.cache.caffeine.spec}”;首先加载第一个是PropertyPlaceholderConfigurer,然后才是PropertySourcesPlaceholderConfigurer;
- PropertyPlaceholderConfigure是没有spring.cache.caffeine.spec属性
- PropertySourcesPlaceholderConfigurer有spring.cache.caffeine.spec属性
- 经过类初始化过程:getBean->doGetBean->createBean
- createBeanInstance:通过策略创建bean包装实例——这里可能回去递归初始化构造器中参数的类
- 调用所有的 MergedBeanDefinitionPostProcessors,一个扩展点
- 提前暴露引用,允许循环引用,一般为 true
- populateBean:注入依赖类和相关属性——这里递归初始化注解的依赖类
- initializeBean:调用初始化方法
- registerDisposableBeanIfNecessary:搬到 bean 销毁的方法
- resolveEmbeddedValue解析@Value中的"${}"相关的字段属性,并且从properties中的属性做替换。
@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
if (value == null) {
return null;
}
String result = value;
// 这里的顺序:0- PropertyPlaceholderConfigure缓存解析器
// 1- PropertySourcesPlaceholderConfigure缓存解析器
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
if (result == null) {
return null;
}
}
return result;
}
- 由于使用第一个PropertyPlaceholderConfigure缓存解析器解析对应的@Value("${spring.cache.caffeine.spec}") 字符串失败,直接报错 Could not resolve placeholder ‘spring.cache.caffeine.spec’ in value "
${spring.cache.caffeine.spec}" 。这里直接报错以后是不会再走后面的流程,所以是不会去调用PropertySourcesPlaceholderConfigure缓存解析器再解析${spring.cache.caffeine.spec} 这个配置属性。程序启动直接终止,整个启动直接玩完。
脑瓜子嗡嗡的了吧,是不是这个是检查其他的配置都是一样的,但是这个项目就是加载配置属性的时候报错,然后你就会和我一样马上徘徊在类加载的配置文件加载解析和BEAN初始化后对属性填充替换进行源码分析里遨游了。
给你提供解决方案
- 某些业务可以直接把配置项去掉,使用默认的配置加载,就不会再出现此类不好寻找的问题了。
- 也可以直接把PropertyPlaceholderConfigure修改为PropertySourcesPlaceholderConfigurer就可以了。
<!-- 配置的资源文件 -->
<bean id="propertyConfigurer"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true"></property>
<property name="locations">
<list>
<value>classpath:dubbo-customer.properties</value>
<value>classpath:dubbo-provider.properties</value>
</list>
</property>
</bean>