Muat properti Anda dengan cerdas

8 Agustus 2003

T: Apa strategi terbaik untuk memuat file properti dan konfigurasi di Java?

J: Secara umum, file konfigurasi dapat memiliki struktur kompleks yang sewenang-wenang (misalnya, file definisi skema XML). Tetapi untuk kesederhanaan, saya berasumsi di bawah ini bahwa kita berurusan dengan daftar datar pasangan nama-nilai ( .propertiesformat yang sudah dikenal ). Namun, tidak ada alasan mengapa Anda tidak dapat menerapkan gagasan yang ditampilkan di bawah ini dalam situasi lain, selama sumber daya yang dimaksud dibuat dari InputStream.

Java.io.File jahat

Menggunakan file lama yang baik (melalui FileInputStream, FileReaderdanRandomAccessFile) cukup sederhana dan tentunya merupakan rute yang jelas untuk dipertimbangkan bagi siapa pun yang tidak memiliki latar belakang Java. Tapi ini adalah pilihan terburuk dalam hal kemudahan penyebaran aplikasi Java. Menggunakan nama file absolut dalam kode Anda bukanlah cara untuk menulis kode portabel dan kode independen posisi disk. Menggunakan nama file relatif sepertinya merupakan alternatif yang lebih baik, tetapi ingat bahwa nama tersebut diselesaikan relatif terhadap direktori JVM saat ini. Pengaturan direktori ini bergantung pada detail proses peluncuran JVM, yang dapat disamarkan oleh skrip shell startup, dll. Menentukan pengaturan akan menempatkan beban konfigurasi yang tidak adil pada pengguna akhir (dan dalam beberapa kasus, jumlah kepercayaan yang tidak dapat dibenarkan kemampuan pengguna). Dan dalam konteks lain (seperti server aplikasi Enterprise JavaBeans (EJB) / Web),baik Anda maupun pengguna tidak memiliki banyak kendali atas direktori JVM saat ini.

Modul Java yang ideal adalah sesuatu yang Anda tambahkan ke classpath, dan siap digunakan. Pikirkan botol EJB, aplikasi Web yang dikemas dalam .warfile, dan strategi penyebaran nyaman serupa lainnya. java.io.Fileadalah area yang paling tidak bergantung platform di Jawa. Kecuali Anda benar-benar harus menggunakannya, cukup katakan tidak pada file.

Resource classpath

Setelah mengabaikan cacian di atas, mari kita bicara tentang opsi yang lebih baik: memuat resource melalui classloader. Ini jauh lebih baik karena classloader pada dasarnya bertindak sebagai lapisan abstraksi antara nama sumber daya dan lokasi sebenarnya pada disk (atau di tempat lain).

Katakanlah Anda perlu memuat resource classpath yang terkait dengan sebuah some/pkg/resource.propertiesfile. Saya menggunakan resource classpath yang berarti sesuatu yang dikemas dalam salah satu aplikasi jar atau ditambahkan ke classpath sebelum aplikasi diluncurkan. Anda dapat menambahkan ke classpath melalui -classpathopsi JVM setiap kali aplikasi dimulai atau dengan menempatkan file di \classesdirektori sekali dan untuk selamanya. Poin utamanya adalah bahwa men-deploy resource classpath mirip dengan menerapkan class Java yang dikompilasi , dan di situlah letak kenyamanannya.

Anda bisa mendapatkan secara some/pkg/resource.propertiesterprogram dari kode Java Anda dengan beberapa cara. Percobaan pertama:

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

Selain itu, jika kode berada di kelas dalam some.pkgpaket Java, maka berikut ini juga berfungsi:

 Class.getResourceAsStream ("resource.properties"); 

Perhatikan perbedaan halus dalam pemformatan parameter untuk metode ini. Semua getResourceAsStream()metode menggunakan garis miring untuk memisahkan segmen nama paket, dan nama sumber daya menyertakan ekstensi file. Bandingkan itu dengan paket sumber daya yang nama sumber daya lebih mirip pengenal Java, dengan titik-titik yang memisahkan segmen nama paket ( .propertiesekstensi tersirat di sini). Tentu saja, itu karena bundel sumber daya tidak harus didukung oleh .propertiesfile: itu bisa berupa kelas, misalnya.

Untuk sedikit mempersulit gambar, java.lang.Class's getResourceAsStream()metode contoh dapat melakukan pencarian sumber daya paket-relatif (yang dapat berguna juga, lihat 'Got Resources?'). Untuk membedakan antara nama sumber daya relatif dan absolut, Class.getResourceAsStream()gunakan garis miring di depan untuk nama absolut. Secara umum, metode ini tidak perlu digunakan jika Anda tidak berencana menggunakan penamaan resource relatif paket dalam kode.

Sangat mudah untuk ikut campur dalam perbedaan-perbedaan perilaku kecil untuk ClassLoader.getResourceAsStream(), Class.getResourceAsStream(), dan ResourceBundle.getBundle(). Tabel berikut merangkum poin-poin penting untuk membantu Anda mengingat:

Perbedaan perilaku

metode Format parameter Perilaku kegagalan pencarian Contoh penggunaan

ClassLoader.

getResourceAsStream ()

"/" - nama terpisah; tanpa awalan "/" (semua nama mutlak) Diam (kembali null)

this.getClass (). getClassLoader ()

.getResourceAsStream

("beberapa / pkg / resource.properties")

Kelas.

getResourceAsStream ()

"/" - nama terpisah; leading "/" menunjukkan nama absolut; semua nama lain relatif terhadap paket kelas Diam (kembali null)

this.getClass ()

.getResourceAsStream

("resource.properties")

ResourceBundle.

getBundle ()

"." - nama terpisah; semua nama adalah mutlak; .propertiessufiks tersirat

Melempar tidak dicentang

java.util.MissingResourceException

ResourceBundle.getBundle

("some.pkg.resource")

Dari aliran data ke java.util.Properties

Anda mungkin telah memperhatikan bahwa beberapa metode yang disebutkan sebelumnya hanya setengah ukuran: mereka mengembalikan InputStreams dan tidak ada yang menyerupai daftar pasangan nama-nilai. Untungnya, memuat data ke dalam daftar semacam itu (yang bisa menjadi contoh java.util.Properties) cukup mudah. Karena Anda akan mendapati diri Anda melakukan ini berulang kali, masuk akal untuk membuat beberapa metode pembantu untuk tujuan ini.

Perbedaan kecil perilaku di antara metode built-in Java untuk pemuatan resource classpath juga bisa menjadi gangguan, terutama jika beberapa nama resource di-hardcode tetapi sekarang Anda ingin beralih ke metode pemuatan lain. Masuk akal untuk mengabstraksi hal-hal kecil seperti apakah garis miring atau titik digunakan sebagai pemisah nama, dll. Tanpa basa-basi lagi, inilah PropertyLoaderAPI saya yang mungkin berguna bagi Anda (tersedia dengan unduhan artikel ini):

public abstract class PropertyLoader {/ ** * Mencari resource bernama 'name' di classpath. Sumber daya harus memetakan * ke file dengan ekstensi .properties. Nama diasumsikan absolut * dan dapat menggunakan "/" atau "." untuk pemisahan segmen paket dengan akhiran * opsional "/" dan opsional ".properties". Jadi, * nama berikut merujuk ke sumber daya yang sama: *
 * some.pkg.Resource * some.pkg.Resource.properties * some / pkg / Resource * some / pkg / Resource.properties * / some / pkg / Resource * /some/pkg/Resource.properties * 
* * @ nama parameter classpath nama sumber daya [tidak boleh null] * @param loader classloader yang akan digunakan untuk memuat sumber daya [null * setara dengan pemuat aplikasi] * * @return sumber daya dikonversi ke java.util.Properties [mungkin null jika * sumber daya tidak ditemukan dan THROW_ON_LOAD_FAILURE salah] * @throws IllegalArgumentException jika sumber daya tidak ditemukan dan * THROW_ON_LOAD_FAILURE benar * / Properti statis publik loadProperties (Nama string, pemuat ClassLoader) {if (name == null) lempar new IllegalArgumentException ("input null: nama"); jika (name.startsWith ("/")) name = name.substring (1); if (name.endsWith (SUFFIX)) name = name.substring (0, name.length () - SUFFIX.length ()); Hasil properti = null; InputStream in = null; coba {if (loader == null) loader = ClassLoader.getSystemClassLoader ();jika (LOAD_AS_RESOURCE_BUNDLE) {name = name.replace ('/', '.'); // Melempar MissingResourceException pada kegagalan pencarian: ResourceBundle akhir rb = ResourceBundle.getBundle (name, Locale.getDefault (), loader); hasil = Properti baru (); untuk (Enumeration keys = rb.getKeys (); keys.hasMoreElements ();) {final String key = (String) keys.nextElement (); Nilai String terakhir = rb.getString (key); result.put (kunci, nilai); }} lain {nama = nama.replace ('.', '/'); if (! name.endsWith (SUFFIX)) name = name.concat (SUFFIX); // Mengembalikan null pada kegagalan pencarian: in = loader.getResourceAsStream (name); if (in! = null) {result = new Properties (); result.load (dalam); // Bisa melempar IOException}}} catch (Exception e) {result = null; } akhirnya {if (in! = null) coba {in.close ();} catch (Throwable ignore) {}} if (THROW_ON_LOAD_FAILURE && (result == null)) {throw new IllegalArgumentException ("tidak dapat memuat [" + name + "]" + "as" + (LOAD_AS_RESOURCE_BUNDLE? "a resource bundle" : "sumber daya classloader")); } hasil pengembalian; } / ** * Kelebihan kenyamanan {@link #loadProperties (String, ClassLoader)} * yang menggunakan classloader konteks thread saat ini. * / Properti statis publik loadProperties (nama String akhir) {return loadProperties (name, Thread.currentThread () .getContextClassLoader ()); } boolean akhir statis pribadi THROW_ON_LOAD_FAILURE = true; boolean akhir statis pribadi LOAD_AS_RESOURCE_BUNDLE = false; private static final String SUFFIX = ".properties"; } // Akhir kelas(result == null)) {throw new IllegalArgumentException ("tidak dapat memuat [" + name + "]" + "as" + (LOAD_AS_RESOURCE_BUNDLE? "a resource bundle": "a classloader resource")); } hasil pengembalian; } / ** * Kelebihan kenyamanan {@link #loadProperties (String, ClassLoader)} * yang menggunakan classloader konteks thread saat ini. * / Properti statis publik loadProperties (nama String akhir) {return loadProperties (name, Thread.currentThread () .getContextClassLoader ()); } boolean akhir statis pribadi THROW_ON_LOAD_FAILURE = true; boolean akhir statis pribadi LOAD_AS_RESOURCE_BUNDLE = false; private static final String SUFFIX = ".properties"; } // Akhir kelas(result == null)) {throw new IllegalArgumentException ("tidak dapat memuat [" + name + "]" + "as" + (LOAD_AS_RESOURCE_BUNDLE? "a resource bundle": "a classloader resource")); } hasil pengembalian; } / ** * Kelebihan kenyamanan {@link #loadProperties (String, ClassLoader)} * yang menggunakan classloader konteks thread saat ini. * / Properti statis publik loadProperties (nama String akhir) {return loadProperties (name, Thread.currentThread () .getContextClassLoader ()); } boolean akhir statis pribadi THROW_ON_LOAD_FAILURE = true; boolean akhir statis pribadi LOAD_AS_RESOURCE_BUNDLE = false; private static final String SUFFIX = ".properties"; } // Akhir kelaspaket sumber daya ":" a classloader resource "));} hasil kembalian;} / ** * Kelebihan beban praktis dari {@link #loadProperties (String, ClassLoader)} * yang menggunakan classloader konteks thread saat ini. * / public static Properti loadProperties (nama String akhir) {return loadProperties (name, Thread.currentThread () .getContextClassLoader ());} boolean final statis pribadi THROW_ON_LOAD_FAILURE = true; boolean final statis pribadi LOAD_AS_RESOURCE_BUNDLE = false; private static final String SUFFIX = ".properties ";} // Akhir kelaspaket sumber daya ":" a classloader resource "));} hasil kembalian;} / ** * Kelebihan beban praktis dari {@link #loadProperties (String, ClassLoader)} * yang menggunakan classloader konteks thread saat ini. * / public static Properti loadProperties (nama String akhir) {return loadProperties (name, Thread.currentThread () .getContextClassLoader ());} boolean final statis pribadi THROW_ON_LOAD_FAILURE = true; boolean final statis pribadi LOAD_AS_RESOURCE_BUNDLE = false; private static final String SUFFIX = ".properties ";} // Akhir kelasThread.currentThread () .getContextClassLoader ()); } boolean akhir statis pribadi THROW_ON_LOAD_FAILURE = true; boolean akhir statis pribadi LOAD_AS_RESOURCE_BUNDLE = false; private static final String SUFFIX = ".properties"; } // Akhir kelasThread.currentThread () .getContextClassLoader ()); } boolean akhir statis pribadi THROW_ON_LOAD_FAILURE = true; boolean akhir statis pribadi LOAD_AS_RESOURCE_BUNDLE = false; private static final String SUFFIX = ".properties"; } // Akhir kelas

Komentar Javadoc untuk loadProperties()metode tersebut menunjukkan bahwa persyaratan masukan metode cukup santai: ia menerima nama sumber daya yang diformat sesuai dengan skema metode asli mana pun (kecuali untuk nama relatif paket yang memungkinkan dengan Class.getResourceAsStream()) dan menormalkannya secara internal untuk melakukan hal yang benar.

loadProperties()Metode kenyamanan yang lebih singkat memutuskan classloader mana yang akan digunakan untuk memuat sumber daya. Solusi yang ditampilkan masuk akal tetapi tidak sempurna; Anda dapat mempertimbangkan untuk menggunakan teknik yang dijelaskan dalam "Menemukan Jalan Keluar dari Labirin Pemuat Kelas".

Perhatikan bahwa dua konstanta kompilasi bersyarat mengontrol loadProperties()perilaku, dan Anda dapat menyetelnya agar sesuai dengan selera Anda:

  • THROW_ON_LOAD_FAILUREmemilih apakah akan loadProperties()melontarkan pengecualian atau hanya kembali nulljika tidak dapat menemukan sumber daya
  • 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.

Saya tidak membahas ClassLoader.getResources()dalam artikel ini karena layak untuk artikel khusus. Ketika itu terjadi, metode ini sejalan dengan cara yang tersisa untuk memperoleh sumber daya: java.net.URLs. Anda dapat menggunakan ini bahkan sebagai deskriptor sumber daya yang lebih umum daripada string nama sumber daya classpath. Cari detail lebih lanjut di bagian Q&A Java berikutnya .

Vladimir Roubtsov telah memprogram dalam berbagai bahasa selama lebih dari 13 tahun, termasuk Java sejak 1995. Saat ini, ia mengembangkan perangkat lunak perusahaan sebagai insinyur senior untuk Trilogi di Austin, Texas.

Pelajari lebih lanjut tentang topik ini

  • Unduh perpustakaan lengkap yang menyertai artikel ini

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

  • Format .properties

    //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

  • Kunjungi bagian Core Java dari Indeks Topikal JavaWorld

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

  • Jelajahi bagian Java Virtual Machine dari Indeks Topikal JavaWorld

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

  • Kunjungi diskusi Pemula Java

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

  • Mendaftarlah untuk buletin email mingguan gratis JavaWorld

    //www.javaworld.com/subscribe

Artikel ini, "Smartly load your properties", awalnya diterbitkan oleh JavaWorld.