Q:在java中如何加载properties文件或者configure文件才是最好的办法呢?
A:当你在考虑如何加载java的资源文件的时候,许多选择都会立即闪现在你的头脑中:files, classpath resources, 还有URLs。尽管上述所有的方法都能得到最终需要的效果,但经验表明classpath resources 和 URLs 是到目前为止最靠谱的选择。
通常情况下,一个配置文件都有一个异常复杂的结构(比如说xml结构的定义),为了简单,下文里我们以name-value对为例子来讲解(非 常类似properties文件的格式)。就算这样,只要你考虑使用inputStream来读取资源文件,你没有理由不采纳下文里提到的办法。
一、邪恶的java.io.File
任何没有java背景的人明显的做法是使用原来的files里的足够简单的办法(通过FileInputStream, FileReader,RandomAccessFile)。但是在java应用的布署来说,这是最差的办法。对于追求轻便和不依赖磁盘位置的代码来说, 在你的代码中使用绝对文件地址并不是一个很好的方式。使用相对路径看上去是个不错的替代方案,不过不要忘记,是相对于jvm运行时当前的路径。这个相对路 径的设置取决于JVM的启动进程,而且会被启动的shell等脚本搞混乱了。如果决定将一些不标准的设置存放依赖最终用户的环境(而且在一些情况下,还未 被验证过是否有用户权限),只要换个环境,(比如说EJB或者是WEB应用服务器),你和用户都不能有更多的基于JVM一开始启动时目录的控制。 java.io.File是java里最不能跨平台的部分。
java模块的做法是将其加入到classpath中去,直接就可用 。考虑EJB jars,Web应用可以打包成war文件,还有其他类似的便利的布署方式。除非你非得用它,还是对其说不吧。
二、Classpath resources
抛开上面所说的坏话,让我们来谈谈更好一点的办法:通过classloaders来加载资源。这样做会更好,因为classloaders本来就扮演了一个资源文件同它的在硬盘上或者其他地方实际位置的关系之间的抽象层。
比如说,你需要从some/pkg/resource.properties加载一个classpath的资源。使用classpath资源是指把文件打 包到jar包里或者是在程序运行前加入到classpath里。你可以通过JVM的参数-classpath在每次程序启动前向classpath中写 入,也可以一次性写到
\classes的位置一直使用。要点是classpath的资源文件布署和java class文件一样,而方便也正是在于此。
从java代码里拿some/pkg/resource.properties有许多方法。首先可以用:
<font face="Monaco" size="2">ClassLoader.getResourceAsStream ("some/pkg/resource.properties");<br />Class.getResourceAsStream ("/some/pkg/resource.properties");<br />ResourceBundle.getBundle ("some.pkg.resource");</font>
此外,如果代码在一个位于some.pkg包的class里,下面的代码也可以很好地工作:
<font face="Monaco" size="2">Class.getResourceAsStream ("resource.properties");<br /></font>
注意两者在参数上微妙的不同之处。所有的getResourceAsStream方法都使用斜杠分割包名的间隔,而且资源文件要包括扩展 名。和resource bundles相比,后者更像是java标识符,用点标识包(并且没有文件名后缀)。这是理所当然的,因为resource bundle(资源绑定)可以不仅仅是properties文件,比如还可以是class文件。
稍微有点复杂的地方,java.lang.Class的getResourceAsStream方法的实例方法可以执行相对于包的资源搜索 (同样也很灵活,见 http://www.javaworld.com/javaworld/javaqa/2002-11/02-qa-1122- resources.html)。为了区分相对和绝对的资源名字,Class.getResourceAsStream()用斜杠开头表示绝对路径。通 常,如果你在代码里不用相对于package的资源的话,没有必要使用这个方法。
ClassLoader.getResourceAsStream(), Class.getResourceAsStream(), ResourceBundle.getBundle()之间微小的区别很容易造成混乱。下面这个表记录了他们之间的特点:
方法操作差异
Method | Parameter format | Lookup failure behavior | Usage example |
ClassLoader.getResourceAsStream() | “/”-separated names; no leading “/” (all names are absolute) | Silent (returns null) | this.getClass().getClassLoader() .getResourceAsStream (“some/pkg/resource.properties”) |
Class.getResourceAsStream() | “/”-separated names; leading “/” indicates absolute names; all other names are relative to the class’s package | Silent (returns null) | this.getClass() .getResourceAsStream (“resource.properties”) |
ResourceBundle.getBundle() | “.”-separated names; all names are absolute; .properties suffix is implied | Throws unchecked java.util. MissingResource Exception |
ResourceBundle.getBundle(“some.pkg.resource”) |
从数据流到java.util.Properties
你应该注意到之前提过的方法只是一半的措施:他们都只返回输入数据流,而并没有类似键值对的返回。幸运的是,把数据加载成一个列表很简单(可以实例化java.util.Properties即可)。因为你会发现你在一再地使用它,搞成几个帮助类是有意义的。
java的内置方法给classpath加载指定的资源有小小的不同也是一件讨厌的事情,特别是当一些资源名字是硬编码但你现在想换另一个加载 的方法时。抽取出来一些东西是有意义的,类似斜杠和点作为命名的分隔符等等。干脆一点,帖出我的properties的处理类,代码在这里下 载:http://www.javaworld.com/javaqa/2003-08/01-qa-0808- property.html?page=2#resources
[代码略]
在loadProperties方法的javadoc里的注释显示这个方法的输入参数要求非常随意:接受资源名字被任何按照原生的方法设计(除了相关的包外尽量使用Class.getResourceAsStream())的格式化而且使其本地实现标准化。
短一点的loadProperties() 公用方法决定了哪个类加载器加载资源。下面的解决方法是合理的但并非完美。你应该考虑使用文章” Find a Way Out of the ClassLoader Maze“里提到的技术来代替。
注意有两个条件编译的常量来控制loadProperties的行为,你可以调整它们来适应你的口味:
THROW_ON_LOAD_FAILURE选择loadProperties在找不到资源的情况下是抛异常还是返回空。
LOAD_AS_RESOURCE_BUNDLE 选择资源在查找的时候是绑定资源还是给出的classpath资源。
将LOAD_AS_RESOURCE_BUNDLE设置为true是不好的,除非你是想通过编译到java.util.ResourceBundle的本地化支持得到好处。而且,java内部缓存了资源绑定,所以你可以避免重复地对同样的资源名字进行磁盘文件读写。
更多的事情
我有意省略了一个有趣的classpath资源加载方法,ClassLoader.getResources。尽管它不常使用,但其允许许多有用的选项,这些选项在设计高度定制和简单配置的应用程序非常有用。
我没有在这文章里讨论ClassLoader.getResources是因为它值得专门写一篇文章。碰巧,这个方法与剩下的取得资源的方法联系紧密:Java.net.URLs。你可以使用他们,因为资源描述符要比classpath资源名字符要更通用。