Kolumne EnterpriseTales

Java Enterprise: Custom Resource Loading mit Java 8

Arne Limburg, Lars Röwekamp

©shutterstock.com/Demeshko Alexandr

Im dritten Teil unserer kleinen Serie zu Java 8 und Enterprise beschäftigen wir uns mit dem Thema Internationalisierung und Resource Loading. Hier gibt es in Java 8 eine interessante Erweiterung, die natürlich auch in Java EE verwendet werden kann. Die letzten beiden Teile verpasst? Hier geht es zu Teil 1 und zu Teil 2.

Der Standardmechanismus in Java zum Laden von Ressourcen ist das ResourceBundle. Dieses kümmert sich auch um die Internationalisierung. Beim Laden eines ResourceBundles wird auf Basis eines definierten Algorithmus das korrekte Bundle für Sprache und Land ermittelt und geladen.

ResourceBundle in JSF

Um ein ResourceBundle in JSF zu nutzen, muss es entweder in der faces-config.xml eingetragen werden (Listing 1) oder als lokales Bundle über das Tag f:loadBundle in die XHTML-Seite eingebunden werden. An beiden Stellen muss jeweils ein Variablenname angegeben werden, über den dann innerhalb eines Expression-Language-Ausdrucks auf dieses Bundle zugegriffen werden kann. In unserem Beispiel wäre das über msg[‚myKey‘] möglich. Innerhalb von JSF kann zusätzlich über FacesApplication.getResourceBundle programmatisch auf das Bundle zugegriffen werden.

<faces-config>
  <application>
    <resource-bundle>
      <base-name>com.sample.messages</base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>

Ladeprozess anpassen

Neben dem Namen (in Listing 1 ist das messages) kann ein ResourceBundle noch Locale-spezifisch ausgeprägt sein. Der Bezeichner für das Locale wird dabei durch einen Unterstrich vom Namen getrennt (z. B. messages_de). Das Locale selbst kann auch wieder aus mehreren Teilen bestehen (siehe auch hier), die auch durch Unterstriche getrennt sind (z. B. messages_de_DE_northern). Das erste Suffix steht dabei für die Sprache (de), das zweite für das Land (DE) und alle weiteren stellen beliebige Varianten dar, die der Wichtigkeit nach sortiert sein sollten.

Der Standardalgorithmus zum Ermitteln eines ResourceBundles schaut zunächst nach, ob es eine Properties-Datei mit dem konfigurierten Namen und dem aktuellen Locale im Classpath gibt. In Listing 1 und dem Locale de_DE wäre das eine Datei namens messages_de_DE.properties im Ordner com/sample. Ist diese nicht vorhanden, wird überprüft, ob es eine gleichnamige Klasse gibt, die von ResourceBundle ableitet. Das wäre im Beispiel eine Klasse Messages_de_DE.class im Package com.sample. Gibt es beides nicht, wird versucht, eine sprachspezifische Ressource zu finden (messages_DE.properties bzw. Messages_DE.class). Den letzten Versuch unternimmt der Algorithmus dann komplett ohne Suffix (messages.properties bzw. Messages.class).

Bis Java 6 war es nicht möglich, den Mechanismus des Ladens von Ressourcen anzupassen. Wollte man das tun, um zum Beispiel das Laden von Ressourcen aus der Datenbank umzusetzen, blieb einem nichts anderes übrig, als pro Ressource besagte Klasse zu erstellen (Messages_de_DE.java) und den Mechanismus dort zu implementieren. Das Unterstützen mehrerer Locales war dann recht mühsam, auch wenn man durch geschickte Ableitungshierarchien den Implementierungsaufwand minimieren konnte. Das dynamische Hinzufügen von Locales war gar nicht möglich.

Daher wurde in Java 6 die Klasse ResourceBundle.Control eingeführt. Sie kann erweitert und der Ladeprozess dadurch nahezu beliebig angepasst werden. Zusätzlich können eigene Caching-Strategien eingebaut werden. Die Umsetzung der nicht seltenen Anforderung, Ressourcen aus der Datenbank zu laden, ist so leicht möglich. Um einen solchen angepassten Ladeprozess zu verwenden, muss das neue ResourceBundle.Control einfach dem Aufruf von ResourceBundle.getBundle übergeben werden.

Leider gilt das nur für die Fälle, in denen man sich das ResourceBundle programmatisch holt. In den Fällen, in denen das Framework das Laden der Ressourcen übernimmt (wie z. B. in JSF), ist das Ganze nicht so einfach. In Java 6 wurde nämlich versäumt, in den Standardmechanismus zum Laden von Ressourcen eine Möglichkeit einzubauen, ein eigenes ResourceBundle.Control zu übergeben. Alle Frameworks, die das Laden von ResourceBundle.getBundle ohne ein Control aufrufen, lassen sich also schwer anpassen. Zu diesen Frameworks gehört auch JSF. Möchte man also über JSF auf Ressourcen zugreifen, muss man leider auf das ResourceBundle.Control verzichten und auf den oben beschriebenen Mechanismus zurückgreifen, in dem man die Klasse ResourceBundle selbst implementiert. Das ist nicht schön und führt bei mehreren zu unterstützenden Locales zu viel ähnlichem Code. Außerdem lassen sich keine Locales dynamisch hinzufügen.

Java 8 to the rescue

Glücklicherweise wurde hier in Java 8 Abhilfe geschaffen. Denn mit dieser Version wurde das Serviceproviderinterface ResourceBundleControlProvider eingeführt. Der Standard-ResourceBundle-Ladeprozess verwendet nun den ServiceLoader, um eine Instanz eines ResourceBundleControlProviders zu erhalten, von dem dann ein ResourceBundle.Control-Objekt geholt wird. Um also den Standardprozess dazu zu bringen, ein selbst implementiertes ResourceBundle.Control-Objekt zu verwenden, muss eine eigene Implementierung des ResourceBundleControlProvider-Interface erstellt werden, die einen Default-Konstruktor hat. Zusätzlich muss eine Datei namens java.util.spi.ResourceBundleControlProvider im Ordner META-INF/services angelegt werden, die den vollqualifizierten Klassennamen der eigenen ResourceBundleControlProvider-Implementierung enthält. Diese Datei wird dann vom ServiceLoader automatisch gefunden und der ResourceBundleControlProvider über den Default-Konstruktor instanziiert. Dieser wird dann verwendet, um ein ResourceBundle.Control zu erhalten, das zum Laden von Ressourcen herangezogen wird, falls keines explizit angegeben ist, also auch von JSF.

Fazit

Bereits seit Java 6 ist es möglich, mit dem Interface ResourceBundle.Control den Prozess zum Laden von Ressourcen komplett selbst zu gestalten und die Ressourcen so z. B. aus komplett anderen Datenquellen (wie z. B. der Datenbank) zu laden. Außerdem lassen sich darüber eigene Caching-Strategien für Ressourcen implementieren.

Leider war es bisher nicht so ohne Weiteres möglich, diesen Mechanismus in Java EE, insbesondere in JSF einzusetzen, wo das Laden der Ressourcen komplett vom Framework übernommen wird.

Mit Java 8 wurde nun das Serviceproviderinterface ResourceBundleProvider eingeführt, über das die ResourceBundle.Control-Objekte erzeugt werden. Der Default-Mechanismus zum Laden von Ressourcen wurde so angepasst, dass ein ResourceBundleProvider – sofern vorhanden – genutzt wird, sodass nun auch in Frameworks wie JSF, die den Default-Mechanismus verwenden, eigene Ladeverfahren verwendet werden können. Das Laden und Caching von Ressourcen kann nun komplett den eigenen Wünschen angepasst werden.

Aufmacherbild: Grains of sugar in shape of eight on wooden background von Shutterstock / Urheberrecht: Demeshko Alexandr

Geschrieben von
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Lars Röwekamp
Lars Röwekamp
Lars Röwekamp ist Gründer des IT-Beratungs- und Entwicklungsunternehmens open knowledge GmbH, beschäftigt sich im Rahmen seiner Tätigkeit als „CIO New Technologies“ mit der eingehenden Analyse und Bewertung neuer Software- und Technologietrends. Ein besonderer Schwerpunkt seiner Arbeit liegt derzeit in den Bereichen Enterprise und Mobile Computing, wobei neben Design- und Architekturfragen insbesondere die Real-Life-Aspekte im Fokus seiner Betrachtung stehen. Lars Röwekamp, Autor mehrerer Fachartikel und -bücher, beschäftigt sich seit der Geburtsstunde von Java mit dieser Programmiersprache, wobei er einen Großteil seiner praktischen Erfahrungen im Rahmen großer internationaler Projekte sammeln konnte.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Java Enterprise: Custom Resource Loading mit Java 8"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Rainer Hochreiter
Gast

Hallo!
Ich habe den Ansatz mit Custom Resource Loading wie im Beitrag beschrieben in meine JSF Applikation eingebunden, aber leider wird mein DatabaseResourceBundleControlProvider nicht geladen / aufgerufen – was auch immer!

Das Problem schein in der faces-config.xml zu liegen, denn mir ist nicht klar, was ich da jetzt eintragen soll?!

Derzeit habe ich folgendes:

my.webapp.demo.i18n.DatabaseResourceBundle
bundle

Das führt zur Exception „MissingResourceException: Can’t find bundle for base name my.webapp.demo.i18n.DatabaseResourceBundle, locale de“

Bin für jeden Hinweis dankbar!

LG, Rainer Hochreiter