5. 리소스
5.1 소개
자바의 표준 java.net.URL 클래스와 다양한 URL 접두어들에 대한 표준 핸들러는 안타깝게도 저수준 리소스에 모두 접근하기에는 전혀 충분하지 않다. 예를 들면 클래스패스나 ServletContext에 상대경로에서 얻어와야 하는 리소스에 접근하려고 사용할 표준화된 URL 구현체가 없다. 전문화된 URL 접두사(http:같은 접두가에 대한 핸들러가 존재하는 것처럼)에 대한 새로운 핸들러를 등록할 수 있기 때문에 이는 보통 꽤 복잡하고 URL 인터페이스는 리소스의 존재여부를 확인하는 메서드같은 몇가지 기대하는 기능이 여전히 부족하다.
5.2 Resource 인터페이스
스프링의 Resource 인터페이스는 저수준 리소스 접근을 추상화한 더 기능이 많은 인터페이스이다.
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
다음은 Resource인터페이스에서 가장 중요한 메서드들이다.
- getInputStream(): 리소스의 위치를 찾고 오픈한 뒤 리소스를 읽기 위한 InputStream를 리턴한다. 호출할 때마다 새로운 InputStream를 리턴한다. 스트림을 닫는 것은 호출한 쪽에 책임이 있다.
- exists(): 해당 리소스가 물리적인 형태로 실제 존재하는지 나타내는 boolean을 리턴한다.
- isOpen(): 해당 리소스가 오픈 스트림을 가진 하나의 핸들을 나타내는 지를 가르키는 boolean을 리턴한다. 이 값이 true이면 InputStream는 여러번 읽을 수 없고 반드시 오직 한번만 읽은 뒤에 리소스 누출을 피하기 위해 닫아야 한다. InputStreamResource 예외를 가진 모든 일반적인 리소스 구현체에서는 false가 될 것이다.
- getDescription(): 리소스로 작업할 때 오류 출력에 사용할 해당 리소스에 대한 설명을 리턴합니다. 이는 때로 정규화된 파일명이나 리소스의 실제 URL이다.
Resource 추상화는 리소스가 필요할 때 많은 메서드 시그니처에서 아규먼트 타입으로 스프링내에서 광범위하게 사용된다. 몇몇 스프링 API의 다름 메서드들은(다양한 ApplicationContext 구현체에 생성자같은) 간소화되고 간단한 형식으로 해당 컨텍스트 구현체에 적절한 Resource를 생성하는데 사용하거나 String 경로에서 특별한 접두사로 콜러(caller)가 생성하고 사용해야 하는 특정 Resource 구현체를 지정하도록 String을 받아들인다.
Resource 인터페이스는 스프링과 함께 또는 스프링에서 많이 사용되기 때문에 심지어 코드가 알지 못하는 엑세스에 접근하거나 스프링의 다른 부분에 관심이 있을 때 자신의 코드에서 단독으로 일반적인 유틸리티 클래스로써 사용하기에 실제로 아주 유용하다. 이는 코드를 스프링에 커플링하지만 URL에 대한 더 기능이 많은 대체제로 제공하는 이 작은 유틸리티 클래스 세트에만 실제로 커플링하고 같은 목적으로 사용할 다른 어떤 라이브러리에 대한 동일한 역할로 생각할 수 있다.
Resource 추상화가 기능을 교체하지 않는다는 것은 중요하다. 예를 들면 UrlResource는 URL을 감싸고 URL의 작업을 하는데 감싸진 URL를 사용한다.
5.3 내장된 Resource 구현체
스프링에서 혁신적으로 직접 제공하는 많은 수의 Resource 구현체가 있다.
5.3.1 UrlResource
UrlResource는 java.net.URL를 감싸고 일반적으로 URL로 접근할 수 있는 파일, HTTP 대상, FTP 대상 등과 같은 객체에 접근하는데 사용할 수 있다. 적절하게 표준화된 URL 접두사는 다른 타입에서 URL 타입을 가르키는데 사용는 것처럼 모든 URL은 표준화된 String로 나타낸다. 이는 파일시스템 경로에 접근하는 file:, HTTP 프로토콜로 리소스에 접근하는 http:, FTP로 리소스에 접근하는 ftp:등을 포함한다.
UrlResource는 자바 코드로 UrlResource 생성자를 사용해서 명시적으로 생성하지만 때로는 경로를 나타내는 String 아규먼트를 받아들이는 API 메서드를 호출했을 때 암묵적으로 생성될 것이다. 후자의 경우 JavaBeans PropertyEditor가 결국 어떤 타입의 Resource를 생성할 것인지를 결정할 것이다. 경로 문자열에 classpath:같은 어느정도 알려진 접두사가 있다면 해당 접두사에 적절한 Resource를 생성할 것이다. 하지만 접두사를 인식하지 못한다면 그냥 표준 URL 문자열이라고 가정하고 UrlResource을 생성할 것이다.
5.3.2 ClassPathResource
이 클래스는 클래스패스에서 얻어와야하는 리소스를 나타낸다. 이 클래스는 쓰레드 컨텍스트 클래스 로더, 주어진 클래스로더, 클래스를 로딩하려주 주어진 클래스를 모두 사용한다.
Resource 구현체는 클래스패스 리소스가 파일시스템에 존재하면 java.io.File같은 해결책을 지원하지만 jar에 존재하는 클래스패스 리소스는 지원하지 않고 파일시스템으로 확장하지도 않는다. 이를 다양한 Resource 구현체에 배치시키려면 java.net.URL같은 해결책을 항상 지원해야 한다.
ClassPathResource는 ClassPathResource 생성자를 사용해서 자바코드로 명시적으로 생성하지만 때로는 경로를 나타내는 String 아규먼트를 받아들이는 API를 호출할 때 암묵적으로 생성될 것이다. 후자의 경우 JavaBeans PropertyEditor이 문자열 경로에서 특수한 classpath: 접두사를 인식할 것이고 이 경우에 ClassPathResource를 생성할 것이다.
5.3.3 FileSystemResource
이는 java.io.File 핸들에 대한 Resource 구현체이다. 명백하게 File와 URL같은 해결책을 지원한다.
5.3.4 ServletContextResource
이는 관련된 웹 어플리케이션의 루트 경로내에서 상대 경로를 인터프리팅하는 ServletContext 리소스에 대한 Resource 구현체이다.
이는 항상 스트림 접근과 URL 접근을 지원하지만 웹 어플리케이션 아카이브가 확장되었고 리소스가 파일시스템에 물리적으로 있다면 java.io.File 접근만을 허용한다. 이처럼 파일시스템에 있거나 확장되었거나 JAR나 DB같은 다른 곳에서(생각해 볼 수 있는 곳이다.) 직접 접근했는가에 관계 없이 서블릿 컨테이너에 실제로 의존적이다.
5.3.5 InputStreamResource
주어진 InputStream에 대한 Resource 구현체이다. 이는 적용할 수 있는 특정 Resource 구현체가 없을 때만 사용할 수 있다.특히 ByteArrayResource나 파일 기잔의 어떤 Resource 구현체가 가능한 곳에서 더 선호한다.
다른 Resource 구현체와는 달리 이는 이미 오픈된 리소스에 대한 디스크립터(descriptor)이다. 그러므로 isOpen()는 true를 리턴한다. 리소스 디스크립터를 어딘가에 유지할 필요가 있거나 스트림을 여러번 읽어야 할 때는 사용하지 말아야 한다.
5.3.6 ByteArrayResource
주어진 바이트(byte) 배열에 대한 Resource 구현체이다. 이는 주어진 바이트 배열에 대한 ByteArrayInputStream를 생성한다.
일회용 InputStreamResource에 의지하지 않고 주어진 바이트 배열에서 컨텐츠를 로딩하는데 유용하다.
5.4 ResourceLoader
ResourceLoader 인터페이스는 Resource 인스턴스를 리턴(즉 로딩)할 수 있는 객체로 구현된다는 것을 의미한다.
public interface ResourceLoader {
Resource getResource(String location);
}
모든 어플리케이션 컨텍스트는 ResourceLoader 인터페이스를 구현하고 있으므로 Resource 인스턴스를 획득하는데 모든 어플리케이션 컨텍스트를 사용할 수 있다.
특정 어플리케이션 컨텍스트에서 getResource()를 호출하고 지정된 위치경로가 특정 접두사를 가지지 않았을 때 특정 어플리케이션 컨텍스트에 적절한 Resource 타입을 얻을 것이다. 예를 들어 ClassPathXmlApplicationContext 인스턴스에 대해 실행하는 다음의 코드가 있다고 해보자.
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
리턴되는 것은 ClassPathResource가 될 것이다. FileSystemXmlApplicationContext 인스턴스에 대해서 같은 메서드를 실행했다면 FileSystemResource를 얻을 것이다. WebApplicationContext에 대해서는 ServletContextResource를 얻는 등이다.
이처럼 특정 어플리케이션 컨텍스트에 적절한 방법으로 리소스를 로드할 수 있다.
반면에 classpath: 접두사를 지정함으로써 어플리케이션 컨텍스트 타입에 관계없이 ClassPathResource를 사용하도록 강제할 수도 있다.
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
비슷하게 표준 java.net.URL 접두사를 지정해서 UrlResource를 사용하도록 강제할 수 있다.
Resource template = ctx.getResource("file:/some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");
다음 표에 String을 Resource로 변환하는 전략을 요약했다.
Table 5.1. 리소스 문자열
접두사 | 예제 | 설명 |
---|---|---|
classpath: | classpath:com/myapp/config.xml | 클래스패스에서 로드한다. |
file: | file:/data/config.xml | 파일시스템에서 URL로 로드한다. [1] |
http: | http://myserver/logo.png | URL로 로드한다. |
(none) | /data/config.xml | 기반하는 ApplicationContext에 달려있다. |
[1] 하지만 다음도 봐라. Section 5.7.3, “FileSystemResource의 주의사항”. |
5.5 ResourceLoaderAware 인터페이스
ResourceLoaderAware 인터페이스는 ResourceLoader 참조와 함께 제공되기를 기대하는 객체를 식별하는 특별한 마커(marker) 인터페이스이다.
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
클래스가 ResourceLoaderAware는 구현하고 어플리케이션 컨텍스트에 배포했을 때(스프링이 관리하는 빈처럼) 어플리케이션 컨텍스트는 클래스를 ResourceLoaderAware로 인식한다. 그 다음 어플리케이션 컨텍스트는 자신을 아규먼트로 전달하면서 setResourceLoader(ResourceLoader)를 호출할 것이다. (모든 스프링의 모든 어플리케이션 컨텍스트는 ResourceLoader 인터페이스를 구현했다는 것을 기억해라.)
물론 ApplicationContext가 ResourceLoader이기 때문에 빈은 ApplicationContextAware를 구현할 수도 있고 리소스를 로드하려고 제공된 어플리케이션 컨텍스트를 직접 사용할 수도 있다. 하지만 리소스를 로드하려는 목적이라면 전용 ResourceLoader를 사용하는 것이 더 낫다. 코드는 전체 스프링 ApplicationContext 인터페이스가 아니라 유틸리티 인터페이스로 고려될 수 있는 리소스 로딩 인터페이스에 그냥 커플링 될 것이다.
스프링 2.5에서 ResourceLoaderAware 인터페이스를 구현하는 대신에 ResourceLoader의 자동연결에 의존할 수 있다. "전통적인" constructor와 byType의 자동연결 모드는 (Section 4.4.5, “협력객체의 자동연결(Autowiring)”에서 설명했듯이) 이제 생성자 아규먼트나 setter 메서드 파라미터 각각에 대해서 ResourceLoader 타입의 의존성을 제공하는 기능이 있다. 더 유연하게 하려면 (필드와 다중 파라미터 메서드를 자동연결하는 것을 포함해서) 새로운 어노테이션 기반의 자동연결 기능의 사용을 고려해봐라. 이 경우에 ResourceLoader는 필드나 생성자 메서드에 @Autowired 어노테이션이 붙어있으면 ResourceLoader 타입을 기대하는 필드나 생성자 아규먼트, 메서드 파라미터에 자동연결 될 것이다. 더 자세한 내용은 Section 4.9.2, “@Autowired”를 봐라.
덕분에 정리하는데 도움이 됐습니다. ^^ 감사합니다~
네 방문해 주셔서 감사합니다.