Smartly load your properties

Strive for disk location-independent code nirvana

By VladimirRoubtsov, JavaWorld.com, 08/08/03

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

Q: Whatis the best strategy for loading property and configuration files in 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 thatclasspath resources and URLs are by far the most flexible and user-friendlyoptions.

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

Evil java.io.File

Using good old files (via FileInputStream, FileReader,and RandomAccessFile) is simple enough and certainly the obviousroute to consider for anyone without a Java background. But it is the worstoption in terms of ease of Java application deployment. Using absolutefilenames in your code is not the way to write portable and diskposition-independent code. Using relative filenames seems like a betteralternative, but remember that they are resolved relative to the JVM's currentdirectory. This directory setting depends on the details of the JVM's launchprocess, which can be obfuscated by startup shell scripts, etc. Determining thesetting 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 applicationserver), neither you nor the user has much control over the JVM's currentdirectory in the first place.

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

Classpath resources

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

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

You can get at some/pkg/resource.propertiesprogrammatically 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");

 

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

  Class.getResourceAsStream ("resource.properties");

 

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

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

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

Behavioraldifferences

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

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

 

From data streams to java.util.Properties

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

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

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

The shorter loadProperties() conveniencemethod decides which classloader to use for loading the resource. The solutionshown is reasonable but not perfect; you might consider using techniquesdescribed in "Finda Way Out of the ClassLoader Maze" instead.

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

 

Setting LOAD_AS_RESOURCE_BUNDLE to trueisn't advantageous unless you want to benefit from localization support builtinto java.util.ResourceBundle. Also, Java internally cachesresource bundles, so you can avoid repeated disk file reads for the sameresource name.

More things to come

I intentionally omitted an interesting classpath resourceloading method, ClassLoader.getResources(). Despite its infrequentuse, ClassLoader.getResources() allows for some very intriguingoptions in designing highly customizable and easily configurable applications.

I didn't discuss ClassLoader.getResources() inthis article because it's worthy of a dedicated article. As it happens, thismethod 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 thanclasspath resource name strings. Look for more details in the next Java Q&Ainstallment.

About the author

Vladimir Roubtsov has programmed in avariety 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.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值