Spring MVC의 API 통신

하루히즘·2021년 6월 22일
0

Spring Framework

목록 보기
7/15

서론

최근에는 스프링 부트 기반 RESTFul API를 활용하는 할 일 관리 애플리케이션을 만드는 데 집중하고 있다. 설계된 API에서는 어떤 엔티티를 조회하거나 수정한 후 해당 엔티티의 정보(회원의 이름, 항목의 내용 등)를 DTO 객체에 담아서 JSON 형식으로 반환하고 있는데 ObjectMapper, HttpMessageConverter, ResponseEntity 등 생소한 개념을 많이 접하게 되서 한번 정리하려고 한다.

본론

@ResponseBody

스프링 MVC에서는 서버측에서 페이지를 렌더링하는 전통적인 MVC 방식으로 웹 애플리케이션을 구축할 수도 있으나 단순히 JSON 형식의 데이터를 HTTP 응답으로 제공하는 API 방식으로 구축할 수도 있다.

@Target(value=TYPE)
@Retention(value=RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController

이때는 해당 요청을 처리하는 컨트롤러에서 @Controller가 아니라 @RestController라는 어노테이션을 붙여주면 되는데 이 어노테이션은 기존의 @Controller에 @ResponseBody 어노테이션을 추가한 것과 동일하다.

기존 MVC 컨트롤러에서는 컨트롤러의 핸들러 메서드가 ModelAndView나 String 타입의 값을 반환하면 이를 기반으로 ViewResolver가 해당하는 View를 찾아 렌더링한 후 HTTP 응답으로 전달한다. 하지만 REST 컨트롤러를 사용하면 @ResponseBody 어노테이션을 적용, 스프링은 컨트롤러의 핸들러 메서드에서 반환한 응답값을 그대로 HTTP 응답에 삽입하여 클라이언트에게 반환한다. 즉 말 그대로 HTTP ResponseBody인 것이다.

HttpMessageConverter

핸들러 메서드에서 HTTP 패킷에 실을 수 있도록 정수나 문자열을 반환하면 좋겠지만 자바 객체를 반환하는 경우 어떻게 HTTP 응답에 삽입할 수 있을까? 여기서 HttpMessageConverter라는 인터페이스를 활용하게 된다.

이 인터페이스는 HTTP 요청이나 응답을 제너릭스 타입의 객체로 변환하는 역할을 정의하고 있다. HTTP 메시지를 읽거나 클래스로 쓰는 함수들이 정의되어 있으며 이를 구현한 객체들을 활용하여 다양한 방법으로 HTTP 요청, 응답을 읽고 쓸 수 있는 전략 패턴이다.

이 인터페이스의 구현체는 텍스트로 읽고 쓰는 StringHttpMessageConverter, HTML Form으로 읽고 쓰는 FormHttpMessageConverter 등 다양한 구현체가 있다.

Jackson

스프링 부트 Web 모듈의 Starter 패키지는 Jackson 라이브러리를 종속성으로 가지고 있기 때문에 자동으로 등록 및 설정된다. Jackson 라이브러리는 자바 기반 JSON 라이브러리로 HttpMessageConverter 인터페이스의 구현체 MappingJackson2HttpMessageConverter를 제공한다.

이 구현체는 내부적으로 ObjectMapper, JsonGenerator 등의 클래스를 활용하여 자바 POJO 클래스의 객체를 JSON 형식으로 읽고 쓸 수 있다. 만약 라이브러리를 직접 사용한다면 이 튜토리얼처럼 ObjectMapper 객체와 직접 상호작용해야 하겠지만 스프링 프레임워크에서 대신 처리해주고 있기 때문에 우리는 객체만 반환하면 JSON으로 클라이언트에게 응답할 수 있다.

ResponseEntity

위처럼 HttpMessageConverter만 있어도 클라이언트에게 JSON으로 응답할 수 있지만 RESTful한 API를 제공하려면 HTTP 응답에 상태 코드를 적극적으로 반영할 수 있어야 한다. 즉 @ResponsBody 어노테이션으로 HTTP 바디를 제공했다면 이번에는 HTTP 헤더도 제공해야 한다는 것이다.

일반적으로 HTTP 응답의 헤더를 설정하려면 핸들러 메서드에서 HttpServletResponse 파라미터를 받아 setStatus를 설정하거나 @ResponseStatus 어노테이션으로 응답 상태값을 지정할 수 있다.

하지만 이런 경우에는 ResponseEntity라는 제너릭스 클래스를 활용하는 것이 좋다. 이 클래스는 사용자에게 전송되는 HTTP 응답의 모든 것(상태 코드, 헤더, 바디 등)을 포함한다. 핸들러 메서드의 반환값으로 사용될 경우 클라이언트에게 전송되는 HTTP 응답은 이 ResponseEntity 객체의 속성으로 설정되어 HttpMessageConverter로 변환되어 전송된다.

가장 큰 장점은 아래 코드처럼 HTTP 응답을 간결하게 생성할 수 있다는 것이다.

@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).build(body);
}

출처

위의 코드에서는 ResponseEntity의 정적 메서드 ok를 활용하여 200 OK 상태 코드의 HTTP 응답을 생성했다. 그리고 필요한 HTTP 헤더(eTag)를 추가적으로 설정한 후에 build 메서드로 HTTP 바디까지 지정해서 ResponseEntity 객체를 생성, HTTP 응답으로 보낼 수 있었다.

어노테이션으로 컴파일 타임에 미리 지정된 @ResponseStatus와 달리 자바 코드를 기반으로 동적으로 설정할 수 있기 때문에 더 유연하다는 장점이 있다.

이렇게 반환된 ResponseEntity 객체는 스프링이 적절히 처리하여 HTTP 응답으로 변환한다.

결론

@RestController는 @Controller에 @ResponseBody만 붙여서 HTTP 응답으로 직접 반환할 수 있도록 하는 것이 핵심인 것 같다.

HTTP 바디에 데이터를 직접 넣을 때 여러 타입의 객체를 변환하는 것은 Jackson같은 라이브러리에 위임하고 HttpMessageConverter 인터페이스를 이용하여 유연하게 접근하는 모습이 참 객체지향 답다고 느낄 수 있었다.

참고

When use ResponseEntity<T> and @RestController for Spring RESTful applications

profile
YUKI.N > READY?

0개의 댓글