Laden Sie Ihre Eigenschaften intelligent

8. August 2003

F: Was ist die beste Strategie zum Laden von Eigenschafts- und Konfigurationsdateien in Java?

A: Im Allgemeinen kann eine Konfigurationsdatei eine beliebig komplexe Struktur haben (z. B. eine XML-Schemadefinitionsdatei). Der Einfachheit halber gehe ich im Folgenden davon aus, dass es sich um eine flache Liste von Name-Wert-Paaren handelt (das bekannte .propertiesFormat). Es gibt jedoch keinen Grund, warum Sie die unten gezeigten Ideen nicht in anderen Situationen anwenden können, solange die betreffende Ressource aus einem aufgebaut ist InputStream.

Evil java.io.File

Mit guten alten Dateien (über FileInputStream, FileReaderundRandomAccessFile) ist einfach genug und sicherlich der naheliegende Weg für jeden ohne Java-Hintergrund. Dies ist jedoch die schlechteste Option im Hinblick auf die einfache Bereitstellung von Java-Anwendungen. Die Verwendung absoluter Dateinamen in Ihrem Code ist nicht die Möglichkeit, tragbaren und von der Festplattenposition unabhängigen Code zu schreiben. Die Verwendung relativer Dateinamen scheint eine bessere Alternative zu sein, aber denken Sie daran, dass sie relativ zum aktuellen Verzeichnis der JVM aufgelöst werden. Diese Verzeichniseinstellung hängt von den Details des Startvorgangs der JVM ab, die durch Start-Shell-Skripte usw. verschleiert werden können. Durch das Festlegen der Einstellung wird der spätere Benutzer in unfairer Weise konfiguriert (und in einigen Fällen auch ungerechtfertigt) die Fähigkeiten des Benutzers). In anderen Kontexten (z. B. einem Enterprise JavaBeans (EJB) / Webanwendungsserver)Weder Sie noch der Benutzer haben überhaupt viel Kontrolle über das aktuelle Verzeichnis der JVM.

Ein ideales Java-Modul fügen Sie dem Klassenpfad hinzu und es kann losgehen. Denken Sie an EJB-Jars, in .warDateien verpackte Webanwendungen und andere ähnlich bequeme Bereitstellungsstrategien. java.io.Fileist der am wenigsten plattformunabhängige Bereich von Java. Wenn Sie sie nicht unbedingt verwenden müssen, sagen Sie einfach Nein zu Dateien.

Klassenpfadressourcen

Nachdem wir auf die oben genannte Diatribe verzichtet haben, sprechen wir über eine bessere Option: das Laden von Ressourcen über Klassenlader. Dies ist viel besser, da Klassenladeprogramme im Wesentlichen als Abstraktionsebene zwischen einem Ressourcennamen und seinem tatsächlichen Speicherort auf der Festplatte (oder anderswo) fungieren.

Angenommen, Sie müssen eine Klassenpfadressource laden, die einer some/pkg/resource.propertiesDatei entspricht. Ich verwende Klassenpfadressourcen , um etwas zu bezeichnen, das in einem der Anwendungsgläser verpackt oder dem Klassenpfad hinzugefügt wurde, bevor die Anwendung gestartet wird. Sie können den Klassenpfad -classpathjedes Mal über die JVM-Option hinzufügen, wenn die Anwendung gestartet wird, oder indem Sie die Datei \classesein für alle Mal im Verzeichnis ablegen. Der entscheidende Punkt ist, dass das Bereitstellen einer Klassenpfadressource dem Bereitstellen einer kompilierten Java-Klasse ähnelt , und darin liegt der Komfort.

Sie können some/pkg/resource.propertiesprogrammgesteuert von Ihrem Java-Code aus auf verschiedene Arten zugreifen. Erster Versuch:

ClassLoader.getResourceAsStream ("some / pkg / resource.properties"); Class.getResourceAsStream ("/some/pkg/resource.properties"); ResourceBundle.getBundle ("some.pkg.resource");

Wenn sich der Code in einer Klasse innerhalb eines some.pkgJava-Pakets befindet, funktioniert außerdem Folgendes:

 Class.getResourceAsStream ("resource.properties"); 

Beachten Sie die geringfügigen Unterschiede in der Parameterformatierung für diese Methoden. Alle getResourceAsStream()Methoden verwenden Schrägstriche, um Paketnamensegmente zu trennen, und der Ressourcenname enthält die Dateierweiterung. Vergleichen Sie dies mit Ressourcenpaketen, bei denen der Ressourcenname eher wie ein Java-Bezeichner aussieht, wobei Punkte Paketnamensegmente trennen (die .propertiesErweiterung wird hier impliziert). Das liegt natürlich daran, dass ein Ressourcenpaket nicht durch eine .propertiesDatei gesichert werden muss : Es kann beispielsweise eine Klasse sein.

Um das Bild leicht zu erschweren, java.lang.Classist getResourceAsStream()Instanzmethode können Paket relativen Ressourcen Durchsuchungen durchführen (was auch sehr nützlich sein können, finden Sie unter ‚Got Ressourcen?‘). Um zwischen relativen und absoluten Ressourcennamen zu unterscheiden, Class.getResourceAsStream()werden führende Schrägstriche für absolute Namen verwendet. Im Allgemeinen ist diese Methode nicht erforderlich, wenn Sie nicht vorhaben, paketbezogene Ressourcennamen im Code zu verwenden.

Es ist leicht , in diesem kleinen Verhaltensunterschiede zu verwechseln für ClassLoader.getResourceAsStream(), Class.getResourceAsStream(), und ResourceBundle.getBundle(). In der folgenden Tabelle sind die wichtigsten Punkte zusammengefasst, damit Sie sich besser erinnern können:

Verhaltensunterschiede

Methode Parameterformat Suchfehlerverhalten Anwendungsbeispiel

ClassLoader.

getResourceAsStream ()

"/" - getrennte Namen; kein führendes "/" (alle Namen sind absolut) Lautlos (kehrt zurück null)

this.getClass (). getClassLoader ()

.getResourceAsStream

("some / pkg / resource.properties")

Klasse.

getResourceAsStream ()

"/" - getrennte Namen; führendes "/" zeigt absolute Namen an; Alle anderen Namen beziehen sich auf das Paket der Klasse Lautlos (kehrt zurück null)

this.getClass ()

.getResourceAsStream

("resource.properties")

ResourceBundle.

getBundle ()

"." - getrennte Namen; alle Namen sind absolut; .propertiesSuffix ist impliziert

Wirft ungeprüft

java.util.MissingResourceException

ResourceBundle.getBundle

("some.pkg.resource")

Von Datenströmen zu java.util.Properties

Möglicherweise haben Sie bemerkt, dass einige zuvor erwähnte Methoden nur halbe Sachen sind: Sie geben InputStreams zurück und nichts, was einer Liste von Name-Wert-Paaren ähnelt. Glücklicherweise ist das Laden von Daten in eine solche Liste (die eine Instanz sein kann java.util.Properties) einfach genug. Da Sie dies immer wieder tun, ist es sinnvoll, einige Hilfsmethoden für diesen Zweck zu erstellen.

Der kleine Verhaltensunterschied zwischen den in Java integrierten Methoden zum Laden von Klassenpfadressourcen kann ebenfalls störend sein, insbesondere wenn einige Ressourcennamen fest codiert wurden, Sie jetzt jedoch zu einer anderen Lademethode wechseln möchten. Es ist sinnvoll, kleine Dinge zu abstrahieren, z. B. ob Schrägstriche oder Punkte als Namenstrennzeichen usw. verwendet werden. Ohne weiteres ist hier meine PropertyLoaderAPI, die Sie möglicherweise nützlich finden (verfügbar mit dem Download dieses Artikels):

public abstract class PropertyLoader {/ ** * Sucht im Klassenpfad nach einer Ressource mit dem Namen 'name'. Die Ressource muss * einer Datei mit der Erweiterung .properties zuordnen. Der Name wird als absolut * angenommen und kann entweder "/" oder "" verwenden. für die Paketsegmenttrennung mit dem Suffix * optional führend "/" und optional ".properties". Daher beziehen sich die * folgenden Namen auf dieselbe Ressource: *
 * some.pkg.Resource * some.pkg.Resource.properties * some / pkg / Resource * some / pkg / Resource.properties * / some / pkg / Resource * /some/pkg/Resource.properties * 
* * @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 accepts a 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() convenience method decides which classloader to use for loading the resource. The solution shown is reasonable but not perfect; you might consider using techniques described in "Find a 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 true isn't advantageous unless you want to benefit from localization support built into java.util.ResourceBundle. Also, Java internally caches resource bundles, so you can avoid repeated disk file reads for the same resource name.

More things to come

I intentionally omitted an interesting classpath resource loading method, ClassLoader.getResources(). Despite its infrequent use, ClassLoader.getResources() allows for some very intriguing options in designing highly customizable and easily configurable applications.

Ich habe ClassLoader.getResources()in diesem Artikel nicht darüber gesprochen, weil es einen speziellen Artikel wert ist. Diese Methode geht zufällig mit der verbleibenden Methode zum Erwerb von Ressourcen einher: java.net.URLs. Sie können diese als noch allgemeinere Ressourcendeskriptoren als Zeichenfolgen für Klassenpfadressourcennamen verwenden. Weitere Informationen finden Sie in der nächsten Java Q & A- Ausgabe.

Vladimir Roubtsov programmiert seit mehr als 13 Jahren in verschiedenen Sprachen, einschließlich Java seit 1995. Derzeit entwickelt er Unternehmenssoftware als Senior Engineer für Trilogy in Austin, Texas.

Erfahren Sie mehr über dieses Thema

  • Laden Sie die komplette Bibliothek herunter, die diesem Artikel beiliegt

    //images.techhive.com/downloads/idge/imported/article/jvw/2003/08/01-qa-0808-property.zip

  • Das .properties-Format

    //java.sun.com/j2se/1.4.1/docs/api/java/util/Properties.html#load(java.io.InputStream)

  • "Got Resources?" Vladimir Roubtsov (JavaWorld, November 2002)

    //www.javaworld.com/javaworld/javaqa/2002-11/02-qa-1122-resources.html

  • "Find a Way Out of the ClassLoader Maze," Vladimir Roubtsov (JavaWorld, June 2003)

    //www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html

  • Want more? See the Java Q&A index page for the full Q&A catalog

    //www.javaworld.com/columns/jw-qna-index.shtml

  • For more than 100 insightful Java tips, visit JavaWorld's Java Tips index page

    //www.javaworld.com/columns/jw-tips-index.shtml

  • Besuchen Sie den Abschnitt Core Java im aktuellen Index von JavaWorld

    //www.javaworld.com/channel_content/jw-core-index.shtml

  • Durchsuchen Sie den Abschnitt Java Virtual Machine im aktuellen Index von JavaWorld

    //www.javaworld.com/channel_content/jw-jvm-index.shtml

  • Besuchen Sie die Java Anfänger Diskussion

    //www.javaworld.com/javaforums/postlist.php?Cat=&Board=javabeginner

  • Melden Sie sich für den kostenlosen wöchentlichen E-Mail-Newsletter von JavaWorld an

    //www.javaworld.com/subscribe

Diese Geschichte "Intelligentes Laden Ihrer Eigenschaften" wurde ursprünglich von JavaWorld veröffentlicht.