> For the complete documentation index, see [llms.txt](https://dodeon.gitbook.io/study/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dodeon.gitbook.io/study/keesunbaik-spring-framework-core/kessunbaik-spring-framework-core/02-resource-validation/01-resource-abstraction.md).

# 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](https://docs.oracle.com/javase/7/docs/api/java/net/URL.html)

### ApplicationContext에 따라 리소스 읽어오기

```java
@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
    * 파일을 읽고 싶을 땐 항상 `/`를 세 개로 써줘야 한다.

## 예제

```java
@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를 생략한 예제

```java
@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](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/Resource.html)

[java.net.URL](https://docs.oracle.com/javase/7/docs/api/java/net/URL.html)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dodeon.gitbook.io/study/keesunbaik-spring-framework-core/kessunbaik-spring-framework-core/02-resource-validation/01-resource-abstraction.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
