⇧ amaizing...
Javaでクラスパスとpropertiesファイルが読み込まれる関係を知りたい
何をいまさらとお思いの方もおるでしょうが、MavenとかGradleとかいったビルドツールでJavaのプロジェクトを作成すると、
ってフォルダが作られると思うのですが、いずれもクラスパスが通ってますと。
で、
⇧ 上記サイト様によりますと、ClassLoaderが影響してるのか分からんのだけど、先に見つかったリソースが優先されるらしいのですが、先に読み込む優先順位は何が決めてるのか?
と言うか、
⇧ src/test/resources配下の設定ファイルのが影響力が大きいと仰っておられる方もおりますと。
他にも、
⇧ 諸説あるのでよう分からん...
実際にpropertiesファイルを読み込んでみる
ということで、
⇧ Oracleさんが説明してるClassLoaderで読み込む方法で試してみました。
EclipseでMavenプロジェクトを作成して、以下のような構成でファイルを作成。
ファイルの内容は以下。
■/properties-test/conf/config.properties
# conf properties properties_01=conf01_properties_value properties_02=conf01_properties_value properties_03=conf01_properties_value properties_04=conf01_properties_value
■/properties-test/src/main/resources/setting.properties
# main properties properties_01=main01_properties_value properties_02=main01_properties_value properties_03=main01_properties_value properties_04=main01_properties_value
■/properties-test/src/test/resources/setting.properties
# test properties properties_01=test01_properties_value properties_02=test01_properties_value properties_03=test01_properties_value properties_04=test01_properties_value
■/properties-test/src/main/java/com/ReadProperties.java
package com; import java.io.IOException; import java.io.InputStream; import java.util.Map.Entry; import java.util.Properties; public class ReadProperties { public static void main(String[] args) { // InputStream inClassPathResource = ClassLoader.getSystemResourceAsStream("setting.properties"); Properties inClassPathProperties = new Properties(); try { inClassPathProperties.load(inClassPathResource); for (Entry<Object, Object> propertyEnrty: inClassPathProperties.entrySet()) { System.out.println(propertyEnrty.getKey() + ":" + propertyEnrty.getValue()); } } catch (IOException e) { // e.printStackTrace(); } // InputStream inNotClassPathResource = ClassLoader.getSystemResourceAsStream("config.properties"); Properties inNotClassPathProperties = new Properties(); try { inNotClassPathProperties.load(inNotClassPathResource); for (Entry<Object, Object> propertyEnrty: inNotClassPathProperties.entrySet()) { System.out.println(propertyEnrty.getKey() + ":" + propertyEnrty.getValue()); } } catch (IOException e) { // e.printStackTrace(); } } }
■/properties-test/src/test/java/com/ReadPropertiesTest.java
package com; public class ReadPropertiesTest { public static void main(String[] args) { // ReadProperties.main(args); } }
⇧ で保存して、Javaアプリケーションの実行を試してみる。
■/properties-test/src/main/java/com/ReadProperties.javaの実行結果
■/properties-test/src/test/java/com/ReadPropertiesTest.javaの実行結果
というように、ClassLoader.getSystemResourceAsStream()で同名のpropertiesファイルを取得する場合、
- src/main/javaを実行した場合は、src/main/resourcesにあるpropertiesファイル
- src/test/javaからsrc/main/javaを実行した場合は、src/test/resourcesにあるpropertiesファイル
が読み込まれるようです。
ClassLoader.getSystemResourceAsStream()の説明によると、
Open for reading, a resource of the specified name from the search pathused to load classes. This method locates the resource through thesystem class loader (see getSystemClassLoader()).
Resources in named modules are subject to the encapsulation rulesspecified by Module.getResourceAsStream.Additionally, and except for the special case where the resource has aname ending with ".class", this method will only find resources inpackages of named modules when the package is opened unconditionally.
パラメーター:name The resource name
戻り値:An input stream for reading the resource; null if theresource could not be found, the resource is in a package thatis not opened unconditionally, or access to the resource isdenied by the security manager.
開始:1.1
@revised9
⇧ クラスパスだけでなく、Java 9から導入されたModuleなんかも考慮する必要もあるってことなんですかね?
そして、クラスパスではない、/properties-test/confに配置されたpropertieesファイルは読み込むことができないようです。
EclipseでMavenプロジェクト作った場合、.classpathってファイルでクラスパスとか設定されてるっぽい。
<?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" output="target/classes" path="src/main/java"> <attributes> <attribute name="optional" value="true"/> <attribute name="maven.pomderived" value="true"/> </attributes> </classpathentry> <classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"> <attributes> <attribute name="maven.pomderived" value="true"/> </attributes> </classpathentry> <classpathentry kind="src" output="target/test-classes" path="src/test/java"> <attributes> <attribute name="optional" value="true"/> <attribute name="maven.pomderived" value="true"/> <attribute name="test" value="true"/> </attributes> </classpathentry> <classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"> <attributes> <attribute name="maven.pomderived" value="true"/> <attribute name="test" value="true"/> </attributes> </classpathentry> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"> <attributes> <attribute name="maven.pomderived" value="true"/> </attributes> </classpathentry> <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> <attributes> <attribute name="maven.pomderived" value="true"/> </attributes> </classpathentry> <classpathentry kind="output" path="target/classes"/> </classpath>
⇧ 上記のclasspathentryにフォルダを追加すれば、そのフォルダに配置したpropertiesファイルも読み込んでくれるようになるってことですかね?
結局のところ、Java側でpropertiesファイルをどうやって読み込んでいるかにもよりけりということですかね。
2023年3月16日(木)追記:↓ ここから
クラスパスが通ってない場所から読み込みたい場合は、
⇧ クラスローダーは無力ということですかね...
システム・リソースとは、システムに組み込まれているリソース、またはホストの実装が保持するリソースのことで、ローカル・ファイル・システムがその一例です。プログラムがシステム・リソースにアクセスするには、ClassLoader
のメソッドであるgetSystemResource
およびgetSystemResourceAsStream
を使用します。
https://docs.oracle.com/javase/jp/8/docs/technotes/guides/lang/resources.html
⇧ ローカル・ファイル・システムは、ホストの実装が保持するリソースで、
ファイルシステムは、コンピュータのリソースを操作するための、オペレーティングシステム (OS) が持つ機能の一つ。ファイルとは、主に補助記憶装置に格納されたデータを指すが、デバイスやプロセス、カーネル内の情報といったものもファイルとして提供するファイルシステムもある。
⇧「OS(Operation System)」が持つ機能の1つがファイルシステム。
また、Javaのドキュメントの不備なのか...結局、位置に依存しとるやんけ...
public static InputStream getSystemResourceAsStream(String name)
クラスをロードするのに使用される検索パスから、指定された名前のリソースを、読込み用にオープンします。このメソッドはシステム・クラス・ローダー(getSystemClassLoader()
を参照)を使ってリソースを見つけます。
⇧ なんか、あくまで、「クラスパス」に関係するローカル・ファイル・システムしか検索しないんじゃないかな?
public static ClassLoader getSystemClassLoader()
委譲のためのシステム・クラス・ローダーを返します。これは、新しいClassLoaderインスタンスのデフォルトの委譲の親で、通常、アプリケーションを起動するためのクラス・ローダーです。
https://docs.oracle.com/javase/jp/8/docs/api/java/lang/ClassLoader.html#getSystemClassLoader--
⇧ だから、結局、「クラスパス」に含まれないファイルは検索できないってことで良いんかな?
ちなみに、「システム・クラス・ローダー」ってのは、
Figure 2–1 Class Loader Runtime Hierarchy
https://docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html
⇧ 上図の「System Class Loader」のことになるんかね?
『システム・リソースとは、システムに組み込まれているリソース、またはホストの実装が保持するリソースのことで、ローカル・ファイル・システムがその一例です。プログラムがシステム・リソースにアクセスするには、ClassLoader
のメソッドであるgetSystemResource
およびgetSystemResourceAsStream
を使用します。』
⇧ 語弊を招く記述ですかね...
まぁ、多分、ClassLoader系のメソッドは、「クラスパス」に無い場所のファイルは検索できないんでしょうね...
Oracleさんのドキュメントの信用度が低いの何とかして欲しい...
2023年3月16日(木)追記:↑ ここまで
毎度モヤモヤ感が半端ない...
今回はこのへんで。