🙃 서론
스프링은 정말 사용자가 사용하기 편한 기능들을 많이 제공해준다.
그 중에서도 컨트롤러는 정말 다양한 파라미터와 리턴 값을 사용자 뜻대로 이것저것 가져와서 사용할 수 있다.
예를 들어 파라미터로 String이나 Integer 로 받을 수도 있고, 직접 만든 DTO 객체로도 받을 수 있다.
또, 리턴 값으로 String으로 view 경로를 보낼 수도 있고, 진짜 String을 보낼 수도 있고, ModelAndView 를 보낼 수도 있다.
어떻게 Spring은 이렇게 다양하게 사용자 입맛대로 만들 수 있게 해주는지에 대해서 파악해보자.
스프링 MVC에서 받을 수 있는 인수 - 링크
😆 본론
Dispatcher Servlet
일단, 가장 중요한 Dispatcher Servlet에 대해서 개념을 잡고 가야 합니다.
클라이언트에서 HTTP 요청을 할 때 우리가 선언한 컨트롤러를 바로 타는 게 아니라,
Dispatcher Servlet 라는 녀석이 모든 요청을 가로챕니다.
Dispatcher Servlet 은 다른 말로 Front Controller 라고도 부르는 데,
앞 단에 있는 컨트롤러라는 뜻으로 모든 요청을 먼저 받아서 다양한 기능들을 수행해주는 녀석이라고 보면 됩니다.
MVC 처리 정리
1. 클라이언트에서 HTTP 요청을 보냅니다.
2. Dispatcher Servlet 이 모든 요청을 가로채갑니다.
3. Handler Mapping 에서 요청된 정보(URI, HTTP Method)와 일치하는, 등록된 핸들러 (컨트롤러 or 정적리소스)가 있는지 찾습니다.
4. 찾은 핸들러(컨트롤러)를 처리할 수 있는 핸들러 어댑터를 조회합니다.
5. 핸들러 어댑터를 통해 핸들러(컨트롤러)를 실행하여 ModelAndView 로 반환합니다.
6. ModelAndView 에 ViewName이 있다면, viewResolver에서 논리 이름으로 변경하여 View를 반환합니다.
ex) "hello" -> "/WEB-INF/views/hello.jsp"
6-1. view 를 reander 하여 화면을 호출합니다.
전체적인 MVC 흐름을 간략하게 정리해보았습니다.
이 중에서도 핸들러 어댑터가 정말 다양한 일들을 하기 때문에, 핸들러 어댑터를 조금 더 살펴보겠습니다.
어댑터 패턴
먼저, 핸들러 어댑터를 알기 위해서는 어댑터 패턴을 알아야 합니다.
나라마다 콘센트 규격이 다 다른 걸 알 수 있습니다.
어디서는 220v, 다른 곳에서는 110v 로 돼지코 모양이 다 다르게 생겼습니다.
그럴 때 우리가 가지고 있는 선의 규격과 연결해야 하는 곳의 규격을 맞추기 위해 어댑터라는 녀석을 사용합니다.
어댑터 패턴도 위와 동일합니다.
여러 다른 처리 로직들을 공통으로 사용할 수 있게 끔 중간에서 변경해주는 패턴이라고 보면 됩니다.
핸들러 어댑터
핸들러 어댑터 처리 정리
1. 찾은 핸들러 어댑터의 handle() 메소드를 실행시킵니다.
2. ArgumentResolver 를 통해 호출할 핸들러(컨트롤러)의 arguments 를 만듭니다.
2-1. 이때 @RequestBody 가 붙어있거나, HttpEntity를 받을 경우 HTTP 메시지 컨버터를 호출합니다.
3. 어댑터에서 만든 arguments 를 넘겨서 핸들러(컨트롤러)를 호출합니다.
4. ReturnValueHandler 를 통해 컨트롤러의 리턴 값을 알맞게 변경합니다.
4-1. 이때 @ResponseBody가 붙어있거나, 리턴 값이 HttpEntity일 경우 HTTP 메시지 컨버터를 호출합니다.
5. ModelAndView 혹은 null 을 DispatcherServlet 으로 리턴합니다.
* HTTP 메시지 컨버터
Spring은 기본적으로 메시지 컨버터를 jackson 라이브러리를 사용합니다.
jackson 을 통해 컨트롤러에서 전달받은 json 데이터를 DTO로 변환해주거나,
컨트롤러로 데이터를 넘길 때 DTO를 json 으로 변환하는 역할을 해줍니다.
Spring Code 따라가보기.
아래 컨트롤러를 예시로 한 번 따라가 보도록 하겠습니다.
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
return data;
}
getHandlerAdapter() : 핸들러 어댑터를 찾습니다.
handle() : 핸들러 어댑터의 handle() 메소드를 실행하여 어떤 핸들러든 ModelAndView 를 반환받습니다.
* 여기서 어떤 핸들러든 동일한 반환 값 (ModelAndView)을 받기 위해 핸들러 어댑터가 사용됩니다.
RequestMapping 으로 컨트롤러가 빈에 등록되어 있기 때문에, "RequestMappingHandlerAdapter" 를 타게 됩니다.
동기 / 비동기에 따라 각각 invokeHandlerMethod() 메소드를 실행합니다.
invokeAndHandle() : 핸들러(컨트롤러)를 실행합니다.
getModelAndView() : 컨트롤러 결과에 따라 ModelAndView 를 만들어서 반환합니다.
invokeForRequest() : 핸들러(컨트롤러)를 실행하고, 리턴 값을 받습니다.
getMethodArgumentValues() : 호출할 핸들러(컨트롤러)의 arguments 를 구합니다.
doInvoke() : 핸들러(컨트롤러) 를 호출합니다.
findProvidedArgument() : exception 을 처리할 때 args를 처리하는 것 같습니다. (이건 좀 더 알아봐야될 것 같습니다.)
this.resolvers.resolveArgument() : 일치하는 HandlerMethodArgumentResolver 를 찾아 실행하여 컨트롤러에 필요한 argumnet를 셋팅합니다.
이렇게 한 번 Spring 코드를 따라가보면서 컨트롤러가 어떻게 실행되는 지 알아보았습니다.
☺️ 결론
뒤죽박죽 이것저것 탐방한 것들을 쭈욱 적긴 했지만,
결국 처음 본론에서 얘기한 MVC 처리 정리 가 간단한 MVC 패턴 구조 설명으로 봐주시면 될 것 같습니다.
Spring 은 정말 추상화가 정말.. 잘 되어 있기 때문에 막상 코드를 하나하나 따라가려고 하다보면
이해가 됐던 것도 따라가다보면 길을 헤매는 경험을 하게 되었습니다..
역시 추상화는 사용자를 편하게 하지만, 개발할 때나 디버깅 할 때는 쫒아가기 어려운 것 같습니다..
잘못된 내용 혹은 궁금한 내용을 댓글 부탁드립니다! (__)
이 내용은 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 인프런 강의를 학습한 후 정리한 내용입니다.
참고
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc
'JVM > Spring' 카테고리의 다른 글
[Spring] enum으로 @Secured 권한 관리 (0) | 2022.06.18 |
---|---|
[Spring] @PropertySource yaml 파일 Load 하는 방법 (0) | 2022.06.16 |
[SPRING] @Valid @Validated 사용하기 - java bean validation (0) | 2022.03.26 |
[SPRING] @Valid 어떻게 동작할까 - java bean validation (0) | 2022.03.26 |
[SPRING] 스프링의 컨트롤러는 어떻게 여러 작업을 처리할까? (0) | 2021.11.15 |