隨筆-7  評(píng)論-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

          巧妙地加載屬性

          為獨(dú)立于磁盤位置的代碼天堂而努力

          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.

          答:當(dāng)你考慮如何在Java中加載一個(gè)外部資源時(shí),多種選擇立即浮現(xiàn)在腦海:文件,類路徑資源和URL。雖然最終它們都能完成工作,但是經(jīng)驗(yàn)表明類路徑資源和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.

          通常,一個(gè)配置文件可以有任意復(fù)雜的結(jié)構(gòu)(例如,XML schema定義文件)。但是對(duì)于簡(jiǎn)單的結(jié)構(gòu),下面我假設(shè)我們正在處理一個(gè)簡(jiǎn)單的名-值對(duì)列表(常見的.properties格式)。然而,只要討論的資源是從一個(gè)InputStream構(gòu)造時(shí),你沒有理由不在以下列出的其它情況下應(yīng)用那個(gè)想法(選擇類路徑資源和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.

          使用舊文件(通過(guò)FileInputStream, FileReader, RandomAccessFile)相當(dāng)簡(jiǎn)單,當(dāng)然考慮到?jīng)]有Java背景的人這也是明顯的方式。但是就Java應(yīng)用部署的簡(jiǎn)易性而言文件是最壞的選擇。在你的代碼中使用絕對(duì)文件名不是編寫可移植、獨(dú)立于磁盤位置的代碼的方式。使用相對(duì)文件名好像是一個(gè)較好的替代方法,但是記住它們是相對(duì)于JVM的當(dāng)前路徑被解析的。目錄設(shè)置依賴于JVM加載進(jìn)程的細(xì)節(jié),像啟動(dòng)shell腳本或其他加載JVM的方式使得目錄設(shè)置變得混亂。將決定如何設(shè)置的配置負(fù)擔(dān)加給最終用戶是不公平的(在某些情況下,對(duì)用戶能力的信任是不合理的)。而且在其它環(huán)境(像企業(yè)JavaBeans(EJB)/Web應(yīng)用服務(wù)器)中,在一開始你和用戶對(duì)JVM的當(dāng)前路徑都沒有太多的控制。

          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的東西,準(zhǔn)備上手吧。考慮一下EJB jar,打包到.war文件的Web應(yīng)用程序和其他類似方便的部署策略。除非你絕對(duì)要使用文件,否則還是對(duì)文件說(shuō)不。

          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).

          我們不再對(duì)File進(jìn)行抨擊,讓我們討論一種更好的選擇:通過(guò)類加載器加載資源。因?yàn)轭惣虞d器在資源名稱和在磁盤上(或其他地方)的實(shí)際位置之間主要扮演了一個(gè)抽象層角色,這是相當(dāng)好的。

          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.

          比如說(shuō)你需要加載一個(gè)類路徑資源,它對(duì)應(yīng)some/pkg/resource.properties文件。我使用類路徑資源意味著這個(gè)資源要打包到應(yīng)用程序的某個(gè)jar中或者在應(yīng)用程序啟動(dòng)之前加到類路徑中。你可以在應(yīng)用程序每次啟動(dòng)時(shí)通過(guò)-classpath這個(gè)JVM參數(shù)添加類路徑,或者干脆把那個(gè)文件放到<jrehome>\classes目錄中。關(guān)鍵點(diǎn)是部署一個(gè)類路徑資源類似于部署一個(gè)已編譯的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");

          你可以通過(guò)多種方式在你的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這個(gè)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.

          注意這些方法在參數(shù)格式上的細(xì)微差別。所有getResourceAsStream()方法都是使用斜杠(/)來(lái)分隔包名段,而且資源文件名包括文件擴(kuò)展名。比較一下,使用資源包(resource bundle)時(shí)資源名看上去更像Java標(biāo)識(shí)符,它是以點(diǎn)(.)分隔包名段(這里的.properties擴(kuò)展名被隱含了)。當(dāng)然,一個(gè)資源包(resource bundle)不必非要是.properties文件:例如,它可以是一個(gè)類。

          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.

          對(duì)于稍復(fù)雜的情況,java.lang.Class getResourceAsStream()實(shí)例方法可以執(zhí)行相對(duì)于包的資源搜索(這是相當(dāng)方便的,參見 Got Resources?)。為了區(qū)別相對(duì)和絕對(duì)資源名稱Class.getResourceAsStream()對(duì)絕對(duì)名稱使用前導(dǎo)斜杠(/)。一般來(lái)說(shuō),如果你沒有計(jì)劃在代碼中使用相對(duì)于包的資源命名沒有必要使用這種方法。

          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()在行為上的細(xì)小區(qū)別。下表總結(jié)了一些顯著點(diǎn)來(lái)幫助你記憶:


           


           

          Behavioral differences

          Method

          (方法)

          Parameter format

          (參數(shù)格式)

          Lookup failure behavior

          (查詢失敗行為)

          Usage example

          (用法示例)

          ClassLoader.
          getResourceAsStream()

          "/"-separated names; no leading "/" (all names are absolute)

          "/"-分隔名稱; 沒有前導(dǎo)"/" (所有名稱都是絕對(duì)名稱)

          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

          "/"-分隔名稱; 前導(dǎo) "/" 表示絕對(duì)名稱;所有其他名稱是相對(duì)于類所在包的。

          Silent (returns null)

          this.getClass()
          .getResourceAsStream
          ("resource.properties")

          ResourceBundle.
          getBundle()

          "."-separated names; all names are absolute; .properties suffix is implied

          "."-分隔名稱;所有名稱都是絕對(duì)名稱; .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.

          你可能已經(jīng)注意到前面提到的一些方法僅是折衷辦法:它們返回的是InputStream,沒有類似一個(gè)名-值對(duì)的列表的東西。幸運(yùn)地是,將數(shù)據(jù)加載到這樣一個(gè)列表(可以是java.util.Properties的一個(gè)實(shí)例)是相當(dāng)容易的。因?yàn)槟銓l(fā)現(xiàn)你在反復(fù)做這個(gè)加載工作,所以為這個(gè)目的創(chuàng)建一組幫助方法是有意義的。

          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內(nèi)建方法之間的細(xì)小行為差別可能是一個(gè)令人討厭的事情,尤其是有些資源名稱是硬編碼的但是你現(xiàn)在想切換到另一個(gè)加載方法。抽象出一些東西像不管是斜杠(/)還是點(diǎn)(.)作為分隔符等等就變得有意義了。不再羅嗦,這是我的PropertyLoader API你可能發(fā)現(xiàn)它是有用的。

          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注釋表明方法的輸入需求是很隨意的:它接受一個(gè)根據(jù)任何原生方法模式格式化的資源名稱(除了使用Class.getResourceAsStream()相對(duì)于包的名稱),內(nèi)部將資源名稱標(biāo)準(zhǔn)化來(lái)做正確的事情。

          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.

          更簡(jiǎn)捷的loadProperties()方法決定了哪個(gè)classloader用于加載資源。已列出的解決方案是有道理的但不是完美的;你可考慮使用"Find a Way Out of the ClassLoader Maze"中描述的技術(shù)替代它。

          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

          說(shuō)明一下兩個(gè)條件編譯常量控制loadProperties()的行為,你可以調(diào)整它們以適應(yīng)你的風(fēng)格:

          • THROW_ON_LOAD_FAILURE當(dāng)loadProperties()不能找到資源時(shí),選擇 拋出異常還是僅僅返回runll
          • LOAD_AS_RESOURCE_BUNDLE 選擇資源是作為一個(gè)資源包被搜索還是作為一個(gè)普通類路徑資源被搜索。

          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設(shè)置為true沒有什么優(yōu)勢(shì)。還有,Java內(nèi)部緩存了資源包,所以你可以避免對(duì)于同一個(gè)資源名的磁盤文件重復(fù)讀。

          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.

          我有意忽略了一個(gè)有意思的類路徑資源加載方法,ClassLoader.getResources()。盡管它用的不多,但是在設(shè)計(jì)高度可自定義的和易配置的應(yīng)用程序中ClassLoader.getResources()考慮到一些很有趣的選項(xiàng)。

          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(),因?yàn)檫@值得用一篇專門的文章來(lái)討論它。碰巧,這個(gè)方法要與獲取資源的另一種方式(java.net.URLs)一起使用。你可以使用它們作為比類路徑資源名稱串更通用目的資源描述符。在下一期Java Q&A中找到更多內(nèi)容。

          About the author

          關(guān)于作者

          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 閱讀(602) 評(píng)論(2)  編輯  收藏

          評(píng)論:
          # re: 巧妙地加載屬性(翻譯) 2010-06-29 14:22 | jeffma
          第一次發(fā)布,歡迎指正。  回復(fù)  更多評(píng)論
            
          # re: 巧妙地加載屬性(翻譯) 2012-11-21 22:35 | 莫老酒
          翻譯的很不錯(cuò)啊,我引用一下。  回復(fù)  更多評(píng)論
            

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 麻栗坡县| 邹平县| 土默特右旗| 万宁市| 杭锦后旗| 巴中市| 渭南市| 南充市| 平塘县| 皋兰县| 河北省| 鄂尔多斯市| 永吉县| 鄯善县| 苍溪县| 石狮市| 健康| 云龙县| 如东县| 华蓥市| 平定县| 大竹县| 玉田县| 抚州市| 泽库县| 西盟| 阳谷县| 县级市| 商都县| 宜兴市| 来宾市| 定安县| 禄丰县| 汉阴县| 奉节县| 怀远县| 长子县| 油尖旺区| 龙里县| 柳江县| 巢湖市|