Spring/Spring Core

[Spring] Resource 추상화

TheWing 2020. 11. 4. 03:44

Resource 추상화

  • Spring Boot에서 resource를 설정할때 classpath로 mapping시 prefix 사용을 선호한다.
  • java.net.URL 을 Resource로 감싸서 추상화 하는 기법을 기술한다

특징

  • Spring은 java.net.URLorg.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 가 있다

Resource 구현체

  • 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