Resource 추상화

org.springframework.core.io.Resource

특징

  • java.net.URL을 추상화 한 것

    • org.springframework.core.io.Resource 클래스로 감싸서 실제 로우 레벨에 접근하는 기능을 만들었다.

  • 스프링 내부에서 많이 사용하는 인터페이스

추상화 한 이유

  • 이전에는 classpath 기준으로 리소를 읽어오는 기능이 없었다.

  • ServletContext를 기준을 상대 경로를 읽어오는 기능이 없었다.

  • 새로운 핸들러를 등록한 뒤 특별한 URL 접미사를 만들어 사용할 수는 있었지만, 구현이 복잡하고 편의성 메서드가 부족했다.

Resource 인터페이스

  • 주요 메서드

    • getInputStream()

    • exists()

      • 리소스가 항상 존재한다는 보장이 없으므로 확인하는 메서드를 정의해두었다.

    • isOpen()

    • getDescription()

    • etc...

구현체

  • UrlResource

    • java.net.URL 참고

    • http, https, ftp, file, jar 프로토콜을 지원한다.

  • ClassPathResource

    • 접두어 classpath: 를 지원한다.

  • FileSystemResource

  • ServletContextResource

    • 읽어들이는 리소스 타입이 애플리케이션 컨텍스트와 관련이 있어서 자주 사용한다.

    • 웹 애플리케이션 루트에서 상대 경로로 리소스를 찾는다.

  • etc...

Reference

java.net.URL

ApplicationContext에 따라 리소스 읽어오기

@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    ResourceLoader resourceLoader;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 클래스 패스 기준으로 문자열에 해당하는 리소스를 찾아서 빈 설정 파일로 활용한다.
        var ctx1 = new ClassPathXmlApplicationContext("config.xml");

        // 파일 시스템 기준으로 문자열에 해당하는 리소스를 찾아서 빈 설정 파일로 활용한다.
        var ctx2 = new FileSystemXmlApplicationContext("config.xml");

        // 애플리케이션 루트 기준으로 문자열에 해당하는 리소스를 찾아서 빈 설정 파일로 활용한다.
        var ctx3 = new WebApplicationContext("config.xml");
    }
}
  • Resource의 타입은 location 문자열과 ApplicationContext 타입에 따라 결정된다.

  • 만약 FileSystemXmlApplicationContext로 불러왔다면, FileSystemResource로 리졸빙 한다.

    • FileSystemXmlApplicationContext -> FileSystemResource

    • ClassPathXmlApplicationContext -> ClassPathResource

    • WebApplicationContext -> ServletContextResource

ApplicationContext에 상관없이 리소스 읽어오기

  • ApplicationContext의 타입에 상관없이 리소스 타입을 강제하려면 java.net.URL 접두어(+ classpath:)중 하나를 사용할 수 있다.

    • 리소스가 어디서 오는지 명시적으로 나타낼 수 있으므로 이 방식을 더 추천한다.

    • classpath:me/whiteship/config.xml -> ClassPathResource

    • file:///some/resource/path/config.xml -> FileSystemResource

      • 파일을 읽고 싶을 땐 항상 /를 세 개로 써줘야 한다.

예제

@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    ResourceLoader resourceLoader;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 리소스 로더의 타입. 웹 애플리케이션 리소스로 나와야 한다.
        System.out.println(resourceLoader.getClass());

        Resource resource = resourceLoader.getResource("classpath:test.txt");
        // 리소스의 타입. 클래스 패스 리소스가 나와야 한다.
        System.out.println(resource.getClass());

        System.out.println(resource.exists());
        System.out.println(resource.getDescription());
        System.out.println(Files.readString(Path.of(resource.getURI())));
    }
}
class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
class org.springframework.core.io.ClassPathResource

true
class path resource [test.txt]
hello spring

classpath를 생략한 예제

@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    ResourceLoader resourceLoader;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 리소스 로더의 타입. 웹 애플리케이션 리소스로 나와야 한다.
        System.out.println(resourceLoader.getClass());

        // classpath를 뺐을 때는 리소스 로더가 웹 애플리케이션 컨텍스트이므로 서블릿으로 나와야 한다.
        Resource resource = resourceLoader.getResource("test.txt");
        System.out.println(resource.getClass());

        // 따라서 이 리소스를 웹 애플리케이션 루트부터 찾게 된다.
        // 하지만 스프링 부트에서 톰캣은 컨텍스트 패스가 기본적으로 지정되어있지 않다.
        // 결국 리소스를 찾을 수 없다.
        System.out.println(resource.exists());
        System.out.println(resource.getDescription());
        System.out.println(Files.readString(Path.of(resource.getURI())));
    }
}
class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
class org.springframework.web.context.support.ServletContextResource
false
ServletContext resource [/test.txt]

Caused by: java.io.FileNotFoundException: ServletContext resource [/test.txt] cannot be resolved to URL because it does not exist
  • 예상한대로 리소스의 타입이 ServletContextResource가 되었으며 리소스를 찾을 수 없다는 에러가 발생했다.

  • 따라서 스프링 부트 사용 시 classpath를 꼭 명시해두자.

Reference

Interface Resource

java.net.URL

Last updated