Spring Environment getProperty bug解决

问题

   公司的项目使用了Spring,用的xml配置,并且需要spring加载一个property配置文件---可能大部分企业Java项目都是这样。

  由于一个特殊的需求,需要spring解析的Properties对象,也需要获取一些配置的值。

 配置文件的选择逻辑是这样的:如果指定了配置文件URL ,则使用此配置,否则 ,如果指定了配置文件的本地文件路径,则使用此配置,否则使用classpath内配置。

 对于获取指定属性,如 "server.port" ,原来的做法是这样的:按照配置文件选择的逻辑自己读一遍properties文件去获取属性。

 但问题来了:现在配置文件的选择逻辑变了,原来的自己读properties文件的代码读不到东西了。这种方式容易出错,并且系统傻傻的读了两遍配置。


       于是去查看Spring文档,发现ApplicationContext有个 getEnvironment() 方法,返回Environment对象,Environment有getProperty()方法。

离目标很接近了,赶紧试试,发现悲剧了,什么也获取不到--BUG ?!;

 然后测试改用Annotation方式配置:

@Configurable
@Configuration
@PropertySource("${confile: file:/${Confpath}/config.properties }")
public class PropConfig {

  public @Bean PropertySourcesPlaceholderConfigurer propertyConfig() {
      return new PropertySourcesPlaceholderConfigurer();
  }


}

 发现注解配置方式下Environment::getProperty可以获取到属性值。但也有问题,注解的el表达式不支持嵌套

 也就是说支持三元表达式 A!=null? A:B , 但不支持 A!=null?(B!=null? B:C) .也就无法再支持classpath的配置


解决

1个思路是忽略classpath配置,部分注解,部分xml:
(PropConfig需要加注解:@ComponentScan(basePackages = "包名"))

ApplicationContext annCtx = null;
if (System.getProperty("confile") != null || System.getProperty("Confpath") != null) {
annCtx = new AnnotationConfigApplicationContext(PropConfig .class);
}
ApplicationContext ac = new 
            ClassPathXmlApplicationContext(new String[] { "appContext.xml" }, annCtx);

不够好,也算能解决问题。

另一个思路是这样,完全用xml配置,主动往Environment添加应用的配置对应的PropertySource对象,
看源码发现Environment 的getProperty是从PropertySource 的list获取配置的
ConfigurableEnvironment 的 getPropertySources() 的addLast | addFirst 等方法来添加PropertySource 
那么我们主动构造一个再加进去好了,xml代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context 
          http://www.springframework.org/schema/context/spring-context-4.2.xsd 
          http://www.springframework.org/schema/beans 
          http://www.springframework.org/schema/beans/spring-beans-4.2.xsd"> 

        <context:component-scan base-package="test.app" />

<!-- 加载相应环境配置文件,使用自定义的配置处理类 -->
<bean id="propertyConfigurer" class="test.app.cfg.PropertyConfigurer">
<property name="location">
<value>#{ systemProperties['confile'] != null?
systemProperties['confile']:
(systemProperties['Confpath'] != null
?'file:'+systemProperties['Confpath']+'config.properties':
'classpath:config_'+systemProperties['ServerLocation']+'_'+systemProperties['ServerType']+'.properties')}
</value>
</property>
</bean>

</beans>


PropertyConfigurer.java 代码如下:

public class PropertyConfigurer extends org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
		implements ApplicationContextAware {
	private Properties props;
	private ApplicationContext applicationContext;
	private String resourceName;

	protected Properties mergeProperties() throws IOException {
		Properties props = super.mergeProperties();
		if (applicationContext != null) {
			addPropertySource(applicationContext, props);
		}
		this.props = props;
		return props;
	}

	public void setLocation(Resource location) {
		super.setLocation(location);
		this.resourceName = location.getDescription();
	}

	public void setLocations(Resource... locations) {
		super.setLocations(locations);
                int LEN; 
		if (locations != null && (LEN = locations.length) > 0) {
			StringBuilder sb = new StringBuilder(LEN * 50);
			sb.append("[ ").append(locations[0].getDescription());
			for (int i = 1; i < LEN; i++) {
				sb.append(", ").append(locations[i].getDescription());
			}
			sb.append(" ]");
			this.resourceName = sb.toString();
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
		if (props != null) {
			addPropertySource(applicationContext, props);
		}
	}


	/**
	 * 规避Spring 的 bug(xml配置方式,无法通过 Environment获得属性值)<br/>
	 * 主动添加应用配置对应的 PropertySource 以解决问题
	 * 
	 * @param applicationContext
	 * @param props
	 */
	private void addPropertySource(ApplicationContext applicationContext, Properties props) {
		ConfigurableEnvironment env = (ConfigurableEnvironment) applicationContext.getEnvironment();
		PropertySource<?> src = null;
		String resName = resourceName != null ? resourceName : "AppProps";
		try {
			Constructor<?> con = ResourcePropertySource.class.getDeclaredConstructor(String.class, String.class,
					Map.class);
			con.setAccessible(true);
			src = (ResourcePropertySource) con.newInstance(resName, null, props);
		} catch (Exception e) {
			src = new PropertiesPropertySource(resName, props);
		}
		if (src != null) {
			// add PropertySource
			env.getPropertySources().addLast(src);
		}
	}


}

总结

     Spring 4.3.5 版本是目前最新的稳定版了,PropertySource 被称为 “新的属性管理API”, 但从3.1版本就有了
     没想到还是有这样的bug。

     从应用层面来规避框架的bug并不是最令人满意的结果,解决的成本比较低,仍有维护成本,无奈Spring的Bug jira页面打不开,被墙了?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值