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代码如下:

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"> 

       




#{ systemProperties['confile'] != null?
systemProperties['confile']:
(systemProperties['Confpath'] != null
?'file:'+systemProperties['Confpath']+'config.properties':
'classpath:config_'+systemProperties['ServerLocation']+'_'+systemProperties['ServerType']+'.properties')}





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获得属性值)
* 主动添加应用配置对应的 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页面打不开,被墙了?

你可能感兴趣的:(Java)