※当サイトの記事には、広告・プロモーションが含まれます。

Javaでクラスパスとpropertiesファイルが読み込まれる関係を知りたい

nazology.net

⇧ amaizing...

Javaでクラスパスとpropertiesファイルが読み込まれる関係を知りたい

何をいまさらとお思いの方もおるでしょうが、MavenとかGradleとかいったビルドツールでJavaのプロジェクトを作成すると、

  • src/main/java
  • src/main/resources
  • src/test/java
  • src/test/resources

ってフォルダが作られると思うのですが、いずれもクラスパスが通ってますと。

で、

qiita.com

tyablog.net

⇧ 上記サイト様によりますと、ClassLoaderが影響してるのか分からんのだけど、先に見つかったリソースが優先されるらしいのですが、先に読み込む優先順位は何が決めてるのか?

と言うか、

horimislime.hateblo.jp

⇧ src/test/resources配下の設定ファイルのが影響力が大きいと仰っておられる方もおりますと。

他にも、

stackoverflow.com

stackoverflow.com

⇧ 諸説あるのでよう分からん...

実際にpropertiesファイルを読み込んでみる

ということで、

docs.oracle.com

Oracleさんが説明してるClassLoaderで読み込む方法で試してみました。

EclipseMavenプロジェクトを作成して、以下のような構成でファイルを作成。

ファイルの内容は以下。

■/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ファイルは読み込むことができないようです。

EclipseMavenプロジェクト作った場合、.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日(木)追記:↓ ここから

クラスパスが通ってない場所から読み込みたい場合は、

stackoverflow.com

stackoverflow.com

⇧ クラスローダーは無力ということですかね...

docs.oracle.com

システム・リソースとは、システムに組み込まれているリソース、またはホストの実装が保持するリソースのことで、ローカル・ファイル・システムがその一例です。プログラムがシステム・リソースにアクセスするには、ClassLoaderのメソッドであるgetSystemResourceおよびgetSystemResourceAsStreamを使用します。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/lang/resources.html

⇧ ローカル・ファイル・システムは、ホストの実装が保持するリソースで、

ファイルシステムは、コンピュータリソースを操作するための、オペレーティングシステム (OS) が持つ機能の一つ。ファイルとは、主に補助記憶装置に格納されたデータを指すが、デバイスプロセスカーネル内の情報といったものもファイルとして提供するファイルシステムもある。

ファイルシステム - Wikipedia

⇧「OS(Operation System)」が持つ機能の1つがファイルシステム

また、Javaのドキュメントの不備なのか...結局、位置に依存しとるやんけ...

docs.oracle.com

public static InputStream getSystemResourceAsStream(String name)

クラスをロードするのに使用される検索パスから、指定された名前のリソースを、読込み用にオープンします。このメソッドはシステム・クラス・ローダー(getSystemClassLoader()を参照)を使ってリソースを見つけます。

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/ClassLoader.html#getSystemResourceAsStream-java.lang.String-

⇧ なんか、あくまで、「クラスパス」に関係するローカル・ファイル・システムしか検索しないんじゃないかな?

public static ClassLoader getSystemClassLoader()

委譲のためのシステム・クラス・ローダーを返します。これは、新しいClassLoaderインスタンスのデフォルトの委譲の親で、通常、アプリケーションを起動するためのクラス・ローダーです。

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/ClassLoader.html#getSystemClassLoader--

⇧ だから、結局、「クラスパス」に含まれないファイルは検索できないってことで良いんかな?

ちなみに、「システム・クラス・ローダー」ってのは、

docs.oracle.com

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日(木)追記:↑ ここまで

毎度モヤモヤ感が半端ない...

今回はこのへんで。