Start - Projekte - Wissen - Impressum -
Prozesse & Systeme - Optimierung von Entwicklungsprozessen

ResourceBundle


Die ResourceBundle API ist für den Zugriff auf Textressourcen das Mittel der Wahl:

ResourceBundle.getBundle([name])
sucht nach Ressourcen unter Anwendung folgender Startegie:
  1. Wenn nicht explizit vorgegeben liefert Locale.getDefault() language, country und version der Laufzeit.
  2. Es wird eine Klasse [name]_[language]_[country]_[version] gesucht, die von ResourceBundle erbt, wenn diese nicht gefunden werden kann wird nach einer Datei [name]_[language]_[country]_[version].properties gesucht. Solange nichts gefunden wird, wiederholt sich Schritt unter Weglassen der Spezialisierungen, bis das Root-Bundle erreicht wird. [name] ist dabei ein vollqualifizierter Klassenname, bei der Suche nach Property Dateien wird dieser vollqualifizierter Name als Verzeichnisstruktur aufgelöst.

Einmal gefundene Resource Bundle werden gecached. Wenn in einem gefundenen Resource Bundle der Wert zu einem Schlüssel nicht gefunden wird, nutzt die Resource Bundle API den gleichen Algorithmus, um einen Wert zum selben Schlüssel in weniger spezialisierten Bundles zu finden.

Die ResourceBundle API bietet insgesamt drei Ansatzpunkte zur Nutzung:

  1. Ableiten von ListResourceBundle und Überschreiben von getContents. Die Ressourcen liegen dann als Java-Code in Object-Arrays vor.
    public class MyResourceBundle extends ListResourceBundle {
      private static final Object[][] CONTENTS = new Object[][] {
       {"city", "London"},
       {"town", "Dover"}
      };
      @Override
      protected Object[][] getContents() {
        return CONTENTS;
      }
    }
    public class MyResourceBundle_de extends ListResourceBundle {
      private static final Object[][] CONTENTS = new Object[][] {
       {"city", "Berlin"},
       {"town", "Hannover"}
      };
      @Override
      protected Object[][] getContents() {
        return CONTENTS;
      }
    }
    :
    
  2. Ableiten von ResourceBundle und Überschreiben von getKeys und handleGetObject. Die Ressourcen können in beliebiger Form adressiert werden (zum Beispiel als Datenbanktabelle mit internationalisiertem Encoding).
    /**
     * Implementierung eines ResourceBundles unter Nutzung einer externen Resource, hier: Datenbanktabelle
     * (Pseudocode)
     *
     * Tabellenstruktur:
     * key – der Schlüssel (not null)
     * root – Standardwert (not null)
     * de – Spezialisierung deutsch
     * it - Spezialisierung italiano
     * : 
     */
    public abstract class DBResourceBundle extends ResourceBundle {
      // Injektion der Resource Database etc.
      private Properties properties;
      public DBResourceBundle() {
        buildProperties();
      }
      protected abstract String getColumnName();
      public Enumeration getKeys() {
        return properties.propertyNames();
      }
      protected Object handleGetObject(String key) {
        return properties.getProperty(key);
      }
     /**
      * Laden der Properties aus einer Datenbanktabelle.
      */
      private void buildProperties() {
        // 1. lese alle Einträge der Ressourcentabelle wenn die Spalte getColumnName() nicht leer ist
        // 2. Für alle Einträge: properties.setProperty(schlüssel, wert);
      }
    }
    // Ausimplementierungen
    public class MyResourceBundle extends DBResourceBundle {
       protected String getColumnName() {
          return "root";
       }
    }
    public class MyResourceBundle_de extends DBResourceBundle {
       protected String getColumnName() {
          return "de";
       }
    }
    public class MyResourceBundle_it extends DBResourceBundle {
       protected String getColumnName() {
          return "it";
       }
    }
    :
    
  3. Bereitstellung einer Property Datei im Classpath der Anwendung. Eine eigene Implementierung ist nicht notwendig, ResourceBundle.getBundle([name]_..) lädt automatisch Textressourcen aus Property Dateien wenn keine Klasse passenden Namens gefunden werden kann. Der Bytestrom der Property Dateien wird grundsätzlich ISO 8859-1 interpretiert, internationale Zeichen sind als Unicode Sequenzen zu umschreiben.

Diese Varianten dürfen dabei für die Definition von ResourceBundle-Hierarchien beliebig gemischt werden! Eigene Ausprägungen von Locale können auch verwendet werden, das Default Locale wird gegebenenfalls über die JVM Startparameter -Duser.language und -Duser.region vorgegeben (statt –Duser.region in der Form country_variant kann auch –Duser.country und –Duser.variant explizit festgelegt werden).

Dynamische ResourceBundle


Sollen Textressourcen zur Laufzeit geändert werden, muss ResourceBundle.clearCache() gerufen werden. Alle ResourceBundle Instanzen werden verworfen und beim nächsten Zugriff durch ResourceBundle.getBundle neu geladen. Diese Dynamik setzt voraus, dass alle Zugriffe auf Ressourcen über ResourceBundle.getBundle initiiert werden. Man sollte deshalb das Ablegen von ResourceBundles in Klassenvariablen unterlassen und immer ResourceBundle.getBundle nutzen. Die ResourceBundle API ist für die Bereitstellung hoch dynamischer Textressourcen allerdings nicht geeignet!

Parametrisierung von Textressourcen - die MessageFormat API


Textressourcen mit Parametern sind mittels der MessageFormat API zu realisieren. Auch hier erfolgt die Internationalisierung der Formatierung auf der Basis von Locale.getDefault(). Beim Rendering der Parameter delegiert MessageFormat das Locale an die eingesetzten spezialisierten Formatter.

// eigentlich aus ResourceBundle holen, hier nur skizziert
String text = "Um {1,time} am {1,date} haben Sie einen Betrag von {0,number,integer} Euro überwiesen.";
// MessageFormat Instanzen können wiederverwendet werden, müssen
// aber in nebenläufigen Umgebungen synchronisiert werden
MessageFormat messageFormat = new MessageFormat(text);
:
// Formatierung:
String msg = messageFormat.format(new Object[]{ new Double(66.66), new Date()});

ResourceBundle in Java EE Umgebungen


ResourceBundle cached die Bundlehierarchien per Classloader, auf diese Weise sind die ResourceBundle der verschiedenen Java EE Anwendungsmodule voneinander isoliert. Die Initialisierung der ResourceBundle ist synchronisiert, da ResourceBundle nach der Initialisierung unveränderlich sind muss der Zugriff auf die eigentlichen Ressourcen nicht synchronisiert sein. ResourceBundle können also unter normalen Umständen (ResourceBundle.clearCache wird selten gerufen, kein exotisches Classloading im Container) problemlos in Java EE Umgebungen eingesetzt werden. Zu beachten ist auch: ResourceBundle.clearCache ist im gleichen Classloader-Context zu rufen, für welchen die Initialisierung erfolgen soll. Das erreicht man, indem man den Classloader (wenn er bekannt ist) explizit vorgibt, oder indem man ResourceBundle.clearCache im Context der Anwendung aufruft.

Der Aufruf von Locale.getDefault() bei der Verwendung von ResourceBundle und MessageFormat muss verhindert werden. Das Default-Locale der Serverumgebung sagt nichts über die Lokalisierung des Clients der Anfrage. Man muss sich also zunächst die gewünschte Lokalisierung des Clients beschaffen und gegebenenfalls merken:

// Lesen der bevorzugten Nutzer-Locale aus den Angaben des Browser-Requests..
Locale usersPrefLocale = request.getLocale();
// .. oder alternativ, wenn der Browser nicht die korrekte Lokalisierung besitzen kann aus anderen Quellen
Locale usersPrefLocale = someService.getUsersPrefLocale(userdaten);
// Setzen des Nutzer-Locale an der Session
session.setValue("preferredLocale", usersPrefLocale);
Anschließend werden bei der Verwendung von ResourceBundle und MessageFormat diese Lokalisierungsangaben explizit verwendet:
// Lesen der Nutzer-Locale aus der Session
Locale usersPrefLocale = (Locale) session.getValue("preferredLocale");
// Nutzung lokalisierter Bundles und Formater
ResourceBundle messages = ResourceBundle.getBundle("MyResourceBundle", usersPrefLocale);
MessageFormat messageFormat = new MessageFormat(messages.getString("MyResourceBundle"), usersPrefLocale);
:

Hinweise:

  • In Webcontainern ist außerdem dafür zu sorgen, dass der Ausgabestrom ein contentType erhält, der Vielsprachigkeit unterstützt (empfohlen: UTF-8).
  • Die Lokalisierung bei der Arbeit mit Textressourcen ist wenn möglich an das zugrundeliegende Basisframework zu delegieren:
    • Servlets: manuell, so wie hier beschrieben
    • JSP Basis: manuell, so wie hier beschrieben
    • JSP2.1 + Taglibs: JSTL I18N (Prefix:fmt)
    • Struts: Struts I18N, gegebenenfalls Implementierung von getLocale in der Action
    • Spring: I18N der BeanFactory
  • MessageFormat.format ist nicht thread-safe. Für jeden Thread nutzt man am besten eine eigene Instanz.
  • Eine Internationalisierung im EJB Tier kann mit den genannten Mitteln ebenso erreicht werden. Wie gesagt sind MessageFormat Instanzen nicht thread-safe. Jede Bean Instanz bekommt ihre eigene MessageFormat Instanz.

Referenzen


Blueprint: Designing Enterprise Applications
Java Tutorial on Internationalization

copyright © 2002-2012 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved