티스토리 뷰

Spring

[Spring] @RequestPart의 동작 방식을 통해 스프링의 MultipartFile 파일 업로드 처리 방식 이해하기

망나니개발자 2025. 8. 5. 10:00
반응형

 



 

1. @RequestPart의 동작 방식을 통해 스프링의 MultipartFile 파일 업로드 처리 방식 이해하기


[ MultipartFile 파일 업로드 활성화하기 ]

일반적으로 스프링 애플리케이션으로 파일을 업로드하기 위해서는 다음의 2가지 옵션이 사용된다. 먼저 enabled 옵션을 통해 멀티파트를 통한 파일 요청을 활성화하고, 서비스의 안정성을 위해 업로드 가능한 최대 파일의 크기를 지정해줄 필요가 있다.

spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.resolve-lazily=true

 

 

여기에 추가적으로 resolve-lazily 옵션 역시 활용하면 좋다. 해당 옵션을 true로 설정되면 요청의 파싱이 실제로 필요한 시점까지 지연시켜 주는데, 이를 통해 불필요한 리소스 사용을 방지하여 최적화를 도와준다. 해당 옵션의 기본값은 false이기에, 별도의 설정이 없다면 요청이 우선적으로 파싱되게 된다. 따라서 리소스 최적화를 위해 해당 옵션을 활성화해주도록 하자.

 

 

[ @RequestPart의 동작 방식과 MaxUploadSizeExceededException 에러가 핸들링되지 않은 이유 ]

@RequestPart는 Spring MVC에서 HTTP 요청의 multipart/form-data 형식으로 전송된 파일을 처리하기 위해 사용된다. 해당 애노테이션이 붙은 메소드 파라미터는 MultiPartFile 타입의 파라미터를 전달받게 된다. MultipartFile은 스프링 프레임워크에서 파일 업로드를 다룰 때 사용하는 인터페이스로, 클라이언트가 HTTP multipart 요청을 통해 업로드한 파일을 서버에서 편리하게 처리할 수 있게 도와준다. 이를 통해 파일의 이름, 크기, 타입 등의 메타데이터와 함께 파일의 내용을 직접적으로 다룰 수 있다.

@RestController
@RequestMapping("/api/images")
class ImageController(
    private val uploadImageService: UploadImageService,
) {

    @Operation(summary = "이미지 업로드 API")
    @PostMapping("/upload")
    fun uploadImage(
        @RequestPart(value = "image") imageFile: MultipartFile,
    ): ResponseEntity<Boolean> {
        uploadImageService.execute(image)
        return ResponseEntity.ok(true)
    }
}

 

 

해당 애노테이션은 @RequestParam 및 @RequestBody와 동일하게, Argument Resolver의 구현체 중 하나인 RequestPartMethodArgumentResolver를 통해 처리된다. RequestPartMethodArgumentResolver 내부에서는 MultipartResolutionDelegate 클래스로 실제 MultipartFile을 구하는 로직을 위임하고 있다.

 

 

MultipartResolutionDelegate 내부에서는 MultipartHttpServletRequest를 통해 실제 파일을 처리하는데, MultipartHttpServletRequest는 HttpServletRequest를 확장한 인터페이스로, multipart/form-data 요청을 처리하기 위한 메소드를 제공한다. 해당 메서드가 참조하는 객체를 보면 ApplicationPart라는 객체가 존재하고, 해당 객체는 우리가 업로드했던 파일을 서버 내부의 임의의 디스크 공간에 저장해두고, 해당 경로를 참조하여 파일을 처리함을 알 수 있다. 참고로 해당 tmp 파일을 복사해서, 우리의 원본 파일에 맞게 확장자를 변경한 후 열어보면 완전히 동일함을 확인할 수 있다.

 

 

그렇다면 해당 파일은 어느 시점에 디스크에 기록된 것일까? 먼저 살펴볼 곳은 스프링 프레임워크의 가장 앞단에서 요청을 받아 처리하는 DispatcherServlet이다. DispatcherServlet은 HTTP 요청을 처리하는 중앙 서블릿으로, 클라이언트의 요청을 적절한 컨트롤러로 전달하고, 컨트롤러의 응답을 클라이언트에게 반환하는 역할을 한다. 따라서 현재의 요청이 multipart/form-data 형식의 요청이라면, DispatcherServlet의 checkMultipart 메소드에서 이를 검사하고 처리함을 확인할 수 있다.

 

 

Multipart 처리는 MultipartResolver 인터페이스를 구현한 StandardServletMultipartResolver에 의해 수행된다. 해당 구현체 내부에서는 content-type이 multipart로 시작하는 경우 multipart 요청으로 인식하며, 이후 HttpServletRequest를 MultipartHttpServletRequest로 변환하여 파일 업로드를 처리한다. 그리고 이때 전달되는 resolveLazily 파라미터가 위에서 설정한 resolv-lazily 프로퍼티로 설정되는 값인 것이다.

 

 

StandardMultipartHttpServletRequest 내부에서는 lazyParsing이라는 옵션에 따라 요청 파싱을 지연하는데, 이 옵션이 위에서 살펴본 resolve-lazily 설정으로 할당되는 값이다. 해당 옵션의 기본값은 false이기에, 별도의 설정이 없다면 요청이 parseRequest 메서드를 통해 즉시 파싱된다.

참고로 아래의 parseRequest 메서드에서 보이는 HttpServletRequest 객체는 자바 표준 서블릿 인터페이스일 뿐이고, 실제 동작은 각각 사용중인 웹서버에 따라 달라질 것이다. 현재 분석중인 서버는 WAS로 톰캣을 사용중이기 때문에, 이후에 request.getParts()로 이어지는 부분은 톰캣에 제한된 이야기임을 알아야 한다.

 

 

이제 getParts()를 따라가보면, 내부에서 이때 멀티파트에 대한 파싱이 일어남을 확인할 수 있다.

 

 

그리고 실제 파싱을 처리하는 parseParts 메서드 내부를 살펴보면, 입력으로 받은 파일의 내용을 임의의 서버 디스크 공간에 저장하기 위한 사전 작업을 진행하고 있음을 확인할 수 있다.

 

 

그리고 실제 파일 업로드를 위한 몇 가지 설정을 진행한 후에, 디스크에 파일 정보를 기록하고, 최종적으로 ApplicationPart 객체로 반환함을 확인할 수 있다.

 

 

 

해당 객체에는 파일이 저장된 경로와 함께 파일 정보를 담은 객체를 다음과 같이 포함하고 있으며, 이는 위에서 살펴봤던 MultipartFile 객체가 참조하는 ApplicationPart 객체인 것이다.

 

 

결국 스프링 기반의 톰캣 애플리케이션에서는, Multipart 파일 처리를 위해 요청에 있는 파일 정보를 먼저 디스크에 기록해둔다. 그리고 해당 내용을 참조하는 MultipartFile 객체를 파라미터로 제공하여 파일에 대한 처리가 이루어지도록 구성되어 있다고 볼 수 있다.

 

 

 

 

 

 

 

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함