자바에서 리소스(설정파일,이미지,사운드 등)를 읽어들일때 Class 클래스와 ClassLoader 클래스에서 똑같은 이름의 메소드를 사용할 수 있다.


Class.getResourceAsStream()

ClassLoader.getResourceAsStream()


결론부터 말하면 Class.getResourceAsStream() 메소드는 결국 ClassLoader.getResourceAsStream() 메소드를 호출한다.


Class.getResouceAsStream() 메소드가 하는 일은 리소스의 경로를 ClassLoader의 기준에 맞춰서 재조합해준 후에 ClassLoader.getResourceAsStream() 메소드를 호출해주는 것 뿐이다.


아래와 같이 세 개의 리소스가 있을때 리소스의 절대 경로는 아래와 같다.


/pkg/relativeR.txt

/other/other.txt

/other/resources/absR.txt


/pkg/Main.class <- 애플리케이션의 시작 지점


[사진1] 리소스 파일의 경로


relativeR.txt 의 경로는 pkg/relativeR.txt 에 위치하고 애플리케이션의 시작지점인 Main 클래스와 동일한 경로에 있다. 이러한 경우 다음과 같이 리소스를 읽어들일 수 있다.


Main.class.getResourceAsStream("relativeR.txt");


show >>

[코드1] 상대 경로 방식


Class.getResourceAsStream(String) 에서는 전달받은 리소스이름이 절대경로 "/" 로 시작하는지를 본다. 절대 경로로 시작하지 않으면 현재 클래스인 Main 의 경로인 "/pkg" 를 기준으로 전달받은 리소스 이름을 적용해서 경로를 정한다.


또는 절대 경로 방식으로 찾을 수도 있다.


Main.class.getResourceAsStream("/pkg/relativeR.txt");


show >>

[코드2] 절대경로 방식


[코드1]에서 상대 경로 방식으로 호출했을때 패키지 경로를 적용해서 [코드2]처럼 절대 경로로 수정된다.( [코드1]은 리소스 파일이 특정 클래스와 같은 경로에 있을 경우에 주로 사용되고, [코드2]는 리소스 파일들을 별도의 패키지 내에 몰아넣었을 경우에 사용되는 방식이다.)


예를 들어서 other.txt와 absR.txt 처럼 Main 클래스와 상관없는 별도의 패키지 안에 리소스들이 들어있는 경우도 절대 경로로 접근할 수 있다.


show >>

[코드3] 절대경로는 클래스 기준이 아니라 클래스로더기준


이러한 경우 기준 지점인 Main 클래스의 경로는 더 이상 아무 의미가 없고, Main 클래스를 읽어들인 클래스로더가 기준이 된다.(원래 리소스는 결국 클래스로더 기준으로 찾게 됨.)


리소스 로딩의 문제점


모든 리소스는 결국 ClassLoader 를 기준으로 찾는데(즉, 클래스패스 내에서 찾는다는 뜻), 사용자 편의를 위해서 Class 에도 리소스를 읽어들일 수 있게 getResourceAsStream 메소드를 만들어놓은 것이 오히려 더 많은 혼란을 일으키는 상황. 


똑같은 메소드 이름이 ClassLoader에 있다보니 많은 사람들이 양쪽의 차이점을 인지하지 못하고 잘못된 경로값을 전달해서 NullPointerException 예외를 겪는다.(리소스가 없어서 그런건지, 리소스는 있지만 경로를 못찾은건지 헷갈림)


대표적인게 ClassLoader에서 다음과 같이 사용하는 것이다.


show >>

[코드4] ClassLoader로 리소스 읽을때 절대 경로 금지


Class.getResourceAsStream() 메소드는


1. 리소스가 상대경로이면 절대 경로로 바꾸고

2. 절대 경로에서 맨 앞의 "/" 을 떼낸 다음에

3. "/"을 떼낸 리소소의 절대 경로를 클래스로더에게 전달해서


리소스를 읽어들이는데, 절대경로에서 "/" 을 떼내는 구현이 ClassLoader에는 없다.


이는 암묵적으로 Class.getResourceAsStream 으로 리소스를 읽어들이는것을 가정한 듯 하다.(그렇다면 ClassLoader 버전은 없애는게 더 나았을 수도 있는데...)


아니면 리소스 로딩을 클래스 로딩과 분리해서 명확하게 경로를 지정하게 했더라면 혼란은 덜하지 않았을까 싶다.


아래와 같이 하면 클래스 패스 내에서 절대 경로로 리소스를 찾고..


ResourceLoader.loadAbsolute("/other/resources/absR.txt");


아래와 같이 하면 Main.class 의 경로를 기준으로 리소스를 찾고...

ResourceLoader.loadRelative(Main.class, "relativeR.txt"); // Main.class이 경로에 있는 리소스..


정리하면, 리소스를 읽어들일때 가급적 Class.getResourceAsStream() 으로 읽어들이는게 정신 건강에 좋을 듯 하다. Class.getResourceAsStream() 을 호출하면 최종적으로 ClassLoader.getResourceAsStream() 이 실행된다.
















'Dev > Java' 카테고리의 다른 글

Jsoup 테스트 가능성 높이기.  (1) 2014.12.30
[JSR 310] New Date and Time API  (0) 2012.09.20
[번역]Java concurrency bug patterns for multicore systems  (0) 2010.12.28
Posted by yeori
,