김찬진의 개발 블로그

[23/06/06] DispatcherServlet, HandlerMapping, HandlerAdapter, ModelAndView, ViewResolver, View, RequestMappingHandlerAdapter, ArgumentResolver, ReturnValueResolver 본문

1일1배움/Spring (김영한 님)

[23/06/06] DispatcherServlet, HandlerMapping, HandlerAdapter, ModelAndView, ViewResolver, View, RequestMappingHandlerAdapter, ArgumentResolver, ReturnValueResolver

kim chan jin 2023. 6. 6. 20:49

개념들이 헷갈려 위 개념들을 포함한 스토리를 만들어서 메커니즘을 정리해보았습니다.

 

1. 클라이언트의 요청이 들어오면 DispatcherServlet이 요청을 수신합니다.


2. DispatcherServletHandlerMapping에게 요청을 전달하여 적절한 handler(=controller)를 찾습니다.


3. HandlerMapping은 요청을 기반으로 적절한 handler(=controller)를 찾아 반환합니다.


4. DispatcherServlet은 반환된 handler(=controller)를 실행할 수 있는 적절한 HandlerAdapter에게 전달합니다. 스프링은 HandlerAdapter 인터페이스를 구현한 다양한 클래스들을 갖고 있습니다. 예를 들어 만약 @RequestMapping 애노테이션을 사용하는 handler(=controller)의 경우,  RequestMappingHandlerAdapter에게 전달되고, 만약 @RestController 애노테이션을 사용하는 handler(=controller)의 경우, RequestMappingHandlerAdapter에게 전달됩니다.

 

@RequestMapping(그 자식 애노테이션 @PostMapping, @GetMapping 등도 포함) 애노테이션을 사용하는 handler(=controller)의 경우, handler(=controller)는 RequestMappingHandlerAdpater을 호출하여 handler(=controller)가 정상작동하기 위한 작업을 수행하도록 합니다. 그 작업의 예시는 다음과 같습니다. 

 

(Request, Response 모두 3가지 방법이 있습니다.)

 

<요청>

1. 쿼리 파라미터에 데이터를 포함해서 GET 요청

 ( @RequestParam 단순타입 / @RequestParam Map / @RequestParam MultiValueMap / @ModelAttribute )

2. HTML Form = HTTP 메세지 바디에 쿼리 파라미터로  POST 요청

 ( @RequestParam 단순타입 / @RequestParam Map / @RequestParam MultiValueMap / @ModelAttribute )

3-1. HTTP message body에 문자열 데이터를 직접 담아서 요청

(@RequestBody 단순타입/ @RequestBody 객체 / HttpEntity)

3-2. HTTP message body에 JSON 데이터를 직접 담아서 요청

(@RequestBody 단순타입 / @RequestBody 객체 / HttpEntity)

 

<응답>

1. 정적 리소스

2. 뷰 템플릿 (단, 반환값이 문자열일 경우 @ResponseBody 붙지 않아야 view로 인식)

(ModelAndView / 문자열 / void)

3. HTTP(REST) API, HTTP Message Body에 직접 입력 (단, 반환값이 문자열일 경우 @ResponseBody 붙어야 문자열로 인식)

(ResponseEntity(문자열) / 문자열 / ResponseEntity(객체) / 객체)

 

<요청 detail>

1,2. 만약 handler(=controller)의 인자가 "@RequestParam 단순타입" 이라면, RequestMappingHandlerAdapter가 호출됩니다. RequestMappingHandlerAdapter는 ArgumentResolver를 호출합니다. ArgumentsResolverRequestParamMethodArgumentResolover를 호출합니다. RequestParamMethodArgumentResolver는 @RequestParam 애노테이션을 해석하고, 요청 파라미터를 해당 handler(=controller) 메소드의 매개변수와 매핑하여 값을 전달합니다.

 

1,2. 만약 handler(=controller)의 인자가 "@RequestParam Map" 이라면, RequestMappingHandlerAdapter가 호출됩니다. RequestMappingHandlerAdapter ArgumentResolver를 호출합니다. ArgumentsResolver RequestParamMapMethodArgumentResolover를 호출합니다. RequestParamMapMethodArgumentResolver는 @RequestParam 애노테이션을 해석하고, 요청 파라미터를 Map 형태로 전달하며, Map을 해당 handler(=controller) 메소드의 매개변수와 매핑하여 값을 전달합니다.  

 

1,2. 만약 handler(=controller)의 인자가 "@RequestParam MultiValueMap" 이라면, RequestMappingHandlerAdapter가 호출됩니다. RequestMappingHandlerAdapter ArgumentResolver를 호출합니다. ArgumentsResolver  RequestParamMapMethodArgumentResolover를 호출합니다. RequestParamMapMethodArgumentResolver는 @RequestParam 애노테이션을 해석하고, 요청 파라미터를 MultiValueMap 형태로 전달하며, MultiValueMap 을 해당 handler(=controller) 메소드의 매개변수와 매핑하여 값을 전달합니다.  

 

1,2. 만약 handler(=controller)의 인자가 "@ModelAttribute" 이라면, RequestMappingHandlerAdapter가 호출됩니다. RequestMappingHandlerAdapter는 ArgumentsResolver를 호출합니다. ArgumentsResolver는 ModelAttributeMethodProcessor를 호출합니다. ModelAttributeMethodProcessor는 @ModelAttribute 애노테이션을 해석하고, 해당 객체를 생성하고 초기화합니다. 요청 파라미터의 key에 따라 객체의 프로퍼티를 찾습니다. 객체의 프로퍼티가 존재한다면 해당 프로퍼티의 setter를 호출하여 객체의 프로퍼티에 요청 파라미터의 value를 바인딩합니다. 객체를 해당 handler(=controller) 메소드의 매개변수와 매핑하여 값을 전달합니다.

 

3-1,3-2. 만약 handler(=controller)의 인자가 "@RequestBody 단순타입" 이라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ArgumentsResolver를 호출합니다. ArgumentsResolver는 HttpMessageConverter를 호출합니다.HttpMessageConverter는 StringHttpMessageConverter를 호출합니다. StringHttpMessageConverter는 요청 메세지의 문자열 데이터를 단순 타입으로 변환합니다. 이 단순 타입 값을 해당 handler(=controller) 메소드의 매개변수와 매핑하여 값을 전달합니다. 이후 참조변수 = objectMapper.readValue(문자열, 객체.class)를 작성하여 직접 매핑하여 객체를 생성합니다. 

 

3-1,3-2. 만약 handler(=controller)의 인자가 "@RequestBody 객체" 라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ArgumentsResolver를 호출합니다. ArgumentsResolver는 HttpMessageConverter를 호출합니다. HttpMessageConverter는 MappingJackson2HttpMessageConverter를 호출합니다. MappingJackson2HttpMessageConverter는 메세지바디에 실려 요청으로 들어온 JSON을 객체의 각 프로퍼티에 매핑 하여 객체를 생성합니다. 생성된 객체를 해당 handler(=controller) 메소드의 매개변수와 매핑하여 값을 전달합니다.

 

3-1,3-2. 만약 handler(=controller)의 인자가 "HttpEntity" 라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ArgumentsResolver를 호출합니다. ArgumentsResolver는 HttpEntityMethodProcessor를 호출합니다. HttpEntityMethodProcessor는 요청의 헤더, 바디, 상태 등의 정보를 추출하여 HttpEntity 객체를 생성합니다. 생성된 HttpEntity 객체는 hanlder(=controller) 메소드의 매개변수와 매핑하여 값을 전달합니다.

 

 

<응답 detail>

2. 만약 handler(=controller)의 반환값이 "ModelAndView"이라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapterReturnValueResolver를 호출합니다. ReturnValueResolver는 ModelAndViewResolver를 호출합니다. ModelAndViewResolver는 handler(=controller)에서 반환된 ModelAndView 객체를 처리하여 뷰 이름과 모델 데이터를 추출합니다. 이후에 뷰 이름과 모델 데이터를 템플릿 엔진 등에 전달하여 최종적인 응답을 생성합니다.

 

2. 만약 handler(=controller)의 반환값이 "문자열"이라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ReturnValueResolver를 호출합니다. ReturnValueResolver는 StringReturnValueHandler를 호출합니다. StringReturnValueHandler는 handler(=controller)에서 반환된 문자열을 그대로 응답으로 사용합니다.

 

2. 만약 handler(=controller)의 반환값이 "void"라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ReturnValueResolver를 호출합니다. ReturnValueResolver는 VoidReturnValueHandler를 호출합니다. VoidReturnValueHandler는 반환값이 없는 경우에는 별도의 처리를 하지 않으며, 응답은 생성되지 않습니다.

 

3. 만약 handler(=controller)의 반환값이 "ResponseEntity(문자열)"이라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ReturnValueResolver를 호출합니다. ReturnValueResolver는 ResponseEntityReturnValueHandler를 호출합니다. ResponseEntityReturnValueHandler는 handler(=controller)에서 반환된 ResponseEntity 객체를 처리하여 해당하는 상태 코드, 헤더, 바디 등을 포함한 응답을 생성합니다.

 

3. 만약 handler(=controller)의 반환값이 "문자열"라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ReturnValueResolver를 호출합니다. ReturnValueResolver는 HttpMessageConverter를 호출합니다. HttpMessageConverter는 StringHttpMessageConverter를 호출하여 문자열을 응답으로 사용합니다.

 

3. 만약 handler(=controller)의 반환값이 "ResponseEntity(객체)"라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ReturnValueResolver를 호출합니다. ReturnValueResolver는 ResponseEntityReturnValueHandler를 호출합니다. ResponseEntityReturnValueHandler는 handler(=controller)에서 반환된 ResponseEntity 객체를 처리하여 해당하는 상태 코드, 헤더, 바디 등을 포함한 응답을 생성합니다.

 

3. 만약 handler(=controller)의 반환값이 "객체"라면, handler(=controller)는 RequestMappingHandlerAdapter를 호출합니다. RequestMappingHandlerAdapter는 ReturnValueResolver를 호출합니다. ReturnValueResolver는 HttpMessageConverter를 호출합니다. HttpMessageConverter는 MappingJackson2HttpMessageConverter를 호출합니다. MappingJackson2HttpMessageConverter는 객체를 JSON으로 변환하여 반환합니다.

 

 

(@RequestBody는 메세지 바디에 싣는 방식, @ModelAttribute는 쿼리 파라미터에 싣는 방식이라는 점이 차이이고, 만약 @RequestBody를 생략한다면 스프링은 handler(=controller)의 인자를 @RequestBody 객체가 아니라 @ModelAttribute 객체로 인식하여 쿼리 파라미터를 찾게 됩니다. 하지만 쿼리 파라미터가 아니라 메세지 바디에 실는 방식을 의도했기 때문에 객체가 생성되지 않아 None이 출력됩니다.) 

 

5. HandlerAdapter구현체가 전달받은 handler(=controller)가 실행될 수 있도록 요청 값 변환, 검증, 반환 값 변환 등의 작업을 수행했다면, 그 결과를 handler(=controller)에게 전달하고, handler(=controller)는 비즈니스 로직 수행을 완료하고, 필요한 데이터를 Model에 저장합니다.

 

6. handler(=controller)ModelAndView 객체를 생성하고, ModelModelAndView에 추가합니다.


7. ModelAndView  객체는 ViewResolver에게 전달됩니다.


8. ViewResolver는 논리적인 View(ex. "myForm")를 물리적인 View(ex. "WEB/views/myForm.jsp")로 변환합니다.


9.. 변환된 ViewDispatcherServlet에 반환됩니다.


10. DispatcherServletView를 실행하여 클라이언트에게 응답을 생성합니다.


11. 응답은 클라이언트에게 전송되고, 요청-응답 주기가 완료됩니다.

김영한님의 스프링 MVC 1편 강의자료 일부입니다. (HandlerAdapter 구현체가 RequestMappingHandlerAdapter인 경우의 그림)

 

Comments