java:自动搜索不同位置的properties文件并加载

在项目开发中,对于一些程序运行的参数可能经常需要根据实际情况修改或调整,所以这些参数我们不会在代码中写死,而是它们保存在properties(或xml)文件中,以方便修改。程序运行时会先从properties文件中读取这些参数用于系统初始化。
举个例子来说吧,比如下面这个代码结构,我们在项目的根目录下建了一个conf目录,保存了3个properties文件。
这里写图片描述
这些文件会在项目编译时与class文件一起被打入jar包中。
我们可以通过 clazz.getClassLoader().getResource("conf/fodbmgr_code.properties")的方式读取这个位于jar包中的参数配置文件。

关于ClassLoader.getResource的用法细节,又可以写一大堆,这里就不展开讲了,网上有很多文章讲的非常详细。

但是,如果我们要运行前要修改fodbmgr_code.properties中的参数,难道要把jar解开再修改properties文件然后再把jar重新打包么?这尼玛太不人道了吧?
实际上我们做法是这样做的:
在项目部署后(比如部署到tomcat),我们会将fodbmgr_code.properties文件在WEB-INF/conf文件夹下复制一份,程序运行时会也会读取到WEB-INF/conf文件夹下的properties文件。
那么你会问了,这样以来,系统中存在两个同样的fodbmgr_code.properties文件,一个在jar包中,一个在WEB-INF/conf文件夹下,如果这两个文件中都定义了同样的参数但值不同,到底以哪个为准呢?
这就是涉及到搜索优先序问题,我们的做法是,先用ClassLoader.getResource搜索加载properties文件,然后再加载WEB-INF/conf文件夹下的properties文件,如果存在同名参数,就用当前的值覆盖前面的定义。也就是优先使用后加载的参数。这样以来,当我们需要修改fodbmgr_code.properties中的某个参数时,只需要在WEB-INF/conf文件夹下的fodbmgr_code.properties文件添加一行就好了,
比如下面这个是jar包中的fodbmgr_code.properties文件内容,定义了全部的默认参数。

#configuation for database connection
#whether use debug config.set to 'true' use config with 'debug' prefix,otherwise use 'work' config.
isDebug=true
#work parameter
work.jdbc.driver=oracle.jdbc.OracleDriver
work.jdbc.url=jdbc:oracle:thin:@facedb:1521:orcl
work.jdbc.username=testuser
work.jdbc.password=testuser
work.c3p0.minPoolSize=10
work.c3p0.maxPoolSize=100
work.c3p0.maxIdleTime=120
work.c3p0.idleConnectionTestPeriod=120

#debug parameter
debug.jdbc.driver=oracle.jdbc.OracleDriver
debug.jdbc.url=jdbc:oracle:thin:@facedb:1521:orcl
debug.jdbc.username=testuser
debug.jdbc.password=testuser
debug.c3p0.minPoolSize=10
debug.c3p0.maxPoolSize=100
debug.c3p0.maxIdleTime=120
debug.c3p0.idleConnectionTestPeriod=120

isDebug定义为true,用户名和密码都定义为testuser。在实际运行环境这样可能是不合适的,所以要修改这些参数。我们可以在WEB-INF/conf文件夹下创建一个同名文件,内容如下

isDebug=false
work.jdbc.username=workuser
work.jdbc.password=87s@1ads<7098>

程序在初始化时,先读取jar包中的fodbmgr_code.properties文件,然后再读取WEB-INF/conf文件夹下的fodbmgr_code.properties文件,isDebugwork.jdbc.username,work.jdbc.password几个参数的值就更新为你指定的值了。

好了道理都说明白了,其实代码也不复杂,下面是完整的java代码:

package net.gdface.utils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

public class ConfigUtils {
	/**
	 * 顺序加载不同位置的properties文件,加载顺序为:<br>
	 * 1.调用{@link ClassLoader#getResource(String)}方法在{@code clazz}所在位置查找,如果失败则抛出异常<br>
	 * 2.如果class在jar包中,则尝试读取在jar所在位置../confFolder/propFile,tomcat下即为WEB-INF/confFolder/propFile<br>
	 * 3.如果环境变量envVar定义,则从envVar指定的目录下读取propFile<br>
	 * 4.user.dir下查找confFolder/propFile加载配置<br>
	 * 后面的配置变量会覆盖前面的定义<br>
	 * @param propFile 要加载的properties文件名,为{@code null}或空时抛出异常 {@link IllegalArgumentException}
	 * @param confFolder popFile所在文件夹,{@code null}时使用默认值'conf'
	 * @param envVar 环境变量名 用于定义propFile位置,可为{@code null}
	 * @param clazz 用于获取 {@link ClassLoader}的类,为null时使用本类的class
	 * @param showProp 加载后是否显示所有值
	 * @return 返回加载后的{@link Properties}对象
	 */
	public static Properties loadAllProperties(String propFile, String confFolder, String envVar, Class<?> clazz, boolean showProp) {
		if(null==propFile||propFile.isEmpty())
			throw new IllegalArgumentException("the argument 'propFile' must not be null or empty");
		if (null == confFolder)
			confFolder = "conf";
		if (null == clazz)
			clazz = ConfigUtils.class;
		final String fileSeparator = System.getProperty("file.separator");
		String prop_path = confFolder.concat(System.getProperty("file.separator")).concat(propFile);
		Properties props = new Properties();
		Set<File> loaded_files = new HashSet<File>();
		try {
			// 在jar包中查找默认配置文件
			URL url = clazz.getClassLoader().getResource(prop_path.replace(fileSeparator, "/"));
			if(null==url)
				throw new ExceptionInInitializerError(String.format("not found default properties %s", prop_path));
			loadProperties(url, props);
		} catch (Exception e) {
			// 默认配置必须加载成功否则抛出异常
			throw new ExceptionInInitializerError(String.format("fail to load default properties(加载默认配置文件失败) %s cause by %s", prop_path,
					e.getMessage()));
		}
		try {
			// 加载 jar包所在位置 ../conf/cassdk.properties
			URL class_location = clazz.getProtectionDomain().getCodeSource().getLocation();
			if (class_location.toString().endsWith(".jar")) {
				// jar包所在目录的父目录,tomcat下即为WEB-INF
				File jar_parent = new File(class_location.getPath()).getParentFile().getParentFile();
				if (null != jar_parent) {
					File conf_file = new File(jar_parent, prop_path);
					if (conf_file.isFile()) {
						loadProperties(conf_file.toURI().toURL(), props);
						loaded_files.add(conf_file);
					}
				}
			}
		} catch (Exception e) {
		}
		try {
			// 通过环境变量查找properties文件
			if (envVar != null && !envVar.isEmpty()) {
				String cf = System.getProperty(envVar);
				if (null != cf&&!cf.isEmpty()) {
					File env_file = new File(cf, propFile);
					if (!loaded_files.contains(env_file)) {
						loadProperties(env_file.toURI().toURL(), props);
						loaded_files.add(env_file);
					}
				} else
					log("not defined environment variable '%s'", envVar);
			}
		} catch (Exception e) {
		}
		try {
			// 在当前路径下查找配置文件
			File propInUserDir = new File(System.getProperty("user.dir"), prop_path);
			if (propInUserDir.isFile() && !loaded_files.contains(propInUserDir)) {
				loadProperties(propInUserDir.toURI().toURL(), props);
				loaded_files.add(propInUserDir);
			}
		} catch (Exception e) {
		}

		// 输出所有参数值
		if(showProp)
			props.list(System.out);
		return props;
	}

	/**
	 * configure with the parameters given in the given url
	 * 
	 * @param url
	 *            the resource filename to be used
	 * @param props
	 *            dest properties to add
	 * @throws IOException
	 */
	private static void loadProperties(URL url, Properties props) throws IOException {
		if (null != url) {
			InputStream is = null;
			try {
				props.load(is = url.openStream());
				log("Load properties from %s", url.toString());
			} finally {
				if (is != null)
					is.close();
			}			
		}
	}
	/*
	 * 这个类原本是用sl4j做日志输出的,因为这里需要输出的日志比较少,<br>
	 * 而且为了增强该类的独立性减少对第三方jar包的依赖,<br>
	 * 在此改为一个简单的log方法来输出信息,输出信息中会包含类名和行号 
	 */
	private static void log(String format, Object ... args){
		System.out.printf("[%s:%d]%s\n", 
				ConfigUtils.class.getSimpleName(),
				Thread.currentThread() .getStackTrace()[2].getLineNumber(),
				String.format(format, args));
	}
}

这个代码中顺序加载4个不同位置的properties文件:
1.jar包中的
2…/confFolder/propFile,即前面我们描述的第二种情况。
3.由环境变量指定的文件夹位置
4.java虚拟定义user.dir文件夹下
第1个位置必须能找到指定的文件否则,就会抛出异常,后续3个位置如果找得到就加载,找不到或抛出任何异常都会被忽略不会报错。

另外,这个代码中只是用来加载.properties文件,如果要加载xml格式的配置文件,只需要把loadProperties方法中使用load方法改为loadFromXML就好了。

下图是tomcat环境下用ConfigUtils.loadAllProperties自动加载properties的log输出,从输出可以看到系统先加载了jar包中的fodbmgr_code.properties(红线部分),然后加载WEB-INF/conf文件夹下的fodbmgr_code.properties文件(黄线部分)

这里写图片描述

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值