Start - Publikationen - Wissen - TOGAF - Impressum -

Das ResourceBundle API


Das ResourceBundle API ist für den Zugriff auf Textressourcen das Mittel der Wahl. Der Aufruf von

ResourceBundle.getBundle([name])
sucht nach Ressourcen unter Anwendung folgender Strategie:
  • Wenn nicht explizit vorgegeben liefert Locale.getDefault() language, country und version der Laufzeitumgebung, was clientseitig ein plausibles Standardverhalten ist.
  • Es wird nun eine Klasse [name]_[language]_[country]_[version] gesucht, die von ResourceBundle erbt.
  • Wenn diese nicht gefunden werden kann wird nach einer Textressource [name]_[language]_[country]_[version].properties im Classpath gesucht.
  • Solange nichts gefunden wird, wiederholt sich diese Suche unter Weglassen der Spezialisierungen, bis das Root-Bundle erreicht wird. [name] ist dabei ein vollqualifizierter Klassenname.
Einmal gefundene Resource Bundle werden gecached. Wenn in einem gefundenen ResourceBundle der Wert zu einem Schlüssel nicht gefunden wird, nutzt die ResourceBundle Implementierung den beschriebenen Algorithmus, um einen Wert zum selben Schlüssel in weniger spezialisierten Bundles zu finden. Im Root-Bundle (keine Angaben von [language]_[country]_[version]) legt man also den Standard ab, spezialisierte Bundles können dann auch unvollständig sein und für alle Spezialisierungen gültige Ressourcen (zum Beispiel Pfade zu Icons) müssen nur einmal definiert werden.

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, damit Änderungen im eigenen Code ankommen. Man sollte deshalb das Ablegen von ResourceBundles in Klassenvariablen unterlassen und immer ResourceBundle.getBundle nutzen. Die ResourceBundle-Implementierung ist für die Bereitstellung hoch dynamischer Textressourcen allerdings nicht geeignet, mit anderen Worten clearCache() wird nur selten gerufen.

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
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 betreffenden 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 Lokalisierungsinformationen 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, hier mit einer hypothetischen getUsersPrefLocale-Funktion
// 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).
  • MessageFormat.format ist nicht thread-safe.
  • 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
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-2018 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved