Web/Spring

Spring MVC

Kim Jinung 2023. 4. 17. 23:16

1. Spring MVC

스프링 프레임워크에서 웹 애플리케이션 개발을 위해 제공하는 모듈이다. 기본적으로 MVC 패턴을 따른다. MVC패턴은 웹 애플리케이션에서 관심사에 따라 역할과 책임을 분리하는 것이다. Model은 애플리케이션의 비지니스 로직, View는 사용자 인터페이스, Controller는 모델과 뷰 사이의 상호작용을 관리한다. 


2. Spring MVC architecture

스프링 부트를 사용하면 기본 옵션으로 내장 톰캣을 사용한다. 웹 서버가 HTTP 요청을 받는 경우 웹 리스너가 이를 감지하고 서블릿 컨테이너로 넘긴다. 서블릿 컨테이너는 적절한 서블릿을 매핑한다. 스프링 프레임워크를 사용하면 일반적으로 서블릿 컨테이너가 디스패처 서블릿(Dispatcher Servlet)을 호출한다.

DispatcherServlet

디스패처 서블릿은 스프링 MVC에서 프론트 컨트롤러 역할을 한다. 이 디스패처 서블릿으로부터 스프링 MVC 아키텍처가 시작된다. 디스패처 서블릿이 수행하는 로직은 doDispatch 메서드에서 확인할 수 있다.

doDispatch

getHandler method

DispatcherServlet doDispatcher - getHandler

1049라인에 위치하고 있는 getHandler 메서드를 사용하여 사용자 요청(request param)에 응답 가능한 핸들러(컨트롤러)를 찾는다. DispatcherServlet 객체의 필드인 handlerMappings 리스트에서 request를 처리할 수 있는지 핸들러를 찾을 때까지 루프를 태운다. 만약 사용자 요청을 처리할 수 있는 핸들러를 못찾는 경우 noHandlerFound 예외가 발생한다.


getHandlerAdapter method

DispatcherServlet doDispatcher - getHandlerAdapter

다음으로 1055 라인에 위치한 getHandlerAdapter를 메서드를 사용하여 찾은 핸들러를 처리할 수 있는 어댑터를 찾는다. 이와 같은 구성을 핸들러 어댑터 패턴이라고 부르는데, 다양한 형태의 컨트롤러를 지원하기 위함이다. 어댑터는 디스패처 서블릿과 핸들러 사이에서 요청 및 응답을 위한 규격을 맞춰주는 역할을 담당한다. 각각의 핸들러가 요구하는 파라미터와 타입과 반환 타입이 다르더라도 이를 처리하는 어댑터가 전처리 및 후처리 과정을 거쳐서 디스패처 서블릿에 넘겨준다. 따라서 HandlerAdapter 인터페이스를 구현하여 커스텀 핸들러를 자유롭게 추가할 수 있다.

 

DispatcherServlet doDispatcher - getHandlerAdapter

getHandlerAdpater 메서드는 getHandler 메서드와 유사한 형태다. handler를 파라미터로 받는다. 그리고 DispatcherServlet의 필드인 handlerAdpaters 리스트에서 어댑터를 하나씩 꺼낸 후에, 파라미터로 받은 핸들러를 지원하는지 확인하고 핸들러를 처리할 수 있는 어댑터를 반환한다. 만약 지원하는 어댑터를 못찾는 경우 서블릿 예외가 발생한다. 


Handler Adpater

HandlerAdpater interface

HandlerAdpater 인터페이스는 supports 메서드 그리고 handler 메서드를 구현할 것을 명시하고 있다. 

Simple Controller Hander Adapter

SimpleControllerHandlerAdater

하나의 예시로 SimpleControllerHandlerAdpater 구현체를 살펴보면, supports 메서드는 파라미터로 받은 객체가 처리할 수 있는 케이스인지 확인한다. 다음으로 handle 메서드는 handler를 각 핸들러 어댑터의 공통 타입으로 업 캐스팅한 뒤(여기서는 Controller다.) 핸들러의 비지니스 로직(handlerRequest 메서드)를 호출한다. 

Overview

https://terasolunaorg.github.io/guideline/1.0.1.RELEASE/en/Overview/SpringMVCOverview.html

 

전체 흐름을 요약하자면 다음과 같다. 해당 과정이 일반적인 서버 사이드 렌더링의 흐름이다.

 

1. 웹 서버로 들어온 요청이 서블릿 컨테이너로 전달 된다.

2. 서블릿 컨테이너는 디스패처 서블릿에 응답을 전달한다.

3. 디스패처 서블릿은 handlerMappings 리스트에서 요청에 해당하는 핸들러를 찾는다.

4. 찾은 핸들러를 처리할 수 있는 핸들러 어댑터를 handlerApdaters 리스트에서 찾는다.

5. 핸들러 어댑터는 핸들러를 공통 타입으로 업캐스팅 한 뒤 핸들러의 비지니스 로직을 수행하는 메서드를 수행한다. 

6. 핸들러 어댑터는 비지니스 로직 메서드의 수행 결과를 ModelAndView 객체에 담아서 디스패처 서블릿에 전달한다.

7. 디스패처 서블릿은 ViewResolver에 ModelAndView 객체를 넘겨서 View를 찾는다. (ModelAndView에는 ViewName과 비지니스 로직을 수행한 결과 데이터가 담겨있다.)

8. View를 렌더링한 결과를 서블릿 컨테이너로 넘긴다.

 

 

그런데 Handler Adapter는 어떻게 각각 다른 Handler(Controller)마다 필요로 하는 파라미터를 꽂아주는 걸까?

Handler Method Argument Resolver

핸들러 어댑터는 핸들러 메서드 아규먼트 리졸버를 이용해서 핸들러의 메서드에서 필요로 하는 파라미터를 생성한다.

Handler Method Argument Resolver

HandlerMethodArgumentResolver interface

핸들러 메서드 아규먼트 리졸버는 인터페이스 형태로 제공된다. 

 

Implementations of HandlerMethodArgumentResolver

스프링은 이에 대한 구현체를 제공하고, 핸들러 어댑터는 해당 구현체를 사용해서 핸들러의 비지니스 로직 메서드 실행에 필요한 파라미터를 생성하여 주입해준다.

Erros Method Argument Resolver

ErrorsMethodArgumentResolver implentation

하나의 예시를 살펴보면, getHandler 메서드와 유사하게, 파라미터를 처리할 수 있는 타입인지 먼저 검사한다. 그리고 처리 가능한 케이스는 resolveArgument 메서드를 호출해서 값을 생성해주는 것이다. (단  HTTP body 메시지를 읽는 등, HTTP 메시지에 관한 처리가 필요한 경우 아규먼트 리졸버가 HTTP 메시지 컨버터에 태워서 파싱한다.) 

 

이를 통해 스프링 프레임워크 내부에서 핸들러 어댑터가 처리해야하는 파라미터들을 아규먼트 리졸버 리스트에 루프 형태로 태워서 넘겨주는 것이 아닐까라는 추측해볼 수 있다.

 

Request Mapping Handler Adpater

RequestMappingHandlerAdpater implentation

 

그러한 예시로 리퀘스트 매핑 핸들러 어댑터를 살펴보면 argumentResolvers 필드를 가지고 있는 것을 확인할 수 있다. argumentRevolers 필드를 이용해서 주입해야하는 파라미터를 지원하는 구현체인지 확인한다. (더 깊게 파지 않고 잠시 멈춤..)


Handler Method Return Value Handler

핸들러 어댑터는 핸들러 메서드 아규먼트 리졸버를 사용하여 핸들러의 메서드 호출에 필요한 값을 생성한 뒤 호출한다. 그리고 핸들러 메서드가 실행한 결과를 다시금 디스패처 서블릿이 요구하는 형태로 변환하여 반환해야 한다. 핸들러 메서드 아규먼트 리졸버는 메서드가 필요로 하는 형태로 파라미터를 생성했다면, 핸들러 메서드 리턴 밸류 핸들러는 핸들러 메서드가 반환한 결과를 디스패처 서블릿이 요구하는 형태로 변환하는 작업을 담당한다. 

 

Handler Method Return Value Handler

HandlerMethodReturnValueHandler interface

핸들러 메서드 아규먼트 리졸버와 동일하게 인터페이스로 제공된다.

 

Http Entity Method Processor

HttpEntityMethodProcessor implementation

구현체 중 하나인 HttpEntityMethodProcessor를 확인 해보면, 처리 가능한 타입인지 확인하는 메서드 그리고 HttpTypeConverter를 사용하여 결과를 변환하는 메서드를 확인할 수 있다. 

 

즉 스프링 MVC에서 HTTP 요청 메시지는

 

디스패치 서블릿 - 핸들러 매핑 - 핸들러 어댑터 - (핸들러 메서드 아규먼트 리졸버, 핸들러 메서드 리턴 밸류 핸들러) - 핸들러 메서드 호출

 

흐름으로 진행 되며 처리 됨을 알 수 있다. 요약하면 핸들러 매핑 뿐만 아니라 아규먼트 리졸버 매핑도 프론트 컨트롤러 패턴으로 구현되어 있다.


스프링 프레임워크를 사용하여 웹 애플리케이션을 개발하면, 디스패처 서블릿 그리고 핸들러 어댑터를 볼 일이 거의 없다. 그러므로 서블릿 컨테이너 - 핸들러 이 사이에 블랙 박스가 생긴다.

 

@RequestMapping 어노테이션을 사용하는 컨트롤러를 개발 한다고 가정하면, 디스패처 서블릿이 우선 컨트롤러를 매핑하고 이를 처리할 수 있는 RequestMappingHandlerAdapter에 태우고 HandlerMethodArgumentResolver를 이용해서 컨트롤러가 요구하는 파라미터를 주입 해준다. 그리고 그 결과를 HandlerMethodReturnValueHandler가 반환 타입에 맞게 후처리한 뒤 핸들러 어댑터, 디스패처 서블릿 순으로 넘어가게 된다.