View를 응답하는 것이 아닌, Rest API(HTTP API)로 JSON, TEXT, XML 등의 데이터를 응답 Message Body에 직접 입력하는 경우 HttpMessageConverter를 사용한다.
1. SSR → @Controller + View Template → 서버 측에서 화면을 동적으로 그린다.
2. CSR → @RestController + Data → 클라이언트 측에서 화면을 동적으로 그린다.
3. 실제로는 두가지 기술이 함께 사용되는 경우가 많다.
HttpMessageConverter 동작 순서
- HTTP 응답 메세지 Body에 데이터를 직접 입력 후 반환한다.
- 요청 Accept Header + Controller 반환 타입
- ViewResolver가 아닌 HttpMessageConverter가 동작한다.
HttpMessageConverter가 적용되는 경우
- HTTP 요청 : @RequestBody, HttpEntity<>, RequestEntity<>
- HTTP 응답 : @ResponseBody, HttpEntity<>, ResponseEntity<>
- HttpMessageConverter는 요청과 응답 모두 사용된다.
💁 @RestController = @Controller + @ResponseBody
HttpMessageConverter 내부구조
canRead(), canWrite() 메서드로 Class, MediaType 지원여부를 체크한다.
read(), write() 메서드로 HttpMessage 를 읽고 쓴다.
T read(
Class<? extends T> clazz,
HttpInputMessage inputMessage
) throws IOException, HttpMessageNotReadableException;
void write(
T t,
@Nullable MediaType contentType,
HttpOutputMessage outputMessage
) throws IOException, HttpMessageNotWritableException;
양방향 Request ↔ Response 으로 동작한다.
우선 순위
Spring은 다양한 HttpMessageConverter를 제공하고 있고 우선순위가 있다. 대상 Class와 MediaType을 체크해서 어떤 Converter를 사용할지 결정한다.
대표적인 HttpMessageConverter
1. ByteArrayHttpMessageConverter
- byte[] Data를 처리한다.
- 대상 : byte[]
- MediaType : */*
- 반환 : application/octet-stream
@Slf4j
@RestController // @Controller + @ResponseBody
public class MessageConverterController {
// 요청 헤더 -> content-type: */*
@PostMapping("/byte-array")
// byte[] -> produces = "application/octext-stream"
public byte[] byteArray(@RequestBody byte[] data) {
log.info("byte-array logic");
return data; // response
}
}
2. StringHttpMessageConverter
- String Data를 처리한다.
- 대상 : String
- MediaType : */*
- 반환 : text/plain
// 요청 헤더 -> content-type: text/plain
@PostMapping("/string")
// String -> produces = "text/plain"
public String string(@RequestBody String data) {
log.info("string logic");
return data;
}
3. MappingJackson2HttpMessageConverter
- JSON Data를 처리한다.
- 대상 : Object, HashMap
- MediaType : application/json
- 반환 : application/json
// 요청 헤더 content-type: application/json
@PostMapping("/json")
// Data -> produces = "application/json"
public Data json(@RequestBody Data data) {
log.info("json logic");
return data;
}
4. 기타
- 이외에도 기본적으로 제공되는 다양한 MessageConverter가 존재한다.
- 대부분의 경우 위 세가지로 해결이 된다.
동작 순서와 예시
요청 데이터 읽기
1. 요청
2. Controller에서 @RequestBody 혹은 HttpEntity<>로 파라미터 바인딩한다.
@RestController
public class ExampleController {
// consumes
@PostMapping(value = "/example", consumes = "application/json")
public String example(@RequestBody RequestDto dto) {
return dto;
}
}
3. MessageConverter는 canRead() 메서드로 읽기가능 여부를 조회한다.
- 대상 클래스가byte[], String, Object 인지 여부 확인
- 요청 헤더의 Content-Type Media Type 지원여부 확인(consumes)
4. read() 메서드를 호출하여 Object를 생성한다.
T read(Class<? extends T> clazz, HttpInputMessage inputMessage
) throws IOException, HttpMessageNotReadableException;
응답 데이터 쓰기
1. Controller에서 @ResponseBody 혹은 HttpEntity<>로 응답이 반환된다.
@RestController // @Controller + @ResponseBody
public class ExampleController {
// produces
@PostMapping(value = "/example", produces = "application/json")
public ResponseDto example(@RequestBody RequestDto dto) {
ResponseDto responseDto = service.example(dto);
return responseDto;
}
}
2. MessageConverter는 canWrite() 메서드로 사용가능 여부를 조회한다.
- 반환 클래스가 byte[], String, Object 인지 여부 확인
- 요청 헤더 Accept 의 Media Type 지원여부 확인(produces)
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
3. write() 메서드를 호출하여 HTTP Response Message Body에 데이터를 입력한다.
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage
) throws IOException, HttpMessageNotWritableException;
Controller에 produces 속성을 따로 설정하지 않으면 요청 헤더의 Accept가 Default 이다.
예시
예시1 (성공)
- content-type : applicatin/json
- @RequestBody : String

@PostMapping("/json-to-text")
// String -> produces = "text/plain"
public String jsonToText(@RequestBody String data) {
// logic
return data;
}
1. ByteArrayHttpMessageConverter 의 canRead() 메서드 호출 : false
2. StringHttpMessageConverter 의canRead() 메서드 호출 : true
3. 우선순위에 밀려서 MappingJackson2HttpMessageConverter 는 동작하지 않는다.
예시2 (성공)
- content-type : applicatin/json
- @RequestBody : Object

@Data
public class DataDto {
private final String key;
}
@PostMapping("/json-to-json")
// Object -> produces = "application/json"
public DataDto jsonToJson(@RequestBody DataDto dto) {
// logic
return dto;
}
1. ByteArrayHttpMessageConverter 의 canRead() 메서드 호출 : false
2. StringHttpMessageConverter 의 canRead() 메서드 호출 : false
3. MappingJackson2HttpMessageConverter 의 canRead() 메서드를 호출 : true
예시3 (실패)
- content-type : text/plain
- @RequestBody : Object

// "{\"key\": \"value\"}"
@PostMapping("/text-to-json")
// Object -> produces = "application/json"
public DataDto textToJson(@RequestBody DataDto dto) {
// logic
return dto;
}
1. ByteArrayHttpMessageConverter 의 canRead() 메서드 호출 : false
2. StringHttpMessageConverter 의 canRead() 메서드 호출 : false
3. MappingJackson2HttpMessageConverter 의 canRead() 메서드 호출 : false
모든 MessageConverter가 동작하지 못한다.
RequestMappingHandlerAdapter
Spring MVC에서 HTTP 요청을 컨트롤러 메서드에 매핑하고 실행하는 핵심 구성 요소로, 클라이언트 요청을 적절한 컨트롤러 메서드와 연결한 후 이 메서드를 호출하여 결과를 반환하는 역할을 수행한다.
Spring MVC 구조
- 요청 데이터가 변환이된다.(HttpMessageConverter)
- 요청이 Controller에 전달되는 HandlerAdapter와 Handler 사이에서 어떤 일이 일어난다!
RequestMappingHandlerAdapter
- @RequestMapping 을 처리하는 HandlerAdapter의 구현체
- @PostMapping , @GetMapping, @PutMapping, @PatchMapping, @DeleteMapping 등은 모두 @RequestMapping의 일종이다.
ArgumentResolver
- RequestMappingHandlerAdapter는 ArgumentResolver를 호출하여 Controller가 필요한 다양한 파라미터의 값을 생성한다.
- HttpServletRequest, Model, HttpEntity,@ModelAttribute, @RequestBody, @RequestParam 등 다양한 파라미터 바인딩을 할 수 있는 이유이다.
- ArgumentResolver를 통하여 값이 준비되면 해당 값을 가지고 실제 Controller를 호출한다.
Argument, 매개변수, 파라미터는 모두 같은말이다.
ArgumentResolver
Spring MVC에서 컨트롤러 메서드의 파라미터를 자동으로 바인딩하는 역할을 하는 인터페이스로 요청이 컨트롤러 메서드에 전달될 때 각 파라미터를 적절한 객체로 변환하여 주입하는 것을 담당한다.
ArgumentResolver 종류
Spring은 다양한 Argument Resolver들을 기본적으로 제공한다.
1. RequestBodyArgumentResolver(@RequestBody)
2. RequestHeaderArgumentResolver(@RequestHeader)
HandlerMethodArgumentResolver
ArgumentResolver의 실제 이름
- 인터페이스로 구성되어 있다.
- implements 하여 커스텀하게 파라미터를 만들 수 있다.(확장)
supportsParameter(MethodParameter parameter);
- 컨트롤러가 필요로하는 메서드의 파라미터를 지원하는지 여부를 검사한다.
지원한다면 resolveArgument() 메서드를 통해 Object(객체)로 만들어준다.
- 만들어진 Object(객체)가 Controller 호출시 메서드의 파라미터로 전달된다.
supportsParameter() 를 사용하는 다양한 ArgumentResolver 구현체
공식문서
사용 가능한 파라미터 목록 https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/arguments.html
Method Arguments :: Spring Framework
JDK 8’s java.util.Optional is supported as a method argument in combination with annotations that have a required attribute (for example, @RequestParam, @RequestHeader, and others) and is equivalent to required=false.
docs.spring.io
ReturnValueHandler
Spring MVC에서 컨트롤러 메서드가 반환하는 값을 처리하여 HTTP 응답에 맞게 변환하는 역할을 하는 인터페이스로 컨트롤러 메서드가 실행된 후 그 반환값을 HTTP 응답의 본문에 적절히 담아 전송할 수 있도록 도와준다.
ReturnValueHandler
1. ModelAndView, @ResponseBody, HttpEntity<> 등이 있으면 응답에 필요한 값으로 변환
- Controller에서 String으로 ViewName을 반환하여도 View가 동작하는 이유
2. Spring은 다양한 ReturnValueHandler를 기본적으로 제공한다.
ModelAndViewMethodReturnValueHandler(ModelAndView 객체 반환)
HttpEntityMethodProcessor(HttpEntity 객체 반환)
HandlerMethodReturnValueHandler
- ReturnValueHandler의 실제 이름
- 인터페이스로 구성되어 있다. 즉, 확장이 가능하다.
- 다양한 ReturnValueHandler 구현체
HttpMessageConverter 구조
HttpMessageConverter를 주로 사용하는 어노테이션 @RequestBody, @ResponseBody
RequestMappingHandlerAdapter 구조
HttpMessageConverter
요청시에는 Argument Resolver가 사용하는것이다.
응답시에는 ReturnValueHandler가 사용한다.
대표적인 ArgumentResolver, ReturnValueHandler
1. RequestResponseBodyMethodProcessor
@RequestBody, @ResponseBody

클래스 다이어그램

HandlerMethodArgumentResolver 상속
HandlerMethodReturnValueHandler 상속
- @RequestBody, @ResponseBody 두가지 모두 처리하기 위해 둘다 상속
2. HttpEntityMethodProcessor
HttpEntity<>

클래스 다이어그램

HandlerMethodArgumentResolver 상속
HandlerMethodReturnValueHandler 상속
- HttpEntity<> 를 상속받은 RequestEntity<> , ResponseEntity<> 모두 처리
HttpEntityMethodProcessor.supportParameter()

HttpEntity<> 혹은 HttpEntity를 상속받은 객체 여부 확인
HttpEntityMethodProcessor.resolveArgument() : 실제 Object로 만들어주는 메서드

readWithMessageConverters(webRequest, parameter, paramType);
위 코드를 실행하여 코드 내부에서 MessageConverter 호출하여 값을 처리한다.
요청과 응답
ArgumentResolver와 HttpMessageConverter는 다르다.
HttpMessageConverter
- ArgumentResolver가 HttpMessageConverter를 사용한다.
- ReturnValueHandler가 HttpMessageConverter를 사용한다.
1. HTTP 요청
- @RequestBody 를 처리하는 ArgumentResolver가 존재한다.
- HttpEntity를 처리하는 ArgumentResolver가 존재한다.
- 다양한 ArgumentResolver들이 HttpMessageConverter를 호출하여 필요한 Object로 변환한다.
2.HTTP 응답
- @ResponseBody 를 처리하는 ReturnValueHandler가 존재한다.
- HttpEntity를 처리하는 ReturnValueHandler가 존재한다.
- 다양한 ReturnValueHandler 들이 HttpMessageConverter를 호출하여 필요한 응답을 입력한다.
WebMvcConfigurer
Spring MVC의 설정을 사용자 정의할 수 있도록 제공되는 인터페이스로 implements하여 설정을 확장하거나 커스터마이징할 수 있다.
주요 인터페이스
1. HandlerMethodArgumentResolver
2. HandlerMethodReturnValueHandler
3. HttpMessageConverter
모두 인터페이스로 구현되어 있으며 대부분 구현되어 있다.
Spring에서 기본적으로 제공하고 있다.
개발자는 잘 사용하면 된다.
WebMvcConfigurer
기능의 확장
WebMvcConfigurer를 상속받고 Spring Bean으로 등록
- addArgumentResolvers()
- addReturnValueHandlers()
- extendMessageConverters()
필요한 메서드를 오버라이딩 하면된다.
public interface WebMvcConfigurer {
default void configurePathMatch(PathMatchConfigurer configurer) {
}
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
default void addFormatters(FormatterRegistry registry) {
}
default void addInterceptors(InterceptorRegistry registry) {
}
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
default void addCorsMappings(CorsRegistry registry) {
}
default void addViewControllers(ViewControllerRegistry registry) {
}
default void configureViewResolvers(ViewResolverRegistry registry) {
}
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
@Nullable
default Validator getValidator() {
return null;
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
@Configuration
@Component 를 포함하고 있다. (Spring Bean 등록이 된다.)
TypeConverter
타입 변환
Spring에서 객체의 타입을 서로 변환하는 데 사용되는 인터페이스로 Spring의 데이터 바인딩 과정에서 문자열을 특정 객체로 변환하거나 하나의 객체 타입을 다른 타입으로 변환할 때 사용한다.
문자를 숫자로, 숫자를 문자로 변환하는 등 Web Application을 만들다보면 Type을 변환해야 하는 경우가 많이 발생한다.
문자열을 숫자로 HttpServletRequest
@Slf4j
@RestController
public class TypeConverterController {
@GetMapping("/param")
public void param(HttpServletRequest request) {
// 조회시 : String
String stringExample = request.getParameter("example");
// Integer로 Type 변환
Integer integerExample = Integer.valueOf(stringExample);
log.info("integerExample = {}", integerExample);
}
}
- 요청 파라미터는 문자열로 처리된다.
- 다른 타입으로 변환해서 사용하고자 한다면 위와같이 검증 및 변환하는 과정이 필요하다.
@RequestParam : 추가적인 변환 작업을 거치지 않고 Integer 타입으로 바인딩된다.
@GetMapping("/v2/param")
public void paramV2(@RequestParam Integer example) {
// Integer 타입으로 바인딩
log.info("example = {}", example);
}
결론
- 요청 파라미터로 전달하는 10 값은 실제로는 문자열(String) 10이다.
- @RequestParam을 사용하면 문자 10을 Integer 타입의 숫자 10으로 변환된다.
- @ModelAttribute, @PathVariable 에서도 타입 변환을 확인할 수 있다.
- Spring 내부에서 누군가가 타입을 자동으로 변환한다.
Converter Interface
Spring에서 특정 타입을 다른 타입으로 변환할 때 사용하는 인터페이스로 타입 변환 로직을 캡슐화하여 코드의 재사용성을 높이고 다양한 곳에서 타입 변환이 일관되게 수행되도록 돕는다.
새로운 타입의 변환
localhost:8080/type-converter?person=wonuk:120
입력받은 문자열 데이터를 Person 객체로 변환
public class Person {
private String name;
private String age;
}
wonuk:120 → TypeConverter → name: wonuk , age : 10
Converter Interface
T convert(S source);
- Spring이 제공하는 인터페이스
- implements하여 Converter로 등록하면 된다.
- Converter는 모든 타입(T)에 적용할 수 있다.
- 개발자가 새로운 Type을 만들어서 사용할 수 있도록 만든다.
- 변환하고자 하는 타입에 맞춰서 Type Converter를 구현하고 등록하면 된다.
Converter
Converter 구현
데이터 유형 간 변환을 담당하는 메커니즘, 주로 웹 요청 파라미터를 Java 객체로 변환하거나 그 반대로 변환할 때 사용되며 커스텀 변환 로직을 정의할 수 있다.
주의점
org.springframework.core.convert.converter
Spring의 Converter와 같은 이름을 가진 Interface가 많으니 주의해야 한다.
covert 메서드 꼭 오버라이드 해야함.
코드 예시
String → Integer
Converter<S, T> 에서 S는 변환할 Source T는 변환할 Type으로 설정하면 된다.
@Slf4j
public class StringToIntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
log.info("source = {}", source);
// 검증
return Integer.valueOf(source);
}
}
파라미터로 들어온 source가 Interger로 변환된다.
Integer → String
@Slf4j
public class IntegerToStringConverter implements Converter<Integer, String> {
@Override
public String convert(Integer source) {
log.info("source = {}", source);
return String.valueOf(source);
}
}
파라미터로 들어온 source가 String으로 변환된다.
String → Person
@Getter
public class Person {
// 이름
private String name;
// 나이
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
요청 예시
localhost:8080/type-converter?person=wonuk:1200
public class StringToPersonConverter implements Converter<String, Person> {
// source = "wonuk:1200"
@Override
public Person convert(String source) {
// ':' 를 구분자로 나누어 배열로 만든다.
String[] parts = source.split(":");
// 첫번째 배열은 이름이다. -> wonuk
String name = parts[0];
// 두번째 배열은 개월수이다. -> 1200
int months = Integer.parseInt(parts[1]);
// 개월수 나누기 12로 나이를 구하는 로직 (12개월 단위만 고려)
int age = months / 12;
return new Person(name, age);
}
}
public class PersonToStringConverter implements Converter<Person, String> {
@Override
public String convert(Person source) {
// 이름
String name = source.getName();
// 개월수
int months = source.getAge * 12;
// "wonuk:1200"
return name + ":" + months;
}
}
TypeConverter 사용
구현은 단순하게 직접 메서드를 구현하여 모듈화 하면된다.
TypeConverter 를 생성하여 직접 사용하면 컨트롤러에서 변환하는 방식과 큰 차이가 없다.
PersonToStringConverter converter = new PersonToStringConverter();
String source = "wonuk:1200";
converter.convert(source);
Converter를 편리하게 등록하고 사용할 수 있도록 만들어주는 기능이 필요하다.
Spring은 String, Integer, Enum등 자주 사용되는 타입에 대한 컨버터를 제공하고 사용할 수 있도록 등록되어 있다.
Spring의 다양한 Converter
Spring에서 제공하는 다양한 Converter 인터페이스가 존재하며 이들은 Spring의 데이터 바인딩, 요청/응답 처리, 속성 값 주입 등에 사용되고 ConversionService를 통해 등록 및 관리된다.
1. Converter
- 기본적인 변환을 담당하는 인터페이스
- 단일 타입에서 단일 타입으로 변환할 때 사용한다.
- Converter<Source, Type>
2. ConverterFactory
- 클래스 계층 구조가 복잡한 경우 사용
- 기본 타입과 다양한 서브 타입 간의 변환을 지원한다.
3. GenericConverter
- 다양한 타입 간의 유연한 변환을 지원한다.
- 복잡한 타입 변환 로직을 구현할 때 유리하다.
4. ConditionalGenericConverter
- GenericConverter 의 확장형으로 특정 조건에서만 타입 변환을 수행한다.
- 추가적으로 matches() 를 통해 변환 가능 여부를 판단할 수 있다.
공식문서
https://docs.spring.io/spring-framework/reference/core/validation/convert.html
Spring Type Conversion :: Spring Framework
When you require a sophisticated Converter implementation, consider using the GenericConverter interface. With a more flexible but less strongly typed signature than Converter, a GenericConverter supports converting between multiple source and target types
docs.spring.io
ConversionService
ConversionService
Spring은 Converter를 모아서 편리하게 관리하고 사용할 수 있게 해주는 기능을 제공한다. 이것이 Conversion Service 이다.
ConversionService 인터페이스
1. canConvert()
Convert 가능 여부를 확인하는 기능
2. convert()
실제 변환하는 기능
DefaultConversionService
Spring의 표준 ConversionService로 기본 제공 Converter와 확장 가능성을 통해 다양한 타입 변환을 유연하게 처리할 수 있도록 지원한다.
DefaultConversionService
ConversionService를 구현한 구현체
ConvertRegistry에 다양한 Converter를 등록한다.
ConverterRegistry
Converter를 등록하고 관리하는 기능을 제공한다.
코드 예시
import static org.assertj.core.api.Assertions.*;
public class ConversionServiceTest {
@Test
void defaultConversionService() {
// given
DefaultConversionService dcs = new DefaultConversionService();
dcs.addConverter(new StringToPersonConverter());
Person wonuk = new Person("wonuk", 100);
// when
Person stringToPerson = dcs.convert("wonuk:1200", Person.class);
// then
// Assertions.assertThat();
assertThat(stringToPerson.getName()).isEqualTo(wonuk.getName());
assertThat(stringToPerson.getAge()).isEqualTo(wonuk.getAge());
}
}
- 컨버터를 사용할 때는 종류를 몰라도된다.
- 컨버터는 ConversionService 내부에서 숨겨진채 제공된다.
- 반환 타입, 파라미터 타입, 제네릭 등으로 ConversionService가 컨버터를 찾는다.
- 즉, 클라이언트는 ConversionService 인터페이스만 의존하면 된다.
- 컨버터 등록과 사용의 분리
ISP(인터페이스 분리 원칙, Interface Segregation Principal)
클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리하는 원칙
인터페이스 분리 원칙(ISP)
DefaultConversionService
- ConversionRegistry : 컨버터 등록
- ConversionService : 컨버터 사용
인터페이스를 분리하면 컨버터를 사용하는 클라이언트는 필요한 메서드만 알면된다.
ConversionRegistry 가 변경되어도 ConversionService와 연관이 없다.
Spring은 내부적으로 위와 같이 등록, 사용이 분리된 인터페이스들이 아주 많다.
Spring은 내부적으로 ConversionService를 사용해 타입을 변환한다. 대표적으로 @RequestParam , @PathVariable, @ModelAttribute 등이 해당 기능을 사용한다.
Converter 적용
실습
Converter 적용
- Spring은 내부적으로 ConversionService를 제공한다.
- WebMvcConfigurer가 제공하는 addFomatters() 를 사용하여 Converter를 등록하면 된다.
- 클라이언트 측에서 컨버터를 사용하면 된다.
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFomatters(FormatterRegistry registry) {
// registry.addConverter(new "${등록할 Converter}");
registry.addConverter(new StringToPersonConverter());
registry.addConverter(new PersonToStringConverter());
}
}
💡 StringToIntegerConverter와 같이 Spring이 기본적으로 제공하는 컨버터들이 존재하지만, 개발자가 StringToIntegerConverter 를 따로 커스텀하게 구현하여 컨버터로 추가하면 추가한 컨버터가 기본 제공된 컨버터보다 높은 우선순위를 가진다.
TypeConverterController
@Slf4j
@RestController
public class TypeConverterController {
@GetMapping("/type-converter")
public void typeConverter(@RequestParam Person person) {
log.info("person.getName() = {}", person.getName());
log.info("person.getAge() = {}", person.getAge());
}
}
Postman
GET localhost:8080/type-converter?person=wonuk:1200

출력 결과

정상적으로 타입이 변환되어 출력되는것을 확인할 수 있다.
wonuk:1200이 Person 객체 타입으로 변환되었다.
@RequestParam 뿐만이 아니라 @PathVariable, @ModelAttribute 모두 적용된다
Argument Resolver

1. @RequestParam 을 처리하는 RequestParamMethodArgumentResolver 에서 ConversionService 를 사용하여 타입을 변환한다.
내부적으로 굉장히 복잡하게 메서드들이 호출되기 때문에 그림으로 이해하면 된다.
💡 Thymeleaf와 같은 ViewTemplate에서도 이러한 TypeConverter 기능을 제공한다.
Formatter
Formatter
주로 사용자 지정 포맷을 적용해 데이터 변환을 처리할 때 사용된다. Formatter는 ConversionService와 비슷한 목적을 가지지만 문자열을 객체로 변환하거나 객체를 문자열로 변환하는 과정에서 포맷팅을 세밀하게 제어할 수 있다.
객체를 특정한 포맷에 맞춰서 문자로 출력하는 기능에 특화된것이 Fomatter이다.
Converter보다 조금 더 세부적인 기능이라고 생각하면 된다.
공식문서 https://docs.spring.io/spring-framework/reference/core/validation/format.html
Spring Field Formatting :: Spring Framework
As discussed in the previous section, core.convert is a general-purpose type conversion system. It provides a unified ConversionService API as well as a strongly typed Converter SPI for implementing conversion logic from one type to another. A Spring conta
docs.spring.io
Locale
지역 및 언어 정보를 나타내는 객체.
- 언어코드 en, ko
- 국가코드 US, KR
특정 지역 및 언어에 대한 정보를 제공하여 국제화 및 지역화 기능을 지원한다.
국제화 : Locale 정보에 따라서 한글을 보여줄지 영문을 보여줄지 선택할 수 있다.
Formatter Interface
Printer, Parser 상속
객체를 문자로 변환하고 문자를 객체로 변환하는 두가지 기능을 모두 가지고 있다.
Printer
Object를 String으로 변환한다.
Parser
String을 Object로 변환한다.
Formatter 적용
코드 예시
숫자(10000)를 금액 형태(10,000)로 변환하는 Formatter
@Slf4j
public class PriceFormatter implements Formatter<Number> {
@Override
public Number parse(String text, Locale locale) throws ParseException {
log.info("text = {}, locale={}", text, locale);
// 변환 로직
// NumberFormat이 제공하는 기능
NumberFormat format = NumberFormat.getInstance(locale);
// "10,000" -> 10000L
return format.parse(text);
}
@Override
public String print(Number object, Locale locale) {
log.info("object = {}, locale = {}", object, locale);
// 10000L -> "10,000"
return NumberFormat.getInstance(locale).format(object);
}
}
Number
Integer, Long, Double 등의 부모 클래스
테스트
class PriceFormatterTest {
PriceFormatter formatter = new PriceFormatter();
@Test
void parse() throws ParseException {
// given, when
Number result = formatter.parse("1,000", Locale.KOREA);
// then
// parse 결과는 Long
Assertions.assertThat(result).isEqualTo(1000L);
}
@Test
void print() {
// given, when
String result = formatter.print(1000, Locale.KOREA);
// then
Assertions.assertThat(result).isEqualTo("1,000");
}
}
Spring Formatter
FormattingConversionService
ConversionService와 Formatter를 결합한 구현체로 타입 변환과 포맷팅이 필요한 모든 작업을 한 곳에서 수행할 수 있도록 설계되어 있어서 다양한 타입의 변환과 포맷팅을 쉽게 적용할 수 있다.
FormattingConversionService
Formatter를 지원하는 ConversionService
어댑터 패턴을 사용하여 Formatter가 Converter처럼 동작하도록 만들어준다.
DefaultFormattingConversionService
FormattingConversionService + 통화, 숫자관련 Formatter를 추가한 것
테스트
ConversionService가 제공하는 convert()를 사용하면 된다.
public class FormattingConversionServiceTest {
@Test
void formattingConversionService() {
// given
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
// Converter 등록
conversionService.addConverter(new StringToPersonConverter());
conversionService.addConverter(new PersonToStringConverter());
// Formatter 등록
conversionService.addFormatter(new PriceFormatter());
// when
String result = conversionService.convert(10000, String.class);
// then
Assertions.assertThat(result).isEqualTo("10,000");
}
}
SpringBoot의 기능
SpringBoot는 기본적으로 WebConversionService를 사용한다.
DefaultFormattingConversionService 상속
Spring이 제공하는 Formatter
Spring은 어노테이션 기반으로 원하는 형식의 Formatter를 사용할 수 있도록 기능을 제공한다.
Spring은 Java에서 기본적으로 제공하는 타입들에 대해 기본적으로 Formatter들을 제공한다. 하지만 기본 제공 Formatter는 기본 형식이 정해져 있어서 필드마다 다른 형식으로 지정하기 어렵다.
Annotation
DTO 필드에 적용 가능
1. @NumberFormat
- 숫자 관련 지정 Formatter 사용
- NumberFormatAnnotationFormatterFactory
2. @DateTimeFormat
- 날짜 관련 지정 Formatter 사용
- Jsr310DateTimeFormatAnnotationFormatterFactory
Spring Field Formatting :: Spring Framework
As discussed in the previous section, core.convert is a general-purpose type conversion system. It provides a unified ConversionService API as well as a strongly typed Converter SPI for implementing conversion logic from one type to another. A Spring conta
docs.spring.io
코드 예시
@Data
public class FormatForm {
@NumberFormat(pattern = "#,###.##")
private BigDecimal price;
@DateTimeFormat(pattern = "dd-MM-yyyy")
private LocalDate orderDate;
}
예시라서 @Data를 사용합니다. 운영 코드에서는 사용하지 말아주세요.
@RestController
public class FormatController {
@PostMapping("/format")
public ResponseEntity<String> format(@ModelAttribute FormatForm form) {
return new ResponseEntity<>(
"form.getPrice() = " + form.getPrice() +
" form.getOrderDate() = " + form.getOrderDate(),
HttpStatus.OK
);
}
}
필드마다 정상적으로 타입이 변환되어 바인딩된다.
JSON
@PostMapping("/json-format")
public ResponseEntity<String> jsonFormat(@RequestBody FormatForm form) {
return new ResponseEntity<>(
"form.getPrice() = " + form.getPrice() +
" form.getOrderDate() = " + form.getOrderDate(),
HttpStatus.OK
);
}
- @NumberFormat과 @DateTimeFormat은 폼 데이터와 URL 파라미터를 처리하는 ConversionService와 관련이 있다.
- Jackson의 직렬화/역직렬화 과정에는 기본적으로 영향을 미치지 않는다.
- @JsonFormat 나 커스텀 Deserializer를 사용하는 방식이 필요하다.
Deserializer
public class CurrencyDeserializer extends JsonDeserializer<BigDecimal> {
@Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
String value = p.getText().replaceAll("[^\\d.]", ""); // 숫자와 소수점만 남기기
return new BigDecimal(value);
}
}
public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
return LocalDate.parse(p.getText(), DateTimeFormatter.ofPattern("dd-MM-yyyy"));
}
}
@Data
public class JsonFormatDto {
@JsonDeserialize(using = CurrencyDeserializer.class)
private BigDecimal price;
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate orderDate;
}
예시라서 @Data를 사용합니다. 운영 코드에서는 사용하지 말아주세요.
@PostMapping("/json-format/deserialize")
public ResponseEntity<String> jsonFormatDeserialize(@RequestBody JsonFormatDto dto) {
return new ResponseEntity<>(
"dto.getPrice() = " + dto.getPrice() +
" dto.getOrderDate() = " + dto.getOrderDate(),
HttpStatus.OK
);
}
정상적으로 변환 후 출력된다.
@JsonFormat
@Data
public class JsonFormatDtoV2 {
@JsonDeserialize(using = CurrencyDeserializer.class)
private BigDecimal price;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private LocalDate orderDate;
}
예시라서 @Data를 사용합니다. 운영 코드에서는 사용하지 말아주세요.
@PostMapping("/json-format/annotation")
public ResponseEntity<String> jsonFormatAnnotation(@RequestBody JsonFormatDtoV2 dto) {
return new ResponseEntity<>(
"dto.getPrice() = " + dto.getPrice() +
" dto.getOrderDate() = " + dto.getOrderDate(),
HttpStatus.OK
);
}
- @JsonFormat은 날짜 형식이나 숫자 포맷을 지정할 수 있다.
- 콤마를 포함한 숫자는 Jackson이 자동으로 변환하지 않으므로 커스텀 처리가 필요하다.
- 커스텀 데이터의 변환이 필요한 경우 Deserialize 사용
'코딩 > sparta TIL' 카테고리의 다른 글
TIL 36 : EntityManagerFactory, EntityManager, EntityTransaction (1) | 2025.04.16 |
---|---|
TIL 35 : 예외처리 복습 (1) | 2025.04.15 |
Paging 공부중 (0) | 2025.04.09 |
CH3 Schedule Project (0) | 2025.04.04 |
TIL 32 : JPA, Spring Data JPA (0) | 2025.04.01 |