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
巧妙地加載屬性
為獨立于磁盤位置的代碼天堂而努力
Q:What is the best strategy for loading property and
configuration files in Java?
問:在Java中加載屬性和配置文件最好的策略是什么?
A:When
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_FAILURE當loadProperties()不能找到資源時,選擇 拋出異常還是僅僅返回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 閱讀(599)
評論(2) 編輯 收藏