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页面打不开,被墙了?

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis 目录(?)[-] mybatis实战教程mybatis in action之一开发环境搭建 mybatis实战教程mybatis in action之二以接口的方式编程 mybatis实战教程mybatis in action之三实现数据的增删改查 mybatis实战教程mybatis in action之四实现关联数据的查询 mybatis实战教程mybatis in action之五与spring3集成附源码 mybatis实战教程mybatis in action之六与Spring MVC 的集成 mybatis实战教程mybatis in action之七实现mybatis分页源码下载 mybatis实战教程mybatis in action之八mybatis 动态sql语句 mybatis实战教程mybatis in action之九mybatis 代码生成工具的使用 mybatis SqlSessionDaoSupport的使用附代码下载 转自:http://www.yihaomen.com/article/java/302.htm (读者注:其实这个应该叫做很基础的入门一下下,如果你看过Hibernate了那这个就非常的简单) (再加一条,其实大家可以看官方的教程更好些:http://mybatis.github.io/mybatis-3/,而且如果英文不是很好的那就看中文的:http://mybatis.github.io/mybatis-3/zh/sqlmap-xml.html) 写在这个系列前面的话: 以前曾经用过ibatis,这是mybatis的前身,当时在做项目时,感觉很不错,比hibernate灵活。性能也比hibernate好。而且也比较轻量级,因为当时在项目中,没来的及做很很多笔记。后来项目结束了,我也没写总结文档。已经过去好久了。但最近突然又对这个ORM 工具感兴趣。因为接下来自己的项目中很有可能采用这个ORM工具。所以在此重新温习了一下 mybatis, 因此就有了这个系列的 mybatis 教程. 什么是mybatis MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan Old Java Objects,普通的Java对象)映射成数据库中的记录. orm工具的基本思想 无论是用过的hibernate,mybatis,你都可以法相他们有一个共同点: 1. 从配置文件(通常是XML配置文件中)得到 sessionfactory. 2. 由sessionfactory 产生 session 3. 在session 中完成对数据的增删改查和事务提交等. 4. 在用完之后关闭session 。 5. 在java 对象和 数据库之间有做mapping 的配置文件,也通常是xml 文件。 mybatis实战教程(mybatis in action)之一:开发环境搭建 mybatis 的开发环境搭建,选择: eclipse j2ee 版本,mysql 5.1 ,jdk 1.7,mybatis3.2.0.jar包。这些软件工具均可以到各自的官方网站上下载。 首先建立一个名字为 MyBaits 的 dynamic web project 1. 现阶段,你可以直接建立java 工程,但一般都是开发web项目,这个系列教程最后也是web的,所以一开始就建立web工程。 2. 将 mybatis-3.2.0-SNAPSHOT.jar,mysql-connector-java-5.1.22-bin.jar 拷贝到 web工程的lib目录. 3. 创建mysql 测试数据库和用户表,注意,这里采用的是 utf-8 编码 创建用户表,并插入一条测试数据 程序代码 程序代码 Create TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userName` varchar(50) DEFAULT NULL, `userAge` int(11) DEFAULT NULL, `userAddress` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; Insert INTO `user` VALUES ('1', 'summer', '100', 'shanghai,pudong'

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值