Converter
스프링 타입 컨버터(converter)란, 서로 다른 타입 간의 변환을 쉽게 처리할 수 있도록 돕는 도구이다.
HTTP 요청 파라미터는 모두 문자로 처리된다. 때문에 요청 파리미터를 자바에서 다른 타입으로 변환해서 사용하고 싶으면 원하는 타입으로 변환하는 과정을 거쳐야한다. 실제로, 스프링 MVC가 제공하는 @RequestParam을 사용해보면, 스프링이 자동으로 타입을 변환해주는데 어떻게 이것이 가능한걸까? 이때, 사용하는 것이 바로 스프링 타입 컨버터이다.
컨버터 인터페이스
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
스프링은 확장 가능한 컨버터 인터페이스를 제공한다. 개발자는 스프링에 추가적인 타입 변환이 필요하면, 해당 컨버터 인터페이스를 구현해서 등록하면 된다. 코드에서 알 수 있듯이 컨버터의 convert() 메서드는 S 타입을 T 타입으로 변환해주는 역할을 한다. 등록하는 방법도 간단하다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToLocalDateConverter());
}
}
위의 코드처럼 스프링 설정 클래스에 커스텀 타입 컨버터를 등록하면 된다. Formatter에 대한 자세한 설명은 뒤에서 기술하겠다. 참고로 위의 코드에서 FormatterRegistry란, 스프링에서 제공하는 인터페이스로, 컨버터와 포맷터를 모두를 등록할 수 있는 인터페이스 저장소라 생각하면 된다. 또한 **addFormatters**는 비록 명칭이 Formatter이지만, 실제로는 포맷터(Formatter)와 컨버터(Converter)를 모두 지원한다.
ConversionService
스프링은 개별 컨버터를 모아두고 그것들을 묶어서 편리하게 사용할 수 있는 기능을 제공하는데, 그것이 바로 컨버전 서비스(ConversionService)이다.
// 등록 예시
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToIntegerConverter());
conversionService.addConverter(new IntegerToStringConverter());
conversionService.addConverter(new StringToIpPortConverter());
conversionService.addConverter(new IpPortToStringConverter());
// 사용 예시
String number = conversionService.convert(10, String.class);
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class)
위의 코드를 살펴보면, ConversionService에 우리가 만든 컨버터를 등록하면, 후에 사용할 때는 구현체에 의존하지 않고 인터페이스인 ConversionService에만 의존하게 된다. 이는 구현체에 의존하지 않고 추상체에 의존하도록 해야한다는 ISP(인터페이스 분리 원칙)에 입각한 것이라 볼 수 있다.
스프링이 제공하는 ConversionService
스프링은 내부에서 ConversionService를 제공한다. 때문에 우리는 WebMvcConfigurer가 제공하는 addFormatters() 메서드를 사용하여 추가하고 싶은 컨버터만 등록하면, 스프링이 자체적으로 ConversionService에 컨버터를 추가해준다. 그럼 다시 처음으로 돌아와서, @RequestParam은 어떻게 타입을 자동으로 변환할까? @RequestParam을 처리하는 ArgumentResolver인 RequestParamMethodArgumentResolver에서 ConversionService를 사용해서 자동으로 타입을 변환해주는 것이다.
주의점
메세지 컨버터(HTTP Message Converter)에는 ConversionService가 적용되지 않음에 주의하자! HTTP 메시지 컨버터는 HTTP 요청과 응답 본문을 객체로 변환하는 데 사용된다. 즉, JSON 또는 XML 포맷의 데이터를 자바 객체로 변환하거나, 자바 객체를 HTTP 응답으로 변환할 때 사용되는 것이다. 쉽게 말해, 메시지 컨버터는 포맷 변환에 집중하고, ConversionService는 타입 변환에 집중하기 때문에 그 목적이 서로 다르다. 때문에 HTTP 메시지 컨버터를 사용하는 @RequestBody는 ConversionService를 사용하지 않는다. ConversionService는 @RequestParam, @ModelAttribute, @PathVariable, 뷰 템플릿 등에서만 사용할 수 있다.
Formatter
앞서 살펴본 Converter는 범용 타입 변환 기능을 제공한다. 이에 반해 Formatter는 주로 문자열(String)과 객체 간 변환에 주로 사용되며 Converter의 특수한 버전이라 생각하면 된다.
포맷터 인터페이스
public interface Formatter<T> extends Printer<T>, Parser<T> {}
@FunctionalInterface
public interface Printer<T> {
String print(T object, Locale locale);
}
@FunctionalInterface
public interface Parser<T> {
T parse(String text, Locale locale) throws ParseException;
}
포맷터 인터페이스는 2가지 함수를 제공해준다. 먼저 print() 메서드는 입력값으로 들어온 T 타입을 Locale 정보를 토대로 문자열로 바꿔주는 기능을 한다. 반대로 parse() 메서드는 문자열 타입을 Locale 정보를 이용해서 우리가 원하는 T 타입으로 변환해준다. 이처럼 Formatter는 문자열의 타입 변환에 특화된 것이라 보면 된다.
포맷터를 지원하는 ConversionService
일반적인 ConversionService는 컨버터만 등록할 수 있고, 포맷터는 등록할 수 없다. 때문에 포맷터를 사용하기 위해서는 포맷터를 지원하는 확장된 ConversionService를 사용해야 한다. 스프링은 대표적으로 FormattingConversionService과 같이 포맷터를 지원하는 컨버전 서비스를 제공해준다. DefaultFormattingConversionService는 이를 상속받아 기본적인 통화, 숫자 관련 몇가지 기본 포맷터도 함께 추가해서 제공한다. 즉, 상속 구조를 간략히 표현하면 아래 사진과 같다. (생략된 클래스 존재)

상속 구조를 위로 타고 올라가보면, FormattingConversionService는 ConvertRegistry인터페이스와 FormatterRegistry의 구현체이다. 그리고 DefaultFormattingConversionService는 FormattingConversionService를 상속받은 자식 객체이기 때문에 Formatter와 Converter 모두 지원하는 ConversionService인 것이다. 추가로 스프링 부트는 DefaultFormattingConversionService를 상속받은 WevConversionService를 ConversionService로 내부에서 사용한다. 따라서, 스프링 부트 환경에서는 기본적으로 컨버터와 포맷터를 모두 지원하는 ConversionService가 자동으로 활성화된다.
스프링이 제공하는 기본 포맷터
- @NumberFormat
이런식으로 패턴을 지정해주면 된다.@NumberFormat(pattern = "###,###") // 천 단위 구분 기호(쉼표)를 추가 private Integer number;
- 숫자 형식을 지정하는 포맷터를 사용하도록 하는 어노테이션
- @DateTimeFormat
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime localDateTime;
- 날짜 관련 형식을 지정하는 포맷터를 사용하도록 하는 어노테이션
Multipart
파일을 업로드하기 위해서는 문자가 아닌, 바이너리 데이터를 전송해야 한다. 이때, 만약 문자열인 이름과 숫자인 나이 그리고 파일을 함께 전송한다면, 문자와 바이너리를 동시에 전송해야 하는 문제가 발생하게 된다. 이를 해결하기 위해 HTTP는 mutipart/form-data라는 전송 방식을 제공한다.

해당 방식을 사용하기 위해서는 Form 태그에 별도의 enctype="multipart/form-data"을 지정해야만 서로 다른 종류의 데이터를 함께 전송할 수 있다. HTTP 메세지를 살펴보면, 각각의 전송 항목에 대해 항목별 헤더가 추가되어 있고 구분되어 있는 것을 볼 수 있다. 이처럼 multipart는 각각의 항목을 구분해서, 한번에 전송하는 방식이다. 파일의 경우, 파일 이름과 Content-Type이 추가되고 바이너리 데이터가 전송된다.
사용 옵션
spring.servlet.multipart.max-file-size=1MB // 파일 하나의 최대 사이즈 제한
spring.servlet.multipart.max-request-size=10MB // 모든 파일들의 사이즈 합 제한
위 옵션을 사용하여, 업로드 사이즈를 제한할 수 있다. 만약 사이즈를 넘으면 SizeLimitExceededException 예외가 발생한다. 만약, Mulitpart를 사용하고 싶지 않다면, spring.servlet.multipart.enabled=false를 아용하여 멀티파트와 관련된 처리를 하지 않도록 처리할 수 있다. 기본 설정은 true이다.
동작 과정
MultiPart 요청이 들어오면, 스프링은 디스패처 서블릿에서 MultipartResolver를 실행한다. 이는 서블릿 컨테이너가 전달하는 일반적인 HttpServletRequest를 MultipartHttpServletRequest로 변환해서 반환한다. MultipartHttpServletRequest는 HttpServletRequest의 자식 인터페이스로, 멀티파트와 관련된 추가 기능을 제공한다. 스프링은 이를 구현한 StandardMultipartHttpServletRequest구현체를 제공한다. 하지만 이후 설명할 MutipartFile이라는 것을 사용하는 것이 더 편리하기 때문에 잘 사용하지는 않는다.
스프링과 파일 업로드
스프링은 MultipartFile이라는 인터페이스로 멀티파트 파일을 매우 편리하게 지원한다.
@PostMapping("/upload")
public String saveFile(@RequestParam String name, @RequestParam MultipartFile file, HttpServletRequest request) throws IOException {
log.info("request={}", request);
log.info("itemName={}", name);
log.info("multipartFile={}", file);
if (!file.isEmpty()) {
String fullPath = fileDir + file.getOriginalFilename();
log.info("파일 저장 fullPath={}", fullPath);
file.transferTo(new File(fullPath)); // 파일 저장
}
return "upload-form";
}
}
@RequestParam MultipartFile file코드처럼 간단하게, 업로드하는 HTML Form의 name에 맞추어 @RequestParam을 적용하면 된다. 추가로 @ModelAttribute 에서도 MultipartFile 을 동일하게 사용할 수 있다.
'Spring' 카테고리의 다른 글
스프링 핵심 원리 고급편 섹션2 (0) | 2025.03.17 |
---|---|
빈으로 등록된 필터가 WebSecurity의 ignoring에 의해 무시되지 않는 이유 (0) | 2025.03.17 |
스프링 MVC 2편 - 섹션 7~9 (0) | 2025.03.17 |
스프링 MVC 2편 - 섹션 4~5 (0) | 2025.03.17 |
스프링 MVC 1편 - 섹션 4~7 (0) | 2025.03.17 |