随笔-7  评论-9  文章-0  trackbacks-0

Smartly load your properties

Strive for disk location-independent code nirvana

By Vladimir Roubtsov, JavaWorld.com, 08/08/2003

http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html

巧妙地加载属性

为独立于磁盘位置的代码天堂而努力

QWhat is the best strategy for loading property and configuration files in Java?

问:在Java中加载属性和配置文件最好的策略是什么?

AWhen you think about how to load an external resource in Java, several options immediately come to mind: files, classpath resources, and URLs. Although all of them eventually get the job done, experience shows that classpath resources and URLs are by far the most flexible and user-friendly options.

答:当你考虑如何在Java中加载一个外部资源时,多种选择立即浮现在脑海:文件,类路径资源和URL。虽然最终它们都能完成工作,但是经验表明类路径资源和URL显然是最灵活最好用的选择。

In general, a configuration file can have an arbitrarily complex structure (e.g., an XML schema definition file). But for simplicity, I assume below that we're dealing with a flat list of name-value pairs (the familiar .properties format). There's no reason, however, why you can't apply the ideas shown below in other situations, as long as the resource in question is constructed from an InputStream.

通常,一个配置文件可以有任意复杂的结构(例如,XML schema定义文件)。但是对于简单的结构,下面我假设我们正在处理一个简单的名-值对列表(常见的.properties格式)。然而,只要讨论的资源是从一个InputStream构造时,你没有理由不在以下列出的其它情况下应用那个想法(选择类路径资源和URL)。

Evil java.io.File

不幸的java.io.File

Using good old files (via FileInputStream, FileReader, and RandomAccessFile) is simple enough and certainly the obvious route to consider for anyone without a Java background. But it is the worst option in terms of ease of Java application deployment. Using absolute filenames in your code is not the way to write portable and disk position-independent code. Using relative filenames seems like a better alternative, but remember that they are resolved relative to the JVM's current directory. This directory setting depends on the details of the JVM's launch process, which can be obfuscated by startup shell scripts, etc. Determining the setting places an unfair amount of configuration burden on the eventual user (and in some cases, an unjustified amount of trust in the user's abilities). And in other contexts (such an Enterprise JavaBeans (EJB)/Web application server), neither you nor the user has much control over the JVM's current directory in the first place.

使用旧文件(通过FileInputStream, FileReader, RandomAccessFile)相当简单,当然考虑到没有Java背景的人这也是明显的方式。但是就Java应用部署的简易性而言文件是最坏的选择。在你的代码中使用绝对文件名不是编写可移植、独立于磁盘位置的代码的方式。使用相对文件名好像是一个较好的替代方法,但是记住它们是相对于JVM的当前路径被解析的。目录设置依赖于JVM加载进程的细节,像启动shell脚本或其他加载JVM的方式使得目录设置变得混乱。将决定如何设置的配置负担加给最终用户是不公平的(在某些情况下,对用户能力的信任是不合理的)。而且在其它环境(像企业JavaBeans(EJB)/Web应用服务器)中,在一开始你和用户对JVM的当前路径都没有太多的控制。

An ideal Java module is something you add to the classpath, and it's ready to go. Think EJB jars, Web applications packaged in .war files, and other similarly convenient deployment strategies. java.io.File is the least platform-independent area of Java. Unless you absolutely must use them, just say no to files.

理想的Java模型是你添加到classpath的东西,准备上手吧。考虑一下EJB jar,打包到.war文件的Web应用程序和其他类似方便的部署策略。除非你绝对要使用文件,否则还是对文件说不。

Classpath resources

Having dispensed with the above diatribe, let's talk about a better option: loading resources through classloaders. This is much better because classloaders essentially act as a layer of abstraction between a resource name and its actual location on disk (or elsewhere).

我们不再对File进行抨击,让我们讨论一种更好的选择:通过类加载器加载资源。因为类加载器在资源名称和在磁盘上(或其他地方)的实际位置之间主要扮演了一个抽象层角色,这是相当好的。

Let's say you need to load a classpath resource that corresponds to a some/pkg/resource.properties file. I use classpath resource to mean something that's packaged in one of the application jars or added to the classpath before the application launches. You can add to the classpath via the -classpath JVM option each time the application starts or by placing the file in the <jre home>\classes directory once and for all. The key point is that deploying a classpath resource is similar to deploying a compiled Java class, and therein lies the convenience.

比如说你需要加载一个类路径资源,它对应some/pkg/resource.properties文件。我使用类路径资源意味着这个资源要打包到应用程序的某个jar中或者在应用程序启动之前加到类路径中。你可以在应用程序每次启动时通过-classpath这个JVM参数添加类路径,或者干脆把那个文件放到<jrehome>\classes目录中。关键点是部署一个类路径资源类似于部署一个已编译的Java,方便的地方就在于此。

You can get at some/pkg/resource.properties programmatically from your Java code in several ways. First, try:

ClassLoader.getResourceAsStream ("some/pkg/resource.properties");

  Class.getResourceAsStream ("/some/pkg/resource.properties");

  ResourceBundle.getBundle ("some.pkg.resource");

你可以通过多种方式在你的Java代码中以编程方式访问到some/pkg/resource.properties。首先,试一试:

ClassLoader.getResourceAsStream ("some/pkg/resource.properties");

  Class.getResourceAsStream ("/some/pkg/resource.properties");

  ResourceBundle.getBundle ("some.pkg.resource");

Additionally, if the code is in a class within a some.pkg Java package, then the following works as well:

Class.getResourceAsStream ("resource.properties");

此外,如果代码在some.pkg这个Java包中的类,以下方式也可以:

Class.getResourceAsStream ("resource.properties");

Note the subtle differences in parameter formatting for these methods. All getResourceAsStream() methods use slashes to separate package name segments, and the resource name includes the file extension. Compare that with resource bundles where the resource name looks more like a Java identifier, with dots separating package name segments (the .properties extension is implied here). Of course, that is because a resource bundle does not have to be backed by a .properties file: it can be a class, for a example.

注意这些方法在参数格式上的细微差别。所有getResourceAsStream()方法都是使用斜杠(/)来分隔包名段,而且资源文件名包括文件扩展名。比较一下,使用资源包(resource bundle)时资源名看上去更像Java标识符,它是以点(.)分隔包名段(这里的.properties扩展名被隐含了)。当然,一个资源包(resource bundle)不必非要是.properties文件:例如,它可以是一个类。

To slightly complicate the picture, java.lang.Class's getResourceAsStream() instance method can perform package-relative resource searches (which can be handy as well, see "Got Resources?"). To distinguish between relative and absolute resource names, Class.getResourceAsStream() uses leading slashes for absolute names. In general, there's no need to use this method if you are not planning to use package-relative resource naming in code.

对于稍复杂的情况,java.lang.Class getResourceAsStream()实例方法可以执行相对于包的资源搜索(这是相当方便的,参见 Got Resources?)。为了区别相对和绝对资源名称Class.getResourceAsStream()对绝对名称使用前导斜杠(/)。一般来说,如果你没有计划在代码中使用相对于包的资源命名没有必要使用这种方法。

It is easy to get mixed up in these small behavioral differences for ClassLoader.getResourceAsStream(), Class.getResourceAsStream(), and ResourceBundle.getBundle(). The following table summarizes the salient points to help you remember:

很容易混淆ClassLoader.getResourceAsStream(), Class.getResourceAsStream(), ResourceBundle.getBundle()在行为上的细小区别。下表总结了一些显著点来帮助你记忆:


 


 

Behavioral differences

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

"."-分隔名称;所有名称都是绝对名称; .properties后缀被隐含。

Throws unchecked
java.util.MissingResourceException

ResourceBundle.getBundle
("some.pkg.resource")

 


 

From data streams to java.util.Properties

You might have noticed that some previously mentioned methods are half measures only: they return InputStreams and nothing resembling a list of name-value pairs. Fortunately, loading data into such a list (which can be an instance of java.util.Properties) is easy enough. Because you will find yourself doing this over and over again, it makes sense to create a couple of helper methods for this purpose.

你可能已经注意到前面提到的一些方法仅是折衷办法:它们返回的是InputStream,没有类似一个名-值对的列表的东西。幸运地是,将数据加载到这样一个列表(可以是java.util.Properties的一个实例)是相当容易的。因为你将发现你在反复做这个加载工作,所以为这个目的创建一组帮助方法是有意义的。

The small behavioral difference among Java's built-in methods for classpath resource loading can also be a nuisance, especially if some resource names were hardcoded but you now want to switch to another load method. It makes sense to abstract away little things like whether slashes or dots are used as name separators, etc. Without further ado, here's my PropertyLoader API that you might find useful (available with this article's download):

类路径资源加载的Java内建方法之间的细小行为差别可能是一个令人讨厌的事情,尤其是有些资源名称是硬编码的但是你现在想切换到另一个加载方法。抽象出一些东西像不管是斜杠(/)还是点(.)作为分隔符等等就变得有意义了。不再罗嗦,这是我的PropertyLoader API你可能发现它是有用的。

package com.jeffma.util;

 

import java.io.InputStream;

import java.util.Enumeration;

import java.util.Locale;

import java.util.Properties;

import java.util.ResourceBundle;

 

public abstract class PropertyLoader {

    /**

     * Looks up a resource named 'name' in the classpath. The resource must map

     * to a file with .properties extention. The name is assumed to be absolute

     * and can use either "/" or "." for package segment separation with an

     * optional leading "/" and optional ".properties" suffix. Thus, the

     * following names refer to the same resource:

     *

     * <pre>

     * some.pkg.Resource

     * some.pkg.Resource.properties

     * some/pkg/Resource

     * some/pkg/Resource.properties

     * /some/pkg/Resource

     * /some/pkg/Resource.properties

     * </pre>

     *

     * @param name

     *            classpath resource name [may not be null]

     * @param loader

     *            classloader through which to load the resource [null is

     *            equivalent to the application loader]

     *

     * @return resource converted to java.util.Properties [may be null if the

     *         resource was not found and THROW_ON_LOAD_FAILURE is false]

     * @throws IllegalArgumentException

     *             if the resource was not found and THROW_ON_LOAD_FAILURE is

     *             true

     */

    public static Properties loadProperties(String name, ClassLoader loader) {

       if (name == null)

           throw new IllegalArgumentException("null input: name");

 

       if (name.startsWith("/"))

           name = name.substring(1);

 

       if (name.endsWith(SUFFIX))

           name = name.substring(0, name.length() - SUFFIX.length());

 

       Properties result = null;

 

       InputStream in = null;

       try {

           if (loader == null)

              loader = ClassLoader.getSystemClassLoader();

 

           if (LOAD_AS_RESOURCE_BUNDLE) {

              name = name.replace('/', '.');

              // Throws MissingResourceException on lookup failures:

              final ResourceBundle rb = ResourceBundle.getBundle(name, Locale

                     .getDefault(), loader);

 

              result = new Properties();

              for (Enumeration keys = rb.getKeys(); keys.hasMoreElements();) {

                  final String key = (String) keys.nextElement();

                  final String value = rb.getString(key);

 

                  result.put(key, value);

              }

           } else {

              name = name.replace('.', '/');

 

              if (!name.endsWith(SUFFIX))

                  name = name.concat(SUFFIX);

 

              // Returns null on lookup failures:

              in = loader.getResourceAsStream(name);

              if (in != null) {

                  result = new Properties();

                  result.load(in); // Can throw IOException

              }

           }

       } catch (Exception e) {

           result = null;

       } finally {

           if (in != null)

              try {

                  in.close();

              } catch (Throwable ignore) {

              }

       }

 

       if (THROW_ON_LOAD_FAILURE && (result == null)) {

           throw new IllegalArgumentException("could not load ["

                  + name

                  + "]"

                  + " as "

                  + (LOAD_AS_RESOURCE_BUNDLE ? "a resource bundle"

                         : "a classloader resource"));

       }

 

       return result;

    }

 

    /**

     * A convenience overload of {@link #loadProperties(String, ClassLoader)}

     * that uses the current thread's context classloader.

     */

    public static Properties loadProperties(final String name) {

       return loadProperties(name, Thread.currentThread()

              .getContextClassLoader());

    }

 

    private static final boolean THROW_ON_LOAD_FAILURE = true;

    private static final boolean LOAD_AS_RESOURCE_BUNDLE = false;

    private static final String SUFFIX = ".properties";

} // End of class

The Javadoc comment for the loadProperties() method shows that the method's input requirements are quite relaxed: it accepts a resource name formatted according to any of the native method's schemes (except for package-relative names possible with Class.getResourceAsStream()) and normalizes it internally to do the right thing.

loadProperties()方法的Javadoc注释表明方法的输入需求是很随意的:它接受一个根据任何原生方法模式格式化的资源名称(除了使用Class.getResourceAsStream()相对于包的名称),内部将资源名称标准化来做正确的事情。

The shorter loadProperties() convenience method decides which classloader to use for loading the resource. The solution shown is reasonable but not perfect; you might consider using techniques described in "Find a Way Out of the ClassLoader Maze" instead.

更简捷的loadProperties()方法决定了哪个classloader用于加载资源。已列出的解决方案是有道理的但不是完美的;你可考虑使用"Find a Way Out of the ClassLoader Maze"中描述的技术替代它。

Note that two conditional compilation constants control loadProperties() behavior, and you can tune them to suit your tastes:

  • THROW_ON_LOAD_FAILURE selects whether loadProperties() throws an exception or merely returns null when it can't find the resource
  • LOAD_AS_RESOURCE_BUNDLE selects whether the resource is searched as a resource bundle or as a generic classpath resource

说明一下两个条件编译常量控制loadProperties()的行为,你可以调整它们以适应你的风格:

  • THROW_ON_LOAD_FAILUREloadProperties()不能找到资源时,选择 抛出异常还是仅仅返回runll
  • LOAD_AS_RESOURCE_BUNDLE 选择资源是作为一个资源包被搜索还是作为一个普通类路径资源被搜索。

Setting LOAD_AS_RESOURCE_BUNDLE to true isn't advantageous unless you want to benefit from localization support built into java.util.ResourceBundle. Also, Java internally caches resource bundles, so you can avoid repeated disk file reads for the same resource name.

除非你想从java.util.ResourceBundle中的本地化支持获得好处,将LOAD_AS_RESOURCE_BUNDLE设置为true没有什么优势。还有,Java内部缓存了资源包,所以你可以避免对于同一个资源名的磁盘文件重复读。

More things to come

I intentionally omitted an interesting classpath resource loading method, ClassLoader.getResources(). Despite its infrequent use, ClassLoader.getResources() allows for some very intriguing options in designing highly customizable and easily configurable applications.

我有意忽略了一个有意思的类路径资源加载方法,ClassLoader.getResources()。尽管它用的不多,但是在设计高度可自定义的和易配置的应用程序中ClassLoader.getResources()考虑到一些很有趣的选项。

I didn't discuss ClassLoader.getResources() in this article because it's worthy of a dedicated article. As it happens, this method goes hand in hand with the remaining way to acquire resources: java.net.URLs. You can use these as even more general-purpose resource descriptors than classpath resource name strings. Look for more details in the next Java Q&A installment.

在这篇文章中我不讨论ClassLoader.getResources(),因为这值得用一篇专门的文章来讨论它。碰巧,这个方法要与获取资源的另一种方式(java.net.URLs)一起使用。你可以使用它们作为比类路径资源名称串更通用目的资源描述符。在下一期Java Q&A中找到更多内容。

About the author

关于作者

Vladimir Roubtsov has programmed in a variety of languages for more than 13 years, including Java since 1995. Currently, he develops enterprise software as a senior engineer for Trilogy in Austin, Texas.

posted on 2010-06-29 14:21 jeffma 阅读(584) 评论(2)  编辑  收藏

评论:
# re: 巧妙地加载属性(翻译) 2010-06-29 14:22 | jeffma
第一次发布,欢迎指正。  回复  更多评论
  
# re: 巧妙地加载属性(翻译) 2012-11-21 22:35 | 莫老酒
翻译的很不错啊,我引用一下。  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: