Spring Boot - @RestController의 동작 과정과 MessageConverter
Spring Boot에서 @RestController를 사용하면 일반적인 @Controller와 달리 ViewResolver가 관여하지 않고, MessageConverter가 실행된다. 이를 통해 API의 응답이 클라이언트가 원하는 형태(JSON, XML 등)로 변환된다.
@RestController와 MessageCoverter의 동작 과정
1. 핸들러 메서드 실행
클라이언트가 요청을 보내면, Spring MVC는 적절한 @RestController의 핸들러 메서드를 실행한다.
@RestController
public class ExampleController {
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
}
이 메서드는 "Hello, World!"를 반환하며, @ResponseBody가 @RestController에 의해 자동 적용된다.
2. ReturnValueHandler가 MessageConverter 선택
Spring은 반환된 객체를 적절한 MessageConverter를 사용해 변환해야 한다. 이때 HandlerMethodReturnValueHandler 중 RequestResponseBodyMethodProcessor가 실행된다.
RequestResponseBodyMethodProcessor는 @ResponseBody 또는 @RestController가 있는 메서드의 반환값을 처리하는 역할을 한다.
RequestResponseBodyMethodProcessor의 내부 로직
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
// ...
// 해당 메서드가 @ResponseBody 적용 대상인지 확인
public boolean supportsReturnType(MethodParameter returnType) {
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);
}
// Controller의 반환 값을 받아 적절한 MessageConverter를 호출
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// View를 렌더링하지 않도록 설정 (REST 응답 처리)
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
// (예외 처리 로직 생략) - ProblemDetail 처리 예시
// 최종적으로 MessageConverter를 실행하여 응답을 변환
this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
// ...
}
supportsReturnType() 메서드는 @ResponseBody가 있는지 확인하고,
handleReturnValue() 메서드가 실행되면서 반환된 객체를 writeWithMessageConverters()를 통해 적절한 HttpMessageConverter를 선택하여 변환한다.
3. MessageConverter의 선택 과정
컨트롤러 메서드에서 반환한 객체가 MessageConverter를 통해 변환될 때, canWrite(Class<?> clazz, MediaType mediaType) 메서드를 이용해 적절한 변환기를 선택한다.
HttpMessageConverter 인터페이스 (추상 구조)
public interface HttpMessageConverter<T> {
// 변환 가능한지 확인
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
// 지원하는 MediaType 목록
List<MediaType> getSupportedMediaTypes();
// 요청 바디를 객체로 변환
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// 객체를 응답 바디로 변환
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
canWrite(Class<?> clazz, MediaType mediaType) 메서드가 실행되면서, returnType을 기반으로 변환기가 선택된다.
4. MessageConverter의 동작 방식
| 반환 타입 | 적용되는 MessageConverter |
| String | StringHttpMessageConverter |
| 객체 (DTO, Map 등) | MappingJackson2HttpMessageConverter |
| byte[] | ByteArrayHttpMessageConverter |
String을 반환할 때
@RestController
public class ExampleController {
@GetMapping("/text")
public String getText() {
return "Hello, String!";
}
}
1. RequestResponseBodyMethodProcessor → canWrite(String.class, text/plain) 확인
2. StringHttpMessageConverter가 선택됨
3. write(String, text/plain, outputMessage) 실행 → 응답 바디에 "Hello, String!" 작성
DTO를 반환할 때
@RestController
public class ExampleController {
@GetMapping("/json")
public Person getPerson() {
return new Person("Alice", 30);
}
}
class Person {
private String name;
private int age;
// Getter & Setter 생략
}
1. RequestResponseBodyMethodProcessor → canWrite(Person.class, application/json) 확인
2. MappingJackson2HttpMessageConverter가 선택됨
3. write(Person, application/json, outputMessage) 실행 → JSON 변환 후 응답 바디에 작성
5. ResponseBodyAdvice로 공통 처리
Spring에서는 ResponseBodyAdvice 인터페이스를 활용하면 MessageConverter가 실행되기 전에 API 응답을 가로채서 공통 로직을 적용할 수 있다.
ResponseBodyAdvice는 모든 API 응답을 가로채 변환할 수 있는 AOP 역할을 한다. 이를 활용하면, API의 응답을 표준화하거나, 로깅 및 데이터 변환 등의 사전 처리 로직을 적용할 수 있다.
ResponseBodyAdvice의 핵심 메서드
public interface ResponseBodyAdvice<T> {
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}
ResponseBodyAdvice<T> 인터페이스는 다음과 같은 두 가지 메서드를 제공한다.
supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType)
- 어떤 컨트롤러 메서드의 반환값을 가로챌지 결정하는 메서드
- 모든 API에 적용하고 싶다면 return true; 로 설정
- 특정 조건(@RestController, 특정 응답 타입 등)을 기반으로 필터링할 수도 있다.
beforeBodyWrite(T body, MethodParameter returnType, ...)
- 응답 본문을 가공할 수 있는 메서드
- 변환된 객체를 감싸거나, 특정 필드를 추가하거나, 로깅하는 등의 작업이 가능
- 최종적으로 변환된 데이터를 반환
최종 응답 처리 과정 정리
1. 컨트롤러 메서드 실행
2. RequestResponseBodyMethodProcessor가 MessageConverter 선택
3. (선택적) ResponseBodyAdvice가 반환 값을 가로채 응답 가공
4. MessageConverter의 writeInternal() 실행 → 응답 바디 작성
5. 클라이언트에게 최종 변환된 응답 반환