본문 바로가기

Back-End/Spring Boot

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. 클라이언트에게 최종 변환된 응답 반환