스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 공부하고 정리하는 포스트입니다.


HTTP 응답

종류

  • HTTP Response로 서버에서 클라이언트로 제공하는 데이터를 만드는 방법은 3가지.
    • 정적 리소스 : 웹 브라우저에 정적인 HTML / CSS / JS 을 제공
    • 뷰 템플릿 : 웹 브라우저에 동적인 HTML을 제공하는 뷰 템플릿
    • HTTP 메시지 사용 : HTTP Response Message Body에 JSON과 같은 형식으로 데이터를 실어 보냄

정적 리소스

  • 스프링 부트의 클래스 패스(classpath)
    • /static
    • /public
    • /resources
    • /META-INF/resources
  • /src/main/resources는 리소스를 보관하는 곳이며, 클래스 패스의 시작경로
    • ex) src/main/resources/static/basic/hello-form.html이 있을 때 -> http://localhost:8080/basic/hello-form.html로 접근 가능 -> 정적 리소스는 해당 파일을 변경 없이 그대로 서비스 하는 것

뷰 템플릿


  • V3는 명시성이 떨어지고 딱 맞는 경우가 많이 없어서 권장하지 않음.
  • JSP / 타임리프 / 머스태치 등 다양한 뷰 템플릿을 동적으로 만들어서 사용자에게 제공
  • 스프링 부트는 기본 뷰 템플릿 경로를 제공 -> /src/main/resources/templates
  • Spring에서는 타임리프 템플릿 엔진을 기본적으로 권장함!
  • application.properties에 prefix / suffix 설정 가능
/* 기본적으로 들어가 있는 설정이다. 타임리프가 템플릿 엔진 사용시 변경하면 된다. */
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

HTTP 메시지 사용



  • responseBodyV1
    • 서블릿을 직접 다룰 때처럼 HttpServletResponse 객체를 통해서 HTTP 메시지 바디에 직접 “ok” 응답 메시지를 전달
  • responseBodyV2
    • ResponseEntity는 HttpEntity를 상속, HTTP Status Code를 직접 설정할 수 있음
  • responseBodyV3
    • @ResponseBody를 통해 응답 메시지 자체로 데이터를 받게 설정
  • responseBodyJsonV1
    • ResponseEntity를 반환, HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환.
  • responseBodyJsonV2
    • ResponseEntity를 사용하면 Status Code를 직접 설정 할 수 있으며, 그렇지 않으면 @ResponseStatus 어노테이션으로 지정할 수도 있음.
  • 실무에서는 Entity를 직접 반환하지 않고 무조건 DTO를 통해 HTTP response를 반환함!
  • 응답 메시지를 처리하는 과정에서도 HTTP 메시지 컨버터가 사용됨.

HTTP 메시지 컨버터

역할


  • HTTP response로 뷰가 아닌 데이터를 담아서 보내는 경우
    • viewResolver 대신 HttpMessageConventer가 동작 (다양한 종류의 컨버터가 존재함)
    • 기본 문자 -> StringHttpMessageConverter가 처리
    • 기본 객체 -> MappingJackson2HttpMessageConverter가 처리
  • HTTP request가 오거나 HTTP response로 데이터를 body에 담아서 보낼 때 -> 스프링이 모든 메시지 내용을 알맞게 변환 -> 이러한 역할을 해주는 것이 바로 HTTP 메시지 컨버터

사용 원리

  • 스프링 MVC는 다음의 경우에 HTTP 메시지 컨버터를 적용 : 메시지 컨버터가 필요한 경우 -> Http request Body에 데이터가 있는 경우
    • HTTP 요청 : @RequestBody / HttpEntity(RequestEntity)
    • HTTP 응답 : @ResponseBody / HttpEntity(ResponseEntity)
  • HTTP 메세지 컨버터 인터페이스


  • canRead() / canWrite() -> 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크
  • read() / write() -> 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능

주요한 메시지 컨버터

  • ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.
    • 클래스 타입 : byte[], 미디어타입 : */*
    • 요청 예) @RequestBody byte[] data
    • 응답 예) @ResponseBody return byte[] 쓰기 미디어타입 application/octet-stream
  • StringHttpMessageConverter : String 문자로 데이터를 처리한다.
    • 클래스 타입: String, 미디어타입 : */*
    • 요청 예) @RequestBody String data
    • 응답 예) @ResponseBody return "ok" 쓰기 미디어타입 text/plain
  • MappingJackson2HttpMessageConverter : application/json
    • 클래스 타입: 객체 또는 HashMap, 미디어타입 : application/json 관련
    • 요청 예) @RequestBody HelloData data
    • 응답 예) @ResponseBody return helloData 쓰기 미디어타입 application/json 관련

HTTP 요청 데이터 읽기

  • HTTP 요청 흐름
    • HTTP Request 요청
    • Controller에서 @RequestBody 혹은 HttpEntity 파라미터를 사용
    • 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead()를 호출
      • 대상 클래스 타입 확인
        ex) @RequestBody로 받은 대상 클래스 타입(byte[] / String 등등)
      • HTTP request의 Conent-Type에 해당하는 미디어 타입을 지원하는지 확인
        ex) text/plain, application/json 등등
    • canRead()를 만족하면 read()를 호출해서 객체를 읽어서 우리가 쓸 수 있도록 생성하고 반환

HTTP 응답 데이터 생성

  • HTTP 응답 흐름
    • Controller에서 @ResponseBody / HttpEntity로 값이 반환
    • 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite()를 호출
      • 대상 클래스 타입 확인
      • HTTP request의 Accept에 해당하는 미디어 타입을 지원하는지 확인
    • canWrite()을 만족하면 write()를 호출해서 HTTP Response Body에 데이터 생성

Argument Resolver & ReturnValue Handler


  • 스프링 MVC에서 HTTP 메시지 컨버터가 사용되는 위치는?
    • 핸들러 어댑터를 통해 실제 핸들러(컨트롤러)를 호출하는 과정을 알아야 함.
    • 애노테이션 기반의 컨트롤러, 즉 @RequestMapping을 처리하는 핸들러 어댑터 RequestMappingHandlerAdapter(요청 매핑 핸들러 어댑터)


  • 애노테이션 기반의 컨트롤러는 굉장히 많은 종류의 파라미터를 사용함 (HttpServletRequest / Model / @RequestParam / @ModelAttribute 등)
  • 이렇게 유연하게 파라미터를 처리할 수 있는 이유가 바로 ArgumentResolver!

Argument Resolver


  • 정확히는 HandlerMethodArgumentResolver인데 줄여서 ArgumentResolver라고 함.
  • 유연하게 다양한 파라미터를 처리하기 위한 것
  • 핸들러 어댑터는 Argument Resolver를 호출해서 미리 들어갈 매개변수를 파악 & 생성
  • 스프링은 30개가 넘는 ArgumentResolver를 기본으로 제공

  • 동작 원리
    • 핸들러 어댑터가 Argument Resolver를 호출
    • Argument Resolver는 내부에 supportsParameter()를 호출하며 어떤 파라미터를 지원하는지 확인
    • 지원하는 파라미터를 찾은 뒤 resolveArgument()를 호출해서 실제 객체를 생성 (실제 객체를 생성할 때 HTTP 메서드 컨버터를 통해 메시지를 변환하여 사용)

ReturnValue Handler

  • HandlerMethodReturnValueHandler를 줄여서 ReturnValueHandler라 부름
  • ArgumentResolver와 비슷, 이것은 응답 값을 변환하고 처리
  • 컨트롤러에 String으로 뷰 이름을 반환해서 동작하는 이유
  • 스프링은 10개가 넘는 ReturnValueHandler를 지원

HTTP 메시지 컨버터


  • ArgumentResolver / ReturnValueHandler가 대응되는 파라미터와 반환값을 만들어주는 과정에서 HTTP 메시지 컨버터를 호출해서 사용
    • 요청 : @RequestBody를 처리하는 ArgumentResolver가 있고, HttpEntity를 처리하는 ArgumentResolver가 있음. 이 ArgumentResolver들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성.
    • 응답 : @ResponseBodyHttpEntity를 처리하는 ReturnValueHandler가 있음. 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 생성.
  • 스프링은 ArgumentResolver / ReturnValueHandler / HttpMessageConverter 모두 인터페이스로 제공
    • 실제 확장이 필요할 때 이러한 내부 원리를 알고있어야 확장 포인트를 알 수 있음!