Separating localized resources from the source code where they are used is a
well-known and approved design. Java's ResourceBundle
class from the java.util
package provides a straightforward way to handle
this. However, there is a tight coupling between a resource bundle and a
localized object, which you may not want to hard-code into your application.
Avoiding such hard-coding makes it easier to modify the storage format and
location of resources. You can achieve this by creating a custom ResourceBundle
class. This article shows you how.
Customizing the standard ResourceBundle class allows you to abstract localized resources from their storage format and location and eases creation of new types of ResourceBundle objects. Normally, you would use ResourceBundle in the following way:
String localizedMsg = ResourceBundle.getBundle("resources.test1").getString(msg)
This approach has two drawbacks. First, you have to explicitly specify
(hard-code into your application) a resource bundle name
(resources.test1). This becomes complicated when the number of classes
using the resource bundle grows. Second, the standard ResourceBundle class
provides only two data storage alternatives: in a file
(PropertyResourceBundle) or a class (ListResourceBundle). If you
want to retrieve resources from elsewhere, such as a database or FTP server, you
have to create your own ResourceBundle class. You can elegantly avoid both of
these problems by using customized ResourceBundle classes from the very
beginning. Let's look at the process of creating a customized ResourceBundle
Creating your own ResourceBundle
First, you need to create a class that keeps all the resource bundles your
application will use. Listing A shows such a class.
The important part of this listing is the getBundle() method, which
returns a CommonResourceBundle. The returned object knows where and how to
retrieve a particular resource. Now you can get localized data like this:
String localizedMsg = CommonResourceBundle.getBundle().getString(msg)
Note that you do not specify a resource bundle name at all. The trick is to add
resource bundles to CommonResourceBundle prior to using it. You can do this
either by means of the addResourceBundle(...) method or with a
As CommonResourceBundle extends the standard ResourceBundle, you can use the
regular technique to get a localized object:
To implement your custom ResourceBundle, you must override the standard
ResourceBundle. You do this by overriding two methods: getKeys() and
handleGetObject(String s). The latter method returns an object for the
given key after searching through all available resource bundles; it returns
null if the key is not found. The available resource bundles are the ones that
were added to CommonResourceBundle.
When you have created the CommonResourceBundle that keeps all the bundles you
want to use, you can create customized ResourceBundle classes to access
localized resources at particular locations and in specified formats. As an
example, let's create the DBResourceBundle class, which will get localized
objects from a database. Listing B provides a sample.
The buildProperties() method fetches resources from a database and caches
them locally into Properties from java.util package. This method is
called only once during a DBResourceBundle object instantiation. The
handleGetObject() method returns a resource object found in Properties;
if it doesn't find the object, it returns null.
Configure your resource bundle
You must configure a CommonResourceBundle before you can use it. As we mentioned
earlier, you can do this by calling the addResourceBundle(...) method or
setting up a configuration file. The sample below adds two resource bundles to
the CommonResourceBundle: the standard and customized ResourceBundles:
CommonResourceBundle.addResourceBundle(ResourceBundle.getBundle("resources.test1")); CommonResourceBundle.addResourceBundle(new DbResourceBundle("jdbc:oracle:thin:scott/tiger@mypc:1521:ORCL:table:ScottProp"));
You can achieve the same result by specifying configuration parameters, that is, adding them to system properties (calling System.setProperty(...)) or using an XML configuration file. Here are examples of FileResourceBundle and DBResourceBundle configuration parameters: file.resource.bundle.name=resources.test1,resources.test2
The first parameter contains a comma-separated list of resource files or class
names. For each name, a ResourceBundle is created and added to the
CommonResourceBundle. The second contains a list of database URLs and table
names (ScottProp). A table has two columns (a key and a value) and
contains localized resources. For each URL, a Properties object is created and
loaded with data from a database.
Now you have everything you need to create your own customized ResourceBundle.
You can find more information on the standard ResourceBundle
specification and details of its simple usage at the Sun Web site.
The complete code for this article is available in Listing C.