Resource 추상화
- Spring Boot에서 resource를 설정할때 classpath로 mapping시 prefix 사용을 선호한다.
- java.net.URL 을 Resource로 감싸서 추상화 하는 기법을 기술한다
특징
- Spring은
java.net.URL
을org.springframework.core.io.Resource
로 감싸서 추상화한 클래스이다. - 스프링 내부에서 많이 사용하는 인터페이스이다
추상화 한 이유
java.net.URL
이 기본적으로 여러 prefix를 지원하는 프로토콜은 http,https, ftp, file, jar 이지만 classpath 기준으로 리소스를 읽어오는 기능이 없다.- ServletContext를 기준으로 상대 경로로 읽어오는 기능이 없다
- 새로운 핸들러를 등록하여 특별한 URL 접미사를 만들어 사용할 수는 있지만 구현이 복잡하고 편의성 메소드가 부족하다
Spring 내부 Resource 사용 예제
- Resource 인터페이스는 spring 내부에서 자주 사용이 된다
ClassPathXmlApplicationContext
ApplicationContext classPathXml = new ClassPathXmlApplicationContext("configLocation.xml");
Resource resource = resourceLoader.getResource("classpath:test.txt");
- configLocation.xml 을 바로 리소스로 추상화되어 문자열이 변환이 된다.
- configLocation.xml이 getResource의 classpath:text.txt로 해당되어 사용된다
FileSystemXmlApplicationContext
ApplicationContext fileSystemXml = new FileSystemXmlApplicationContext("configLocation.xml");
Resource resource = resourceLoader.getResource("classpath:test.txt");
- file시스템 경로를 기준으로 매개변수로 들어오는 configLocation.xml 문자열에 해당하는 리소스를 찾아서 빈 설정 파일로 사용한다.
- 결국 서로 다른 Resource 구현체를 사용한다
주요 메소드
- exists()
- isOpen()
- getInputStream()
- getDescription(): 전체 경로를 포함한 파일 이름 or 실제 URL
boolean exists();
default boolean isOpen() { return false; }
String getDescription();
Resource의 구현체
Resource
의 주요 구현체로는UrlResource
,ClassPathResource
,FileSystemResource
,ServletContextResource
가 있다
- UrlResource
- java.net.URL 참고, 지원하는 프로포콜 http, https, ftp, file, jar
- ClassPathResource
- 접두어 classpath: 지원
- FileSystemResource
- ServletContextResource
- 웹 어플리케이션 루트에서 상대 경로로 리소스를 찾는다.
- 가장 많이 사용된다는 특징이 있다.
- 이유는 읽어오는 Resource 타입이 location 문자열과 ApplicationContext의 타입에 따라 결정된다
- ClassPathXmlApplicationContext → ClassPathResource
- FileSystemXmlApplicationContext → FileSystemResource
- WebApplicationContext → ServletContextResource
- 위와 같이 resolve된다
- ApplicationContext의 타입에 상관없이 리소스 타입을 강제하려면 java.net.URL 접두어(+ classpath:) 중 하나를 사용할 수 있다.(추천 : 명시적으로 사용하기 때문)
- classpath:me/thewing/config.xml → ClassPathResource
- file:///some/resource/path/config.xml → FileSystemResource
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext resourceLoader;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationContext Class = " + resourceLoader.getClass());
Resource resource = resourceLoader.getResource("classpath:test.txt");
System.out.println("Resource Class = "+ resource.getClass());
System.out.println("resource.exists=" + resource.exists());
System.out.println("resource.getDescription = " + resource.getDescription());
System.out.println("Files.readString(Path.of(resource.getURI())) = " + Files.readString(Path.of(resource.getURI())));
}
}
- 결과
ApplicationContext = class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
Resource = class org.springframework.core.io.ClassPathResource
resource.exists=true
resource.getDescription = class path resource [test.txt]
Files.readString(Path.of(resource.getURI())) = hello spring // < 이건 test.txt의 내부 내용이다
- 결국에는
WebServerApplicationContext
이다 - classpath prefix를 사용해서
ClassPathResource
가 나온다
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext resourceLoader;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationContext Class = " + resourceLoader.getClass());
Resource resource = resourceLoader.getResource("test.txt");
System.out.println("Resource Class = "+ resource.getClass());
System.out.println("resource.exists=" + resource.exists());
System.out.println("resource.getDescription = " + resource.getDescription());
System.out.println("Files.readString(Path.of(resource.getURI())) = " + Files.readString(Path.of(resource.getURI())));
}
}
- 결과
ApplicationContext = class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
Resource = class org.springframework.web.context.support.ServletContextResource
resource.exists=false
resource.getDescription = ServletContext resource [/test.txt]
2020-11-04 03:28:41.745 INFO 17240 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-11-04 03:28:41.795 ERROR 17240 --- [ main] o.s.boot.SpringApplication : Application run failed
java.lang.IllegalStateException: Failed to execute ApplicationRunner
- classpath를 지정하지 않으면 ServletContextResource 로 resolve된다
- 톰캣 내장에는 classpath가 지정되어있지 않아서 예외가 발생하게 된다
Resource 타입 강제 지정
"classpath:com/TheWing/config.xml" → ClassPathResource
"file:///some/resource/path/config.xml" → FileSystemResource
- file에는 /// 반드시 3개 필요하다
결론
- Spring boot 기반으로 개발 할때는 resource를 지정할때 classpath: prefix를 하는 것이 좋다.
- classpath를 지정하지 않으면 ServletContextResource 로 resolve된다
References
- 백기선님 인프런 - 스프링 프레임워크 핵심 기술 강의를 보고 정리한 내용입니다
'Spring > Spring Core' 카테고리의 다른 글
[Spring] AOP(Aspect-Oriented Programming) (0) | 2020.11.06 |
---|---|
[Spring] SpEL(Spring Expression Language) (0) | 2020.11.05 |
[Spring] 데이터 바인딩 추상화 - Converter, Formatter (0) | 2020.11.05 |
[Spring] 데이터 바인딩 추상화 - PropertyEditor (0) | 2020.11.05 |
[Spring] Validation 추상화 (0) | 2020.11.04 |