起因
在实际开发过程中经常需要加载各种各样的配置文件..比如数据库的用户名密码,要加载的组件,bean等等..但是这种配置在各个环境中经常是不一样的....比如开发环境和测试环境,真实的生产环境..
这种配置不可能写死在代码里.很有可能会写在XML或者properties文件里..
这些文件可以在spring里去加载. 比如
1 <bean id="propertyPlaceholderConfigurer" 2 class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 3 <property name="locations"> 4 <list> 5 <value>classpath*:mysql.properties</value> 6 </list> 7 </property> 8 </bean>
1 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 2 destroy-method="close"> 3 <property name="driverClassName" value="${jdbc.driverClassName}" /> 4 <property name="url" value="${jdbc.url}" /> 5 <property name="username" value="${jdbc.username}" /> 6 <property name="password" value="${jdbc.password}" /> 7 </bean>
比如不同环境下的数据库账号密码就可以通过配置不同的mysql.properties来解决.不同环境下需要加载的bean也可以通过这种方式解决.
然而这里有一个关键问题没有解决:
打包的结果是得到一个war包.mysql.properties会被打包到classpath的目录里去.
打包一般都是从一个环境里下统一打出来的.怎么让它把不同的配置打进去?
解决办法
其实我们并不需要把mysql.properties打包进去.可以通过设置maven的war plugin过滤掉mysql.properties.
既然不是打包进去的.那我们肯定要让Spring能够读取到这个配置.
直接将配置的路径写死在程序里显然不是一个好方法.因为不同机器上的mysql.properties差异文件的路径绝大多数都是不同的..
这里采用相对路径是个不错的办法...
我们先来看看Spring是怎么解析我们在XML中配置的路径(classpath*:mysql.properties)的.
在创建applicationContext的时候会根据xml的配置加载bean的配置
我们在PropertyPlaceholderConfigurer这个bean里配置了要加载mysql.properties.所以applicationContext会读取这个properties的地址.
在XML中这只是一个字符串...所以Spring还需要将它解析成具体的Resource才行....
这里用到了PathMatchingResourcePatternResolver类.
第323行可以发现怎么字符串mysql.properties这是一个相对路径,它要转化成绝对路径,再转化成Resource才行.转化成绝对路径最后靠的是ClassLoader的getResources方法.
1 public Enumeration<URL> getResources(String name) throws IOException { 2 Enumeration[] tmp = new Enumeration[2]; 3 if (parent != null) { 4 tmp[0] = parent.getResources(name); 5 } else { 6 tmp[0] = getBootstrapResources(name); 7 } 8 tmp[1] = findResources(name); 9 10 return new CompoundEnumeration<>(tmp); 11 }
我没有tomcat的源码..但是从java的ClassLoader类中也可以看到会循环向上级ClassLoader查找资源,然后返回.
那么前面的问题就可以解决了..
我可以把mysql.properties放到这个webapp的classloader或者它父ClassLoader的classpath下.那Spring就能找到这个资源.
然后我们来看一下资料..
从资料(文章及图片)中我们可以得知:
webapp的classpath是WEB-INF下的lib和classes
我们的mysql.properties不可能放到这个里面.因为这个目录是被打到war包里面的...
那我们可以将mysql.properties放到sharedLoader或者commonLoader对应的classpath里即可...
那怎么得到那2种ClassLoader的classpath呢...
从 http://www.cnblogs.com/penzz/p/3241870.html 资料中我们可以得知.可以修改shared.loader去修改共享的class目录.
所以我们修改catalina.properties下的shared.loader.然后把mysql.properties放到shared.loader指定的目录下即可.
思考
配置当然也可以放到commonLoader的classpath下...但是显然是不需要的.因为Catalina服务器不需要用到我们的配置文件....
即使把配置放到shared.loader的classpath下也是多个web项目共享的...每个webapp项目都能够获取这个配置文件...
但是因为实际上可以通过一个tomcat只部署一个项目来解决这样的问题...多个webapp为了不相互干扰也不会用同1个tomcat启动的..不然1个webapp要重启tomcat也会影响其他webapp....所以sharedloader也不失为一种不错的解决办法...
当然最佳的解决办法还是让webappClassLoader能够读取到自己的配置文件.多个webappClassLoader不共享配置....但是貌似我的tomcat6不支持...不知道高版本的tomcat是不是有什么更好的方法....
总结
最后再来总结下实现差异化配置的方法(基于tomcat):
1.把差异化的东西放到特定的properties中(不一定真要是properties.只是它比较简单...或者把所有配置都放到XML中,但是把启动哪种配置的开关选项放到properties里),在打包的时候过滤掉这些文件.
2.设置catalina.properties.修改shared.loader的值为一个指定的目录
3.把差异化的properties文件放到shared.loader指定的目录下..
这样不同的环境下就可以有不同的配置文件了.而且每次打包也不需要关注那些差异的配置了..
另外不同的web容器肯定有不同的方法去设置classpath.不管怎么设置,只要让它能找到我们的配置文件就可以了..比如公司用的是weblogic...做法就比较简单粗暴...通过修改sh脚本直接在原本的classpath目录下再增加了一个classpath.......或许tomcat也可以....只是我一直实验失败.....