[Spring MVC 1편] 6. 스프링 MVC 기능

HJ·2022년 8월 17일
0

Spring MVC 1편

목록 보기
6/8

김영한 님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard


1. Jar, War

  • Jar

    • Jar를 사용하면 항상 내장 서버( 톰캣 등 )를 사용하고, webapp 경로도 사용하지 않고 내장 서버 사용에 최적화 되어 있는 기능

    • JSP를 사용하지 않는 경우, Jar를 사용하는 것이 좋음

    • 스프링부트에 Jar 를 사용하면 /resources/static/ 위치에 index.html 파일을 두면 Welcome 페이지로 처리해준다

  • War

    • 톰캣 같은 WAS를 별도로 설치하고 빌드한 파일을 넣을 때 사용

    • JSP를 사용하는 경우 War를 사용

    • War를 사용하면 내장 서버도 사용 가능하지만 주로 외부 서버에 배포하는 목적으로 사용




2. 로깅

2-1. 로깅 라이브러리

  • spring-boot-starter 라이브러리를사용하면 spring-boot-starter-logging 라이브러리가 함께 포함

  • SLF4J : 원래는 많은 로그 라이브러리가 있는데 그것을 통합해서 인터페이스로 제공

  • SLF4J 인터페이스와 이를 구현한 Logback을 주로 사용


2-2. 로그 선언 및 호출

@Slf4j
@RestController
public class LogTestController {

    // 로그 선언
    // private final Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/log-test")
    public String logTest() {
        String name = "Spring";

        log.trace("trace log={}", name);
        log.debug("debug log={}", name);
        log.info("info log={}", name);
        log.warn("warn log={}", name);
        log.error("error log={}", name);

        return "ok";
    }
}
  • @Slf4j를 붙여주면 로그 선언 없이 로그 사용 가능
  • @RestController

    • 문자를 반환하면 HTTP message body에 입력 ( String 자체를 반환 )
  • @Controller

    • 문자를 반환하면 view 이름으로 인식 ( ➜ view를 찾고 렌더링됨 )

    • @Controller를 사용하면서 메소드 위에 @ResponseBody를 사용하면 String을 반환해도 @RestController처럼 동작하게 됨

  • @ResponseBody

    • View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력

2-3. 로그 레벨

  • trace > debug > info > warn > error

  • application.properties에서 로그 레벨을 설정할 수 있음

  • logging.level.프로젝트이름.패키지=레벨

    • 패키지와 하위 로그 레벨 설정

    • 레벨이 trace이면 trace, debg 등 모든 하위 레벨 로그가 출력됨

    • debug로 설정하면 trace 제외한 모든 로그가 출력됨

  • logging.level.root=레벨

    • 전체 로그 레벨 설정

    • 레벨의 디폴트는 info




3. 요청 매핑

3-1. @RequestMapping

  • 배열을 이용해서 2개 이상의 url을 매핑할 수 있다

  • method 속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 무조건 호출된다

    • 지정된 url에 GET, POST, PUT, PATCH, DELETE 어떤 방식으로 요청해도 호출된다
  • method 지정 시, url은 맞지만 method에 맞지 않는 요청이 들어오면 스프링 MVC는 405 상태코드를 반환한다

  • method 속성을 부여하는 대신 @GetMapping 처럼 축약해서 사용 가능

    • @GetMapping내부를 살펴보면 @RequestMapping에 method 속성이 RequestMethod.GET으로 설정되어 있음

    • 즉, @RequestMapping(value = "url", method = RequestMethod.GET) = @GetMapping("url")


3-2. @PathVariable

  • URL 자체에 값이 들어가 있는 경우, 파라미터에서 @PathVariable을 사용해 꺼낼 수 있음

    • ex> @GetMapping("/mapping/{userId}") 이면 @PathVariable("userId") String userId
  • 경로 변수의 이름 ( URL을 통해 들어오는 값의 이름 )과 메서드에서 사용되는 파라미터 이름이 같으면 생략할 수 있다

    • ex> @PathVariable("userId") String userId@PathVariable String userId

3-3. 조건 매핑

3-3-1. 파라미터 조건 매핑

@GetMapping(value = "/mapping-param", params = "mode=debug")
  • mode=debug라는 파라미터가 있어야 메소드가 실행됨

  • mapping-param?mode=debug


3-3-2. 헤더 조건 매핑

@GetMapping(value = "/mapping-header", headers = "mode=debug")

3-3-3. 미디어 타입 조건 매핑

// Content-Type 지정
// 맞지 않으면 415 상태 코드 반환
@PostMapping(value = "/mapping-consume", consumes = "application/json")

// Accept 지정
// 안되면 406 상태 코드 반환
@PostMapping(value = "/mapping-produce", produces = "text/html")



4. HTTP 요청 메세지 헤더 조회

  • 어노테이션 기반의 컨트롤러는 헤더를 조회하기 위해 다양한 파라미터를 받아들일 수 있음

  • HttpServletRequest

  • HttpServletResponse

  • HttpMethod : HTTP 메서드를 조회

  • Locale : Locale 정보 ( 언어 정보 )를 조회

  • @RequestHeader MultiValueMap<String, String> headerMap : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회

  • @RequestHeader("헤더이름") String host : 특정 HTTP 헤더를 조회

  • @CookieValue(value = "쿠키이름", required = false) String cookie : 특정 쿠키 조회

  • MultiValueMap

    • 하나의 키에 여러 값을 받을 수 있음

    • HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용

      • keyA=value1&keyA=value2
    • get("keyA")으로 키의 값을 꺼내면 배열로 반환된다

      • 반환형이 List<>
  • @Controller에서 사용 가능한 파라미터 목록 : https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments




5. 쿼리 파라미터, HTML Form으로 전달된 요청 파라미터 조회

  • 스프링 MVC 1편 2번 게시글의 5-2 ~ 5-3 번 참고

5-1. HttpServletRequest 사용

  • 쿼리 파라미터, HTML Form은 형식이 같기 때문에 동일한 메서드로 조회 가능

  • HttpServletRequestrequest.getParameter()를 사용해서 조회

  • HttpServletResponseresponse.getWriter().write()를 사용해서 응답


5-2. @RequestParam 사용

  • @RequestParam("파라미터이름") 반환형 변수명

    • 요청 파라미터에서 데이터를 읽을 때 사용

    • 파라미터 이름과 변수명이 동일하다면 파라미터 이름을 생략 가능

      • @RequestParam 반환형 변수명
    • 파라미터 이름과 변수명이 동일하고 String, int, Integer 등의 단순타입이면 @RequestParam까지 생략 가능

  • 속성

    • defaultValue

      • 파라미터 값이 넘어오지 않은 경우의 기본값을 설정

      • 파라미터 값으로 빈 문자열이 들어와도 기본값으로 설정된다

    • required

      • 파라미터 필수 여부 ( default는 true )

      • 들어오지 않으면 400 상태 코드 반환

      • false이고 값이 들어오지 않으면 null이 들어오는데 이 때 int 형이면 오류 ( 500 상태 코드 반환 )

      • 위의 경우라면 Integer를 사용해야함 ( 객체에는 null이 들어갈 수 있기 때문에 )

  • 모든 파라미터 Map으로 조회

    • @RequestParam Map<String, Object> paramMap : 요청 파라미터의 value가 1개인 경우에 사용

    • @RequestParam MultiValueMap<String, Object> paramMap : 하나의 파라미터에 값이 2개 이상인 경우

    • get("파라미터 이름")을 통해 조회

  • @ResponseBody

    • 클래스에 @Controller가 붙어 있는 경우, 특정 메서드에서 문자를 그대로를 반환하고 싶다면 메서드 레벨에 @ResponseBody 어노테이션을 붙인다

    • 반환되는 문자를 view 이름으로 인식하지 않고, HTTP message body에 직접 내용을 입력한다


5-3. @ModelAttribute 사용

  • 요청 파라미터로 받은 값을 이용해 객체를 만드는 경우에 사용

  • @RequestParam으로 받아서 직접 객체를 생성할 수 있지만 @ModelAttribute를 사용하면 이 과정을 스프링이 자동으로 처리

  • 단> 객체 클래스에 @Data 어노테이션이 붙어 있어야 가능하다

    • @Data@Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor 를 자동으로 적용해준다
  • @ModelAttribute

    • 명시된 객체를 만든다 ➜ 요청 파라미터의 이름으로 객체의 프로퍼티를 찾는다 ➜ setter()를 호출하여 요청 파라미터의 값을 객체에 주입한다

    • @ModelAttribute 클래스이름 객체이름

    • @ModelAttribute를 생략할 수 있음

      • 생략한 경우, String, int, Integer 같은 단순 타입은 @RequestParam으로 인식하고 나머지는 @ModelAttribute로 인식한다



6. Message body를 통해 들어온 데이터 조회

  • 스프링 MVC 1편 2번 게시글의 5-4 ~ 5-6 번 참고

  • 요청 파라미터로 데이터가 넘어오는 방식과는 다르게 message body를 통해 데이터가 넘어오는 경우 @Requestparam, @ModelAttribute를 사용할 수 없음

  • 요청 파라미터를 조회 : @RequestParam , @ModelAttribute

  • HTTP 메시지 바디를 직접 조회 : @RequestBody


6-1. 텍스트 형식 조회

6-1-1. HttpServletRequest, HttpServletResponse

@PostMapping("/request-body-string-v1")
public void requestBodyStringV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
    ServletInputStream inputStream = request.getInputStream();
    String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    ...
}
  • message body의 데이터는 InputStream을 사용해서 직접 읽을 수 있음

  • getInputStream() : message body의 내용을 바이트 코드로 반환

  • StreamUtils.copyToString() : 위의 메서드로 얻은 stream( inputStream )을 String 으로 변환


6-1-2. InputStream

@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
    String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    ...
}
  • InputStream : HTTP 요청 메시지 바디의 내용을 직접 조회

  • Writer : HTTP 응답 메시지의 바디에 직접 결과 출력


6-1-3. HttpEntity

@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
    String messageBody = httpEntity.getBody();

    log.info("message body={}", messageBody);

    return new HttpEntity<>("ok");
}
  • HttpEntity<>는 HTTP header, body 정보를 편리하게 조회할 수 있는 객체

    • getHeaders(), getBody()
  • HttpEntity<>로 응답도 가능

    • 메세지 바디에 정보를 직접 넣는다

    • 헤더 정보를 포함할 수 있다

  • HttpEntity를 상속받은 객체들

    • RequestEntity<>

      • HttpMethod, url 정보가 추가
    • ResponseEntity<>

      • HTTP 상태 코드 설정 가능

6-1-4. @RequestBody

@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {

    log.info("message body={}", messageBody);

    return "ok";
}
  • @RequestBody : HTTP 요청 메세지의 body를 조회

  • @RequestHeader : 요청 메세지의 헤더를 조회할 때 사용

  • @ResponseBody : 응답 결과(반환되는 값)를 HTTP 메시지 바디에 직접 담아서 전달

  • HttpEntity, @RequestBody를 사용하면 스프링 MVC 내부에서 HTTP message body를 읽어서 문자나 객체로 변환해서 전달해준다

    • 이 때, HTTP 메세지 컨버터 기능을 사용

    • 위에서 String으로 지정했으니 String으로 변환된다

    • 단> HttpEntity는 getBody()를 통해 내용을 꺼내와야 함



6-2. JSON 형식 조회

6-2-1. HttpServletRequest, HttpServletResponse

@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {

    ServletInputStream inputStream = request.getInputStream();
    String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

    HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
    ...
}
  • HttpServletRequest를 사용해 직접 HTTP message body에서 데이터를 읽어와 문자로 변환

  • 문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper 를 사용해서 자바 객체로 변환


6-2-2. @RequestBody

@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {

    HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
    ...
}
  • @RequestBody로 데이터를 문자로 읽어온다 ➜ 객체로 변환

@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException {

    log.info("messageBodey = {}", helloData);
    log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());

    ...
}
  • @RequestBody 에 객체를 지정하면 HTTP 메세지 컨버터가 message body의 내용을 객체로 변환시켜준다

    • ObjectMapper를 통해 직접 객체로 변환했던 것을 자동으로 처리해줌
  • @RequestBody는 생략 불가능

    • 생락하면 @RequestParam, @ModelAttribute가 적용됨

6-2-3. HttpEntity

@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) throws IOException {

    HelloData helloData = httpEntity.getBody();
    ...
}
  • HttpEntity도 원하는 형식으로 받아올 수 있지만 getBody()를 통해 꺼내와야 한다

@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return data;
}
  • @ResponseBody 는 반환되는 값을 직접 message body에 넣어주는데, 문자 뿐만 아니라 객체를 message body에 넣어줄 수도 있다

  • @RequestBody 요청

    • JSON 요청 -> HTTP 메시지 컨버터 -> 객체
  • @ResponseBody 응답

    • 객체 -> HTTP 메시지 컨버터 -> JSON 응답



7. 정적 리소스, view template으로 응답 데이터 생성

  • 스프링(서버)에서 응답 데이터를 만드는 방법

    • 정적 리소스

    • view template

    • message body에 직접 입력

7-1. 정적 리소스

  • 웹 브라우저에 정적인 HTML, css, js를 제공할 때 사용

  • 해당 파일을 변경 없이 그대로 제공

  • resources/static 하위에 있는 파일들이 정적 리소스 파일


7-2. view template

  • 웹 브라우저에 동적인 HTML을 제공할 때 view template을 사용

  • 뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달

  • /resources/templates 하위에 있는 파일들이 뷰 템플릿 파일


7-2-1. ModelAndView 이용

@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
    ModelAndView mav = new ModelAndView("response/hello")
            .addObject("data", "hello!");

    return mav;
}
  • response/hello

    • 뷰 템플릿 이름 ( view의 논리이름으로 ModelAndView 객체 생성 )

    • 위의 경로로 view resolver가 실행되어서 view를 찾고 랜더링

  • addObject("data", "hello!")

    • data = model 이름 ( 타임리프에서 data 라는 이름의 모델에서 값을 꺼내서 사용 )

    • "hello!" = model에 들어가는 값


7-2-2. Model 이용

@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
    model.addAttribute("data", "hello!!");
    return "response/hello";
}
  • 클래스 레벨에 @Controller가 붙어있는 경우, String을 반환하면 view의 논리 이름으로 인식

    • view Resolver가 실행되어서 view를 찾고 렌더링한다
  • 위 메서드에 @ResponseBody를 붙이면 message body에 "response/hello" 라는 문자열이 그대로 입력된다

    • view Resolver가 실행되지 않는다

7-2-3. 반환형이 없는 경우

@RequestMapping("/response/hello")
public void responseViewV3(Model model) {
    model.addAttribute("data", "hello!!");
}
  • @Controller를 사용하고 응답 메세지 바디를 처리하는 파라미터 ( HttpServletResponse, Writer )가 없으면 요청 URL을 참고해 view의 논리 이름으로 사용

    • 요청 경로와 view의 논리 이름이 같은 경우, void 반환형 가능

    • 요청경로가 /response/hello이면 view의 논리 이름을 response/hello로 인식

  • 권장하지 않는 방식




8. HTTP 응답 메세지 바디를 통해 데이터 전달

  • HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다

8-1. 텍스트 형식 데이터 전달

8-1-1. HttpServletResponse

// 문자 처리
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException {
    response.getWriter().write("ok");
}
  • HttpServletResponse 객체를 통해서 HTTP 메시지 바디에 직접 ok 응답 메시지를 전달

8-1-2. ResponseEntity<>

@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() throws IOException {
    return new ResponseEntity<>("ok", HttpStatus.OK);
}
  • HttpEntity는 HTTP 메시지의 헤더, 바디 정보를 가지고 있다

  • HttpEntity를 상속받은 ResponseEntity 는 HTTP 응답 코드를 설정할 수 있다


8-1-3. @ResopnseBody

@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
    return "ok";
}
  • @ResponseBody 를 사용하면 view를 사용하지 않고, HTTP 메시지 컨버터를 통해 HTTP 메시지를 직접 입력


8-2. JSON 형식 데이터 전달

8-2-1. ResponseEntity<>

// JSON 처리
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(20);

    return new ResponseEntity<>(helloData, HttpStatus.OK);
}
  • ResponseEntity 를 반환하면 HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환

8-2-1. 어노테이션 이용

@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(20);

    return helloData;
}
  • @ResponseBody를 사용하면 응답 코드 설정이 어렵기 때문에 @ResponseStatus(HttpStatus.OK) 어노테이션을 사용해 응답 코드 설정



9. HTTP Message Converter

9-1. 설명

  • HTTP message body에서 JSON 데이터를 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다

  • HttpMessageConverter는 인터페이스이고 이를 구현한 클래스가 많이 존재한다

  • 스프링 MVC는 아래의 경우에 HTTP message Converter를 자동으로 적용한다

    • @RequestBody, HttpEntity<RequestEntity>

    • @ResponseBody, HttpEntity<ResponseEntity>

    • 응답의 경우 클라이언트의 HTTP Accept 해더와 서버의 컨트롤러 반환 타입 정보를 조합해서 HttpMessageConverter 가 선택

  • @ResponseBody 를 사용

    • message body에 문자 내용을 직접 반환

    • viewResolver 대신에 HttpMessageConverter 가 동작

    • 기본 문자처리: StringHttpMessageConverter

    • 기본 객체처리: MappingJackson2HttpMessageConverter

    • byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있음


9-2. HTTP Message Converter Interface

public interface HttpMessageConverter<T> {

	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();

	default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
		return (canRead(clazz, null) || canWrite(clazz, null) ?
				getSupportedMediaTypes() : Collections.emptyList());
	}

	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}
  • HTTP Message Converter는 요청과 응답 둘 다 사용된다

  • canRead() , canWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크

  • read() , write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능


9-3. 스프링부트 기본 메세지 컨버터

  • 스프링부트는 대상 클래스 타입과 미디어 타입을 체크해서 사용 여부를 결정

    • 미디어 타입 : HTTP 요청의 경우 Content-Type
  • 만족하지 않으면 다음 메시지 컨버터로 우선순위가 넘어간다

  • 0순위 : ByteArrayHttpMessageConverter

    • byte[] 데이터를 처리

    • 클래스 타입 : byte[] , 미디어타입 : */* ( 모든 미디어 타입을 받아 들일 수 있음 )

    • 응답 시 쓰기 미디어 타입 : application/octet-stream

  • 1순위 : StringHttpMessageConverter

    • String 문자로 데이터를 처리

    • 클래스 타입 : String , 미디어타입 : */*

    • 응답 시 쓰기 미디어 타입 : text/plain

  • 2순위 : MappingJackson2HttpMessageConverter

    • application/json

    • 클래스 타입 : 객체 또는 HashMap , 미디어타입 : application/json 관련

    • 응답 시 쓰기 미디어 타입 : application/json


9-4. HTTP 요청 데이터 읽기

  • HTTP 요청이 온 후 컨트롤러에서 @RequestBody , HttpEntity 파라미터를 사용한다고 가정하면

  • 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 를 호출

    • 대상 클래스 타입을 지원하는가 ( @RequestBody의 대상 클래스 )

    • HTTP 요청의 Content-Type을 지원하는가

  • canRead() 조건을 만족하면 read() 를 호출해서 객체 생성하고, 컨트롤러의 파라미터로 반환


9-5. HTTP 응답 데이터 생성

  • 컨트롤러에서 @ResponseBody , HttpEntity 로 값이 반환된다고 가정하면

  • 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 를 호출

    • 대상 클래스 타입을 지원하는가 ( return의 대상 클래스 )

    • HTTP 요청의 Accept 미디어 타입을 지원하는가 ( 더 정확히는 @RequestMapping 의 produces )

      • 클라이언트가 읽을 수 있는가를 판단
  • canWrite() 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성




10. RequestMappingHandlerAdapter

10-1. 설명

@Controller
public class RequestBodyStringController {

    @PostMapping("/request-body-string-v1")
    public void requestBodyStringV1(HttpServletRequest request, HttpServletResponse response) throws IOException { ... }

    @PostMapping("/request-body-string-v2")
    public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException { ... }

    @PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException { ... }

    @ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV4(@RequestBody String messageBody) throws IOException { ... }
}
  • 어노테이션 기반 컨트롤러에서 파라미터로 HttpServletRequest, Model, @RequestParam, @RequestBody, HttpEntity 등을 사용했는데 이것을 사용할 수 있으려면 누군가가 데이터를 해당 파라미터에 맞게 전달해주어야 한다

  • 이런 것을 처리해주는 것이 바로 ArgumentResolver 이다

  • 어노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter 는 바로 이 ArgumentResolver 를 호출해서 컨트롤러가 필요로 하는 다양한 파라미터의 값(객체)을 생성하고 모든 파라미터의 값이 준비되면 컨트롤러를 호출하면서 값을 넘겨준다


10-2. ArgumentResolver

public interface HandlerMethodArgumentResolver {

	boolean supportsParameter(MethodParameter parameter);

	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}
  • ArgumentResolver의 정확한 이름은 HandlerMethodArgumentResolver

  • 핸들러( Controller )가 필요로 하는 파라미터의 값( 객체 )을 생성하는 역할을 수행

  • RequestMappingHandlerAdapter가 호출함으로써 실행되고, 생성한 객체를 RequestMappingHandlerAdapter에게 넘겨준다

  • supportsParameter() : 핸들러( Controller )가 받아야 하는 파라미터 정보를 지원하는지 판단

  • 지원하는 경우, resolveArgument()를 통해 객체를 만들어서 반환한다

  • 스프링은 30개가 넘는 ArgumentResolver 를 기본으로 제공


10-3. 동작 방식

  • RequestMappingHandlerAdapter는 @RequestMapping을 사용하는 어노테이션 기반의 컨트롤러에서 사용되는 핸들러 어댑터

  • RequestMappingHandlerAdapter 가 ArgumentResolver 를 호출

    • ArgumentResolver 의 supportsParameter() 를 호출해서 해당 파라미터를 지원하는지 체크
  • 지원한다면 resolveArgument() 를 호출해서 핸들러( Controller )가 필요로 하는 객체를 생성

  • 이렇게 생성된 객체들이 컨트롤러 호출 시 넘어가게 된다


10-4. ReturnValueHandler

public interface HandlerMethodReturnValueHandler {

	boolean supportsReturnType(MethodParameter returnType);

	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}
  • ReturnValueHandler : 핸들러가 값을 반환할 때 응답 값을 변환하고 처리해준다

  • 핸들러에서 String으로 view 이름을 반환해도 동작하는 이유가 ReturnValueHandler 덕분

  • 스프링은 ModelAndView , @ResponseBody , HttpEntity, String 등을 처리하는 10여 개의 ReturnValueHandler가 있다


10-5. 스프링 MVC와 HttpMessageConverter

  • @RequestBody@ResponseBody 를 컨트롤러에서 사용하는데 이들은 모두 HttpMessageConverter 를 사용한다

  • 즉, @RequestBody, @ResponseBody, HttpEntity 를 사용하는 경우 ArgumentResolverReturnValueHandler 가 메세지 컨버터를 사용한다

  • 요청 시

    • @RequestBody, HttpEntity 등을 처리하는 서로 다른 ArgumentResolver 가 있다 ( 여러 개 존재 )

    • ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성한다

  • 응답 시

    • @ResponseBody, HttpEntity 등을 처리하는 서로 다른 ReturnValueHandler 가 있다 ( 여러 개 존재 )

    • ReturnValueHandler 들이 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다

  • 참고

    • HttpEntityMethodProcessor : HttpEntity 가 있을 때 사용하는 ArgumentResolver

    • RequestResponseBodyMethodProcessor : @RequestBody, @ResponseBody 가 있을 때 사용하는 ArgumentResolver


10-6. 기능 확장

  • 스프링은 ArgumentResolver, ReturnValueHandler, HttpMessageConverter 를 모두 인터페이스로 제공하기 때문에 필요한 경우 기능을 확장할 수 있다

  • 실제로 기능 확장할 일은 많지 않지만 필요하다면 WebMvcConfigurer를 상속받아서 스프링 빈으로 등록하면 된다

0개의 댓글