스프링 MVC - 기본기능

고동현·2024년 4월 24일
0

스프링 MVC

목록 보기
5/13

요청매핑


우선 RestController로 등록해주었다. 일단, RestController내부에 컨트롤러 애노테이션이 있어서 디스페쳐 서블릿이 핸들러 맵핑할때 RequestMappingHandlerMapping에서 컨르롤러 인식해서 맵핑해준다.

어쨋든, RestController를 사용한 이유는 @Controller같은 경우에 반환형이 String이면 뷰이름으로 인식하고 뷰를 찾고 렌더링 된다.
@RestController는 반환값으로 뷰를 찾는게 아니라 HTTP메시지 바디에 바로 입력한다. 따라서 return되는 문자열을 렌더링 할 수 있다.


RequestMapping을 이용해 /hello-basic URL이 호출되면 helloBasic메서드가 실행되도록 매핑하였다.


@RequestMapping에 method속성으로 Http메서드를 지정하지 않으면 HTTP메서드와 무관하게 전부 호출되며느로 method를 지정한다.


HTTP메서드를 축약하 애노테이션
코드 내부에서 @RequestMapping과 method가 있는 것을 알 수 있다.


@RequestMapping은 URL경로를 템플릿화 할 수 있는데 @Pathvariable을 사용하면 매칭되는 부분을 편리하게 조회가능하다.


header에 mode=debug가 있어야 메서드가 실행된다.

미디어타입 조건 매핑 - HTTP 요청 ContentType consume

 @PostMapping(value = "/mapping-consume", consumes = "application/json")
 public String mappingConsumes() {
    log.info("mappingConsumes");
 return "ok";
 }

HTTP 요청의 Content-Type 헤더를 기반으로 매핑한다. 만약 Content-Type이 application.json이 아니라면 매핑되지 않는다. ex) text/plain등

미디어타입 조건 매핑 - HTTP 요청 Accept,produce

@PostMapping(value = "/mapping-produce", produces = "text/html")
 public String mappingProduces() {
    log.info("mappingProduces");
 return "ok";
 }

HTTP요청이 들어왔을때 Accept헤더를 기반으로 매핑한다.
즉 Accept헤더타입이 text/html이어야지 매핑되어 사용할 수 있다. 만약 Accept=application.json이면 매핑안된다.

요청 매핑 - API예시

회원관리 API 예시

  • 회원 목록 조회: Get /users
  • 회원 등록: Post /users
  • 회원 조회: Get /users/{userId}
  • 회원 수정: Patch /users/{userId}
  • 회원 삭제: DELETE /users/{userId}


RequestMapping을 class단에 붙이면 url을 조합해서 사용할 수 있다.

HTTP 요청 - 기본, 헤더 조회

HTTP헤더 정보 조회 방법

결과

헤더에 있는 정보들을 확인 할 수 있다.
request, response, httpMethod, 지역, headerMap,host cooki정보까지

  • HTTPServletRequest
  • HttpServletResponse
  • HttpMethod: HTTP 메서드조회, get,post등등
  • Locale: Locale정보 조회
  • @RequestHeader MultiValueMap<String, String> hederMap
    모든 HTTp 헤더를 MultiValueMap 형식으로 조회한다.
    MultivalueMap->Map과 유사한데, 하나의 키에 여러개의 값을 받을 수 있다.
    HTTP header,HTTP 쿼리파라미터와 같이 하나의 키에 여러개의 값을 받을 때 사용한다.
    keyA=value1 & keyA = value2 이런식으로
  • @RequestHeader("host") String host
    특정한 HTTP헤더를 조회한다.
  • CookieValue(value = "myCookie", required = false) String cookie
    특정 쿠키를 조회한다.
    필수값 여부, 기본값등을 지정할 수 있다.

HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

이전에 HTTP 요청 데이터를 조회하는 방식을 다시 떠올려 보자.

클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 3가지 방식을 사용한다.

  • GET 쿼리 파라미터
    /url?username=hello&age=20
  • POST - HTML Form
    content-type: application/x-www=form-urlencoded
    메시지 바디에 쿼리 파라미터 형식으로 전달 username=kim&age=20
  • HTTP messageBody에 직접 데이터를 담아서 요청
    JSON형식

GET 쿼리 파라미터

HTTPServletRequest가 제공하는 방식으로 조회하였다.

Post - HTML Form
동일한 컨트롤러의 메서드를 사용하면 된다. 왜? 메세지 바디에 들어있는 데이터도 쿼리 파라미터 형식이므로,
html form만들기

Get 쿼리 파라미터와 동일한 url로 보냈다.

동일하게 요청데이터 조회가 가능하다

HTTP 요청 파라미터 - @RequestParam

@RequestParam을 사용하면 매우 편리하게 요청 파라미터를 사용할 수 있다.


단 요청 파라미터의 key값과 @RequestParam의 변수명이 동일해야한다.
다르게 하고 싶으면
@ReqeustParam("파라미터 이름") String 내가 원하는 변수명으로 해야한다.

또한 파라미터 필수 여부를 정할 수 있는데,

 @ResponseBody
 @RequestMapping("/request-param-required")
 public String requestParamRequired(
    @RequestParam(required = true) String username,
    @RequestParam(required = false) Integer age) {
    log.info("username={}, age={}", username, age);
     return "ok";
 }

이런식으로 required를 사용하여 true,false를 지정할 수 있다. 여기서 true가 기본값이고, false를 지정하면, 쿼리 파라미터에 해당 age변수를 넣지 않아도 된다.

그러나 여기서 int가 아니라 Integer로 선언한게 보이는데,
int는 기본형이라 null이 들어가지 못한다. 그래서 age를 적지 않더라도 null이 들어갈 수 있게 객체로 Integer로 선언하였다.

만약에 요청 파라미터에 값을 적지 않으면 default value가 들어오게 하고 싶으면

 @ResponseBody
 @RequestMapping("/request-param-default")
 public String requestParamDefault(
 @RequestParam(defaultValue = "guest") String username,
 @RequestParam(defaultValue = "-1") int age) {
    log.info("username={}, age={}", username, age);
return "ok";
 }

이런식으로 defaultValue를 사용해서 지정해주면된다. 이때는 age가 int인데 그 이유가 안적어도 -1이 할당 되기 때문이다.

또한 빈 문자열도 처리해주는 데 만약 localhost:8080/request-param-default?username=
이런식으로 빈문자를 적어도 defaultValue를 알아서 넣어준다.

파라미터를 Map으로 조회하기, requestParamMap

 @ResponseBody
 @RequestMapping("/request-param-map")
 public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
     log.info("username={}, age={}",    
     paramMap.get("username"),
     paramMap.get("age"));
     return "ok";
 }

요청파라미터들을 @RequestParam Map 으로 받을 수 있다.
만약에 key 1개에 여러개의 값이 들어온다면,
ex username=kim&username=go Map이 아니라 MultivalueMap을 써야한다.

파라미터의 값이 하나임이 확실하면 Map을 쓰면되는데 대부분 Map을 쓴다.

HTTP 요청 파라미터 - @ModelAttribute

@RequestParam String username;
@RequestParam int age;

HelloData data = new HelloData();
data.setUsername(username);
data.setAge(age);

실제로 스프링이 요청파라미터로 객체를 받을 수 는 없다. 위의 과정으로 대신해줄 뿐이다.

이 과정을 자동적으로 해주는게 @ModelAttribute이다.

우선 요청 파라미터를 바인딩 받을 객체를 사용하자.

여기서 Data 에노테이션을 쓴 이유가, @Getter, @Setter,@ToString, @RequiredArgsConstructor등이 다 들어있다.


이런식으로 하면 끝난다. @ModelAttribute에노테이션이 있으면, 우선 HelloData 객체를 생성한다.
요청 파라미터 이름으로 HelloData객체 프로퍼티를 찾는다.

프로퍼티가 뭐냐면,
객체에 getUsername(),setUsername()메서드가 있으면 username이라는 프로퍼티가 있다는 것이다.

즉 username이라는 프로퍼티의 값을 변경하면 setUsername()이 호출되고, 조회하면 getUsername()이 호출되는것이다.

고로 요청파라미터 username을 가지고 HelloData에 username프로퍼티를 찾는다.
그다음에 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)한다.
=>파라미터 이름이 username이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.

만약에 int age인데 문자열 abc를 입력하면 어떻게 될까?=>BindingException이 발생

여기까지, 결국 @RequestParam,@ModelAttribute는 쿼리파라미터 방식의 요청 데이터를 가져오는데 쓰이는 것이다.

  • GET 쿼리 파라미터
    /url?username=hello&age=20
  • POST - HTML Form
    content-type: application/x-www=form-urlencoded
    메시지 바디에 쿼리 파라미터 형식으로 전달 username=kim&age=20

여기 두가지에 해당하는것을 배운것이다.
다음 시간에서는 HTTP message Body에 데이터를 직접 담는 방식을 확인 해볼 것이다.

HTTP 요청 메시지 - 단순 텍스트

요청 파라미터와 다르게, HTTP 메시지 바디를 통해서 데이터가 직접 넘어오는 경우는 @RequestParam,@ModelAttribute를 사용할 수없다.
어? 근데 Post HTML Form은 메시지 바디에 쿼리 파라미터 형식으로 전달하는거 아닌가요?
맞다. 그래서 그 type을 보고 이 경우에는 처리를 해준다.

단순한 텍스트 메시지를 HTTP메시지 바디에 담아서 전송하고 읽어보자.

request.getInputStream부터 copyToString으로 캐릭터 셋을 정해서 메시지 바디에있는 값을 가져온다.
여기서 String은 byte코드이므로, 이걸 변환할때 어떤 캐릭터 셋을 정할지 utf-8을 버전을 설정했다.
만약에 이걸 안하면, OS의 기본 버전을 사용하던가 이럴것이다.

근데 우리는 inputStream만 필요하지 HttpServletRequest의 모든게 필요하지는 않다.


스프링 MVC는 다음 파라미터를 지원한다.

  • InputStream(Reader): HTTP요청 메시지 바디의 내용을 직접 조회
  • OutputStream(Writer): HTTP 응답 메시지 바디에 직접 결과 출력

그런데 이런 Stream을 가져오는게 불편하니까 HttpEntity기능을 제공한다.

HttpEntity: HTTP header,body 정보를 편리하게 조회
메시지 바디 정보를 적접 조회
요청 파리미터를 조회하는 기능과 관계 없음 @RequestParam x @ModelAttribute x

HttpEntity는 응답에도 사용 가능
메시지 바디 정보 직접 반환
헤더 정보 포함 가능
view 조회 x

그런데 이런 궁금증이 있을 수 있다. 도대체 requestBodyStringV3메서드의 파라미터인 httpEntity에 < String>값이 어떻게 들어가길래, getBody()를 통해서 가져올 수 있을까?

결국 스프링 MVC내부에서 HTTP메시지 바디를 읽어서 문자나 객체로 변환해서 전달해 주는데, 이때 HTTP메시지 컨버터가 바꿔서 준다. 이 내용은 뒤에서 다시 설명할 것이다.

@RequestBody

@RequestBody를 사용하면, HTTP 메시지 바디 정보를 편리하게 조회 할 수 있다.

HTTP메시지 바디 정보를 직접 가져와서 조회하는 @RequestBody와 동일하게 HTTP메시지 바디에 직접 정보를 담아서 전달하는 @ResponseBody가 세트이다.
이경우에는 당연히 HTTP메시지 바디에 직접 콱 data를 넣어서 전달하므로 view를 사용하지 않는다.

우리 이전에 그냥 "ok"라는 string을 반환하고 싶을때 Mapping위에다가 ResponseBody썼던거 기억하는가?
바로 Controller는 String반환형이면 상대경로인 return값을 찾아서 viewResolver를 호출해서 렌더링 하는데 @ResponseBody가 있으면 그냥 메시지 바디에다가 콱넣어서 반환하는것이다.

결국 다시 정리를 해보자면,

  • 요청 파라미터를 조회하는기능: @RequestParam, @ModelAttribute
  • HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody
  • HTTP 메시지 바디를 직접 조회함과 더불어 헤더정보도 필요한경우: HttpEntity를 사용하거나 @RequestHeader사용

HTTP 요청 메시지 - JSON

마지막으로 HTTP API에서 주로 사용하는 JSON 데이터 형식을 조회해보도록 하자.
우선, JSON 데이터를 객체로 변환하기위해서 ObjectMapper를 사용하였다.


HttpServletRequest에 있는 inputStream을 가져와서 string으로 변환하고, ObjectMapper를 사용해서 객체로 바꿔주었다.

@RequestBody이용

@RequestBody애노테이션을 사용하면, 직접만든 객체를 지정할 수 있다.
그렇다면 HTTP메시지 바디에 들어있는 JSON형식은 어차피 문자열인데 어떤방식으로 객체에 들어 갈 수 있는것 일까?
바로, HttpEntity,@RequestBody를 사용하면, HTTP메시지 컨버터가 HTTP메시지 바디의 내용을 우리가 원하는 문자나 객체로 변환해준다.

마찬가지로 HTTPEntity를 사용해도 된다

즉, @RequestBody요청이 들어오면
JSON요청 -> HTTP 메시지 컨버터 -> 객체로 바꿔서 넣어줌

HTTP메시지 컨버터는 뒤에서 다시 자세히 설명할 것이다.

이것 뿐만 아니라, ResponseBody를 적으면 문자열로 반환 할 수 있다고 하였는데 HTTP메시지 컨버터가 JSON형식으로 응답도 가능하게 해준다.


@Response응답
객체=>HTTP메시지 컨버터 -> JSON응답

전체적으로 다시 처음부터 정리를 해보자면,

  • 요청 파라미터를 조회하는기능: @RequestParam, @ModelAttribute
  • HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody
  • HTTP 메시지 바디를 직접 조회함과 더불어 헤더정보도 필요한경우: HttpEntity를 사용하거나 @RequestHeader사용
  • Json형식 요청을 조회하는경우: @RequestBody, HTTPEntity사용

HTTP 응답 - 정적 리소스, 뷰 템플릿

이번에는 응답에 대해서 알아보도록하겠다.

  • 정적리소스: 웹브라우저에 정적인 HTML,CSS,js를 제공할때는 정적 리소스를 사용한다.
  • 뷰템플릿 사용: 웹 브라우저에 동적인 HTMl을 제공할 때는 뷰 템플릿을 사용한다.
  • HTTP 메시지 사용: HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP메시지 바디에 JSON같은 형식으로 데이터를 실어 보낸다.

정적리소스
스프링 부트는 /static, /public, /resources, /META-INF/resources 경로에 있는 정적리소스를 제공한다.

serc/main.resources는 리소스를 보관하는 곳이고, 또한 클래스 패스의 시작 경로이다.

따라서 src/main/resources/static/basic/hello-form.html 경로에 파일이 들어있다면,
웹브라우저에서 http://localhost::8080/basic/hello-form.html을 실행하면 그대로 띄워준다.

뷰템플릿
뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달한다.
스프링 부트는 기본 템플릿 경로를, src/main/resources/templates로 제공한다.

뷰템플릿생성

뷰템플릿 호출 컨트롤러

엇 여기서 ModelAndView만 반환했는데 어떻게 뷰템플릿을 거쳐서 html이 렌더링되는거냐? 라고 물어보신다면 너무 섭섭하다.
지난편에서 다시 보면
dispatcherServlet이 이 ModelAndView를 받으면 이 "response/hello"라는 논리적 뷰 경로를 뷰 리졸버를 호출해 절대경로로 바꾸고, 그다음에 render메서드를 호출해서 렌더링을 해준다. 이해가가지 않는다면 지난편을 다시 보고오자
MVC 프레임워크 만들기

또한 이것도 가능하다.

엇 이것은 그럼 어떻게 가능한가?
@Controller에서 반환형이 String이면 뷰에서 논리적이름이 /response/hello가 되고, 이 경로로 뷰 리졸버가 실행되어서 뷰를 찾고 렌더링한다.
만약 @ResponseBody가 있으면 뷰 리졸버를 실행하지 않고, HTTP메시지 바디에 직접 response/hello라는 문자열을 집어넣게 되는것이다.

또한 여기서 궁금한게 v1처럼 model을 직접넘겨준게아니라 model에다가는 addAttribute밖에 안했는데 도대체 어떻게 템플릿에서 data를 꺼내 쓰는걸까?

바로, 파라미터로 받은 model은 참조값이고 주솟값이 동일하기 때문에 이 동일한 주솟값으로 템플릿에서 찾아서 꺼내쓰는것이다.

HTTP 응답 - HTTP API, 메시지 바디에 직접 입력

이미 응답하는과정은 했었으므로, 이번에 다시 정리하는 과정을 살펴보겠다.

@Slf4j
 @Controller
 //@RestController
 public class ResponseBodyController {
 @GetMapping("/response-body-string-v1")
 public void responseBodyV1(HttpServletResponse response) throws IOException 
{
        response.getWriter().write("ok");
 }

ok를 직접 반환한다.

 @ResponseBody
 @GetMapping("/response-body-string-v3")
 public String responseBodyV3() {
 return "ok";
 }

ResponseBody애노테이션을쓰면 뷰 템플릿을 찾는게아니라 HTTP메시지 바디에 직접 콱넣어서 반환한다.

 @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를 사용하면 좋은점이 상태코드를 지정할 수 있다.
어? 그런데 이건 ResponseBody가 없는데 어떻게 이게 뷰템플릿이 아니라 helloData를 그대로 반환하는거지?
바로 ResponseEntity를 반환하더라도 HTTP메시지 컨버터를 통해서 JSON형식으로 변환되어서 반환된다.

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

이런식으로 객체 자체를 반환할 수 있는데,
객체를 반환하는 경우 ResponseEntity처럼 상태코드 지정이 어려우므로, ResponseStatus 애노테이션을 사용하고
ResponseBody를 사용하면, 객체 자체를 반환해도 된다.

참고
@RestController
@Controller대신에 RestController 애노테이션을 사용하면, 해당 컨트롤러에 전부 @ResponseBody가 적용된다.
그러면 모든 컨트롤러의 반환이 뷰 템플릿을 찾아서 렌더링 하는게 아니라 반환값을 HTTP메시지 바디에 콱박아서 이 바디에있는것을 렌더링해준다.

HTTP메시지 컨버터

Json형식이 들어오거나, 혹은 JSON형식이 나가거나, ResponseBody형식으로 string등을 콱 박아서 나갈때는 결국 HTTP메시지 컨버터가 필요하다.
이번시간에는 HTTP메시지 컨버터에 대해서 알아보자.

@ResponseBody 사용 원리

요청이 들어오면고, @ResponseBody 애노테이션이 달려있으면, viewResolver대신에 HttpMessageConverter가 동작한다.

HttpMessageConverter는 인터페이스로 설정되있는데 당연하게, 이 컨버터의 구현체가 여러개 존재하기 때문이다.
JsonConverter,StringConverter 등등..

결국,
스프링 MVC는 다음의 경우에 HTTP 메시지 컨버터를 적용한다.

  • HTTP 요청: @RequestBody,HttpEntity(RequestEntity)
  • HTTP 응답: @ResponseBody, HttpEntity(ResponseEntity)

결국, 맨처음에 요청데이터 보내기에서, 1,2번에 해당하는 Pathvariable이나, HTMlform data요청은 모델에트리뷰트나, pathvariable을 쓰면되지만,
HTTP메시지 바디에 직접 String을 넣거나 Json형식을 넣을때는, @RequestBody나 HttpEntity를 사용하게 되는데 이때 HTTP메시지 컨버터가 동작하는것이다.

반환도 마찬가지로, HTTP메시지 바디에 콱 넣는 ResponseBody 혹은 HTTPEntity반환할때 HTTp메시지 컨버터가 동작하게 된다.

HTTP메시지 컨버터

package org.springframework.http.converter;
 public interface HttpMessageConverter<T> {
 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
 boolean canWrite(Class<?> clazz, @Nullable 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;
 }

HTTP메시지 컨버터는 HTTP요청과 응답 둘다 사용이 되고,
이런식으로 canRead와 canWrite메서드가 있다.

여기서 MediaType파라미터가 있는데, 해당 클래스와 미디어타입을 지원하는지 두가지를 체크한다.

스프링 부트 기본 메시지 컨버터 우선순위
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter

  • 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메시지 바디에있는 것의 type
쓰기 미디어타입: 서버가 보내는 응답 HTTP메시지 바디에 있는것의 Type

당연히 서로 받을 수 있는 Type이 일치하지 않는경우에는 요청,응답을 받을 수 없다.

예시를 들어보자
content-type: application/json

@RequestMapping
void hello(@RequestBody String data){}

HTTP요청 데이터 읽기
1. HTTP요청이 들어오고, 컨트롤러에서 @RequestBody, HTTPEntity 파라미터가 있는지 확인한다.
2. 만약 있다면, 메시지 컨버터가 작동해서 메시지를 읽을 수 있는지 확인하기 위해서 canRead()를 호출한다.
3. 그러면 @RequestBody의 해당 클래스를 확인한다.
여기서는 String이므로, 1번의 Byte패스 2번의 String통과
4. 클래스뿐만아니라 Content-Type의 미디어 타입을 지원하는지 확인한다. application/json은 /에 포함되므로 조건에 만족하므로 read()를 호출해서 객체를 생성하고, 컨트롤러의 파라미터로 전달한다.

content-type: application/json
@RequestMapping
void hello(@RequestBody HelloData data) {}
이경우는 mappingJackson2HttpMessageConverter가 작동한다.

만약에 미디어 타입이 안맞으면?
content-type: text/html
@RequestMapping
void hello(@RequestBody HelloData data) {}
이런식으로 mappingJackson2HttpMessageConverter를 작동시키려했는데 content-type이 맞지 않다. 그러면 컨버터가 작동하지 않는다.

Http 응답 데이터 생성

  • 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환된다.
  • 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite()를 호출한다.
  • 대상 클래스 타입을 지원하는지 확인한다.
    예) return의 대상 클래스 (byte[], String, HelloData)
    HTTP 요청의 Accept 미디어 타입을 지원하는가.(더 정확히는 @RequestMapping의 produces)--클라이언트 측에서 이 return 대상 클래스 타입을 받을수 있는가? 확인
    예) text/plain , application/json , /
  • canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.

요청 매핑 핸들러 어뎁터 구조

그렇다면 결국 HTTP메시지 컨버터는 스프링 MVC에서 어디서 사용되는걸까?

만약 MVC구조를 잘 모른다면 이전글을 보고오자.


순서를 적어보자.
1. Controller의 컴포넌트에 의해 빈으로 등록
2. URL로 요청이 오면, 핸들러 조회를 하면서 핸들러 매핑
근데 여기 Controller애노테이션 썻으므로, 올타쿠나 RequestMappingHandlerMapping이 매핑된다.2
3. 그다음에 이 핸들러를 처리할수있은 어댑터가 매핑된다.
RequestMappingHandlerAdaptor가 매핑됨
4. 그러면 handle메서드를 호출할때, 이 RequestMappingHandlerMapping의 process에 해당하는 이 Controller의 메서드가 실행되는데
5. 문제는 우리가 옛날에 V3버전을 보면

이런식으로 process메서드에 필요한 파라미터들을 넣어서 호출해줬다.
각 process메서드는 이런모습이다.

우리가 만든 컨트롤러랑 똑같다.

즉 결국 뭐냐면, process메서드가 결국 우리가 호출하고자하는 modelAttributev1메서드이고 여기서 필요한 파라미터를 미리 핸들러어댑터가 가지고 이런식으로
ModelView mv = adapter.handle(request, response, handler);
메서드를 호출해야한다는것이다.

그럼 여기서 파라미터를 누가 처리를 해주냐? 바로 ArgumentResolver가 처리해준다.

argumnetResolver가 HttpServletRequest,model뿐만아니라, RequestParam modelAttribute까지 전부 만들수있는 파라미터값, 객체를 생성해주는데
결국, RequestMapping핸들러 어댑터가 바로 ArgumentResolver를 호출해서 컨트롤러가 필요로하는 다양한 파라미터값을 생성하고, 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.

그리고 이 값을 받으면 RequestMapping핸들러 어댑터가 파라미터를 가지고 핸들러를 호출한다.

ArgumentResolver는 정확히 말하면 handlerMethodArgumentResolver인데

supports메서드로 해당 파라미터를 지원하는지 체크한 후에, 지원을 하면 resolveArgument()를 호출해서 실제 객체를 생성하는것이다.
그리고 이 생성된 객체가 바로 컨트롤러 호출시 넘어가는것이다.

ReturnValueHandler는 HandlerMethodReturnVlaueHandler인데,
ArgumnetResolver와 비슷하다.

우리는 handle메서드를 호출하고 반환을 반드시 ModelAndView로 해줘야하는데

우리는 String값으로 논리적 viewPath를 반환할수도, ModelAndView를 반환할수도, @ResponseBody를 반환할수도 HttpEntity로 반환할 수있는데

이 ReturnValueHandler(HandlerMethodReturnVlaueHandler)를 통해서 ModelAndView로 반환된다.

그러면, HTTP 메시지 컨버터 위치는 도대체 어디냐?
바로 ArgumentResolver와 ReturnValueHandler가 HTTP메세지 컨버터를 사용한다.

ArgumentResolver가 파라미터에 해당하는 객체를 만들때, 단순하게 String이라던지 처리할수있는것들은 그냥 쓰고, 만약에 @RequestBody,HttpEntity같은 HttpMessageBody에서 데이터를 꺼내서 처리를해야하면 HTTP메시지 컨버터를 사용하여서 만들어진 객체를 핸들러 어댑터한테 주는것이다.

ReturnVlaueHandler도 마찬가지다 그냥 String이나 상태코드를 반환하는건 그냥 쓰면되는데, 응답 데이터를 HTTP메시지에 입력하는 @ResponseBody나 HTTPEntity같은것을 처리하기위해서는 HTTP메시지컨버터를 사용하여서 ModelAndView를 만들어서 반환해주는것이다.

스프링 MVC는 @RequestBody,@ResponseBody가 있으면 RequestResponseBodyMethodProcess를 사용하고
HttpEntity가 있으면 HTTPEntityMethodProcess를 사용한다.

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글