[java] 리소스를 읽는 3가지 방법 (feat. compile, source path, classloader)

YJ KIM·2025년 2월 19일
1
현재 환경 

- JavaSE-1.8 (별 상관이 없다)
- eclipse (bin 파일 이름 관련해서만..)

사실 java 공통적인 내용이라 환경에 영향 받지 않는다. IDE 내부 설정에는 영향 받을 수 있음!

현재 개발 중인 프로젝트에서 테스트 코드를 작성할 때, 리소스 파일을 동적으로 로드해야 하는 상황이 생겼다.

사실 단순히 파일 시스템으로 해결한다면 1초만에 해결할 수 있는 상황이나, 로컬 개발 환경이 아닐 때 테스트를 진행한다면 경로를 찾을 수 없어 테스트에 실패한다.

결과적으로!
동적으로, 파일 시스템(절대/상대 경로)이 아닌 class loader를 통해 리소스를 로드해야 한다. 는 결론을 얻게 되었다.

기본적으로 현재 메소드 안에서, 리소스를 사용한다면 파일의 상대 경로를 통해 접근할 수 있다.

1. 상대 경로로 접근하기

	static final String relativePath = "src/text/text.txt"; // 현재 프로젝트 루트 경로에서 실행함!
    
// 1. 현재 파일 경로로 읽기
	public static void readTxtInFileSys() {
		File file = new File(relativePath);
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
		} catch (FileNotFoundException e) {
			System.out.println("현재 파일 경로에서 파일을 찾을 수 없습니다!");
		}
	}

상대 경로를 지정할 때, 항상 기준은 현재 프로젝트의 루트 경로이다. 그에 따라 relativePath를 정의할 수 있다.
현재 프로젝트에서는 src/text 디렉토리 하위에 txt 파일을 저장하고 있기 때문에 이를 source path로 정의해야 한다.

현재 프로젝트 > properties > build path > source 들어가서 add folder 설정

source path란?
compile 시에, .java 파일은 .class 파일로 컴파일 된다는 사실은 모두 다 알고 있다. 동시에, source path에 기재된 source 파일도 bin 파일에 복사되어 저장된다.

IDE에 따라 bin 파일일수도 (eclipse는 bin) out, ouput일 수도 있다. class 파일이 저장되는 위치를 말한다.
jdk compiler가 .text, .xml, .properties, .png(등 이하 이미지 파일) 이외에 많은 리소스들을 리소스인지 아닌지 판단해서 컴파일 타임에 컴파일 결과물들을 담는 디렉토리에 복사한다.

2. compile 산출물 결과로 접근하기

	// 2. 빌드 binary 파일에서 읽기 (source path copy -> bin folder)
	public static void readTxtInBinSys() {
		final String binFile = "bin/text.txt";

		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(binFile)));
		} catch (FileNotFoundException e) {
			System.out.println("현재 빌드 파일 경로에서 파일을 찾을 수 없습니다!");
		}
	}

위에서 말한 compile시에 bin 디렉토리에 복사된 text.txt에 접근해서 읽는 방법이다.

어차피 1과 같은 파일시스템이라 큰 차이점은 존재하지 않는다.

3. class loader를 통해 접근하기

프로젝트를 배포하면 보통 .jar 파일로 배포하게 된다.
jar 파일 내부에서는 파일 시스템의 경로로 접근할 수 없다. 결론적으로 1, 2의 방법을 사용할 수 없다.

프로젝트를 항상 로컬에서만 개발할 수는 없기 때문에 결과적으로 class loader를 통해 접근하는 식으로 변경해야겠다!

	// 3. class loader를 통해 읽기
	public static void readTxtInJarSys() {
		try {
			// check class loader
			checkTwoClassLoader();

			InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream("text.txt");
			if (in == null) {
				throw new FileNotFoundException();
			}
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
		} catch (FileNotFoundException e) {
			System.out.println("classLoader가 test.txt를 찾는데 실패했습니다! (혹은 stream 문제일수도..)");
		} catch (Exception e) {
			System.out.println("classLoader를 통한 text.txt를 읽는데 실패했습니다!");
		}
	}

	public static void checkTwoClassLoader() {
		ClassLoader nowClassLoader = Test.class.getClassLoader();
		ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();

		System.out.println("현재 클래스의 class loader: " + nowClassLoader);
		System.out.println("현재 클래스의 class loader: " + sysClassLoader);
	}

class loader를 얻어서, 해당 class loader를 통해 resource를 stream의 형태로 읽을 수 있다. jar는 파일 시스템으로 동작할 수 없기 때문에 File의 형태로 얻어올 수는 없다!

이때 class loader를 비교하는 함수를 만들어봤는데, 어떤 class loader를 사용해야 하는지 궁금해서 해봤다. 아직 class loader hierarchy에 대해 자세히 알진 못하지만, 현재 클래스의 class loader와 system class loader가 동일한 application class loader이다.

현재 클래스의 class loader: sun.misc.Launcher$AppClassLoader@4e0e2f2a
현재 클래스의 class loader: sun.misc.Launcher$AppClassLoader@4e0e2f2a

class loader 관련해서 좋은 글을 발견했다!
Java 클래스로더 훑어보기

몰랐던 개념들에 대해 많이 알게 된 것 같다~! 확실히 모르는 게 많군 ..

profile
모르면 쓰지 말고 쓸 거면 알고 쓰자

0개의 댓글

관련 채용 정보