[백엔드] 스프링 MVC 패턴이란? (2)

유승선 ·2022년 10월 19일
0

Spring 강의

목록 보기
6/12
post-thumbnail

Spring MVC 구조

MVC 패턴을 정리한 첫번째 글에 마지막에 Dispatcher Servlet 이라는 구조 설명을 하였다. MVC 구조에 익숙하지 않은 내가 이 그림을 봤을때 가장 헷갈렸던거는

Dispatcher Servlet 을 내가 구현 해야하나?

이었다...왜냐면은 MVC 강의에서 꽤 많은 시간을 썼던게 이 구조를 이해하려고 직접 MVC 프레임워크를 구현해 보는것이었고 당연히 Dispatcher Servlet 도 내가 해야하나 싶었는데 다행스럽게도 이미 Spring 이 전부 구현해준 코드였다.

Spring MVC의 프론트 컨트롤러가 바로 Dispatcher Servlet 이고 MVC의 핵심이다. 특징으로는 스프링 부트가 시작이 되면은 Dispatcher Servlet을 서블릿으로 자동 등록 해준다는 것이다

--

요청 흐름

  1. 클라이언트가 HTTP 요청을 한다
  2. Dispatcher Servlet 호출 -> HttpServlet 이 제공하는 service() 호출 (DispatcherServlet은 부모인 FrameworkServlet 에서 service 오버라이드를 완료함)
  3. service() 를 시작으로 안에 여러 메서드가 호출되고 DispatcherServlet.doDispatch() 가 호출된다. 그리고 doDispatch 메서드 안에 있는 기능들이 위에 나와있는 설명들을 모두 실행시켜준다고 생각할 수 있다.

굳이 코드 자체를 외울 필요는 없지만 위와 같은 구성으로 이루어진것만 기억하면 좋을거 같다.

동작 순서

  1. 핸들러 조회: 핸들러 매핑을 통해 요청 URL 에 매핑된 핸드러(컨트롤러) 를 조회한다.

  2. 핸들러 어댑터 조회: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.

  3. 핸들러 어댑터 실행: 핸들러 어댑터를 실행한다.

  4. 핸들러 실행: 핸들러 어댑터가 실제 핸들러를 실행한다.

  5. ModelAndView 변환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView 로 변환해서 반환한다.

  6. viewResolver 호출 : 뷰 리졸버를 찾고 실행한다.

  7. View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역활을 담당하는 뷰 객체를 반환한다.

핸들러 매핑과 핸들러 어댑터

생각보다 이해하기 힘들었던 구조였지만 다양한 예시와 핸들러 매핑, 어댑터 구현 과정 영상을 보면서 잘 이해가 됐었다.

핸들러 어댑터가 필요한 이유: 한 프로젝트 안에서 여러가지 컨트롤러를 사용할 수 있도록, 프론트 컨트롤러 (Dispatcher Servlet)가 다양한 방식의 컨트롤러를 처리할 수 있도록 하기 위함.

핸들러 어댑터: 중간에서 어댑터 역활을 하는데 덕분에 다양한 종류의 컨트롤러를 호출할 수 있다. (Controller V3 혹은 V4를 자유롭게 사용가능하게 만든다)

핸들러: 컨틀롤러의 더 넓은 범위에 이름

미래에 내가 헷갈릴 경우에는 스프링 MVC 1편에 유연한 컨트롤러1 - V5 강의를 다시 보기를 권장한다

강의에서 조금 헷갈릴 수 있으니 MVC 프레임워크를 직접 만든 강의를 떠올리며 약간의 요약을 해보겠다.

해당 상황은 Controller V4 뿐만 아니라 V3 도 호출하고 싶을떄 핸들러 매핑 -> 어댑터 목록 -> 핸들러 어댑터를 통한 탐색을 하려고 하는 상황이다.

-> 맨 처음 FrontController의 service() 가 호출이 된다면은 getHandler(request) 를 통해서 핸들러를 찾게된다. 여기서 말하는 핸들러는 Controller 의 또다른 이름이란것을 잊지말자.

-> 임의로 만든 HandlerMapping 에는 Map 형식으로 key 가 URL 그리고 value 가 컨트롤러로 나와있다. 자연스럽게 내가 들어온 URL 링크에 따라서 Controller 객체가 리턴될것이다.

-> 후에는 아래 코드를 실행시키며 getHandlerAdapter(handler) 을 호출한다. 여기서 handler 은 V3 Controller을 의미한다.

-> getHandlerAdapter 같은 경우 위와 같은 컨트롤러를 받게 되면 handlerAdapters 에서 어댑터를 찾게된다.

핸들러 어댑터는 아래와 같이 ArrayList() 로 구성이 되어있다. 이곳에 handlerAdapters.add(new ControllerV3HandlerAdapter()) 이라는 새로운 어댑터 객채를 넣어주었다.

For 룹을 돌리면서 핸들러 어댑터 리스트 안에 있는 임의에 어댑터 객채를 꺼낸 후에는 어댑터안에 supprt(handler) 매서드를 실행시킨다.

어댑터의 support() 매서드는 우리가 맨 처음에 URL로 핸들러 매핑에서 꺼내온 Controller가 ControllerV3 부모를 가지고 있는지 확인을 해준다. 만약 ControllerV3가 부모인것이 확인되면은 True 를 리턴해주며,

해당 어댑터의 .handle(request,response,handler) 매서드를 실행시킨다.

handle() 함수는 ControllerV3에 있는 로직들을 수행시켜주고 ModelView 를 리턴해주는 역활을 수행한다.

이 모든 구조는 실제 DispatcherServlet 을 통한 핸들러 매핑 코드와 똑같지는 않지만 흐름은 같다.

HandlerMapping 에서 컨트롤러를 찾고
HandlerAdapter 목록에서 그 컨트롤러에 맞는 어댑터를 리턴해준다. 맞는 어댑터란, 해당 컨트롤러가 어느 버전에 맞는 컨트롤러인지 확인을 해주고 그 버전을 사용할 수 있게 해주는 어댑터를 리턴해주는것이다. (220V 를 150V 로 변환해주는 콘센트 기억)

어댑터를 찾아왔으면 컨트롤러를 수행하고 로직을 구현하면 된다. 마지막에는 항상 ModelView 를 리턴하는것도 잊지말자.


HandlerMapping, HandlerAdapter

스프링에서 핸들러 어댑터가 왜 필요한지 또 매핑이 어떻게 이루어지는지 이제는 이해가 갔다. 그러나 위에 구현에서는 핸들러 매핑은 단순하게 Map 그리고 Adapter 은 List 로 처리가 됐는데 실제로 스프링에서 맞는 Controller 을 찾을때는 아래와 같은 우선순위를 가지게 된다.


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

스프링 공부를 하면서 자세하게 내부적인 구조를 몰라서 편리한 기능들을 자연스럽게 썼지만 솔직히 어떻게 이런 기능이 될까 하고 질문을 많이 했었다.

인터넷에는 사실 너무 어려운? 설명이 훨씬 많았어서 포기했는데 이 기회에 자세하게 알게되었다.

이 구조를 다시 한번 보고 위에서 설명했던 HandlerMapping, HandlerAdapter 읽어보자. 결국 내가 만든 컨트롤러를 실행시키기 위해서는 HandlerMapping 과 HandlerAdapter 을 거쳐야하는데 99.99 퍼센트의 개발에서 우리는 스프링 부트를 사용하며 애노테이션 기반의 개발을 한다.

@RequestParam, Model, @RequestBody 등등 많은 어노테이션을 사용하는데 이때 선택되는 애노테이션 기반의 컨트롤러, 즉 @RequestMapping 을 처리하는 어댑터인 RequestMappingHandlerAdapter(요청 매핑 핸들러 어뎁터) 를 선택하게 된다.

RequestMappingHandlerAdapter 의 동작 방식을 살펴보자.

핸들러 어댑터에서 컨트롤러를 호출하기 전에 위 그림과 같은 Argument Resolver 을 거치게 된다. 그렇다면 Argument Resolver은 무엇일까?

가령 위와 같은 코드를 입력했다고 생각해보자. 해당 컨트롤러 파라미터 안에는 @RequestParam 같은 어노테이션을 사용해서 코드를 실행시키고 있는데 이 모든것들을 유연하게 처리해주는것이 RequestMappingHandlerAdapter 이다.

애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlereAdapter 는 이 ArgumentResolver 을 호출해서 컨트롤러가 필요로 하는 다양한 파라미터의 값(객체)를 생성한다. @ModelAttribute 같이 객체를 생성할때도 전부 해당된다.

그리고 이렇게 파라미터의 값이 모두 준비되면 컨트롤러를 호출하고 값을 넘겨준다.

@ResponseBody 혹은 @RequestBody 같이 HTTP 메세지 컨버터를 이용해야 할때도 ArgumentResolver 혹은 ReturnValueHandler이 값을 처리하는 과정에서 HTTP 메세지 바디에 있는 것의 처리를 부탁하기 위해서 호출하게 된다.

ReturnValueHandler 또한 ArgumentResolver와 비슷하지만 다른점은 ArgumentResolver은 요청에서의 처리를 담당하고 ReturnValueHandler은 응답값을 변환하고 처리한다.


참고

Spring 블로그

profile
성장하는 사람

0개의 댓글