Spring MVC의 동작

하루히즘·2021년 6월 15일
0

Spring Framework

목록 보기
6/15

서론

스프링 프레임워크의 핵심 기능은 웹 애플리케이션을 작성할 수 있는 스프링 MVC 모듈일 것이다. 스프링 프레임워크에서는 웹 애플리케이션을 작성하는 데 필요한 여러 기능들을 제공하며 스프링 부트에서는 웹 서버까지 내장해서 제공하고 있다.

본론

스프링 MVC는 여러 역할이 정의된 인터페이스를 구현하는 다양한 Bean 객체를 활용하여 사용자의 요청을 처리하고 응답을 반환한다. 이를 사용자가 일일히 등록하는 것은 너무 번거롭기 때문에 스프링 레거시에서는 @EnableWebMvc 어노테이션, 스프링 부트에서는 클래스패스에서 mvc 모듈을 자동으로 인식하여 대부분 자동으로 설정해준다.

DispatcherServlet

스프링 MVC에서는 웹 요청을 처리하기 위해 DispatcherServlet이라는 서블릿 객체를 활용한다. 이 서블릿은 스프링 프레임워크를 이용하여 개발된 웹 애플리케이션에 전송되는 요청을 최전선에서 받아 요청을 처리할 수 있는 컨트롤러로 전송(dispatch)하고 그 실행 결과를 기반으로 사용자에게 전달할 응답(뷰)을 생성, 반환하는 역할을 수행한다.
전자정부프레임워크 위키
이미지 출처

위의 그림에서 볼 수 있듯이 DispatcherServlet은 사용자의 요청(request)부터 응답(response)까지 모든 분야에 관여하고 흐름을 제어하는 중요한 컴포넌트라 할 수 있다.

HandlerMapping

사용자가 특정 URI를 요청하면 이를 처리할 수 있는 컨트롤러 클래스의 핸들러 메서드가 필요하다. 이 핸들러 메서드가 어떤 URI를 처리할 수 있는지 맵핑해둬야 DispatcherServlet에서 요청을 분석하여 적절한 핸들러 메서드로 넘길 수 있는데 이 역할이 HandlerMapping이라는 인터페이스에 정의되어 있다.

스프링에서는 핸들러 메서드와 URI를 맵핑할 때 다양한 방법을 사용할 수 있기 때문에 DIP 원칙을 준수하여 맵핑이라는 역할만 인터페이스로 남겨두고 유연하게 구현체를 변경할 수 있다. 즉 스프링에서 제공하는 것 뿐 아니라 사용자가 직접 핸들러 메서드를 맵핑하는 방식을 구현할 수도 있다는 것이다.

스프링에서는 BeanNameUrlHandlerMapping, RequestMappingHandlerMapping이라는 두 가지 구현체를 제공한다. 전자는 슬래시("/")로 시작하는 Bean 객체의 이름을 기반으로, 후자는 흔히 보는 @Controller 클래스의 @RequestMapping 메서드를 핸들러 메서드로 맵핑한다. 스프링 3.1 이전까지는 전자, 이후부터는 후자가 기본 구현체로 사용된다.

HandlerAdapter

이렇게 HandlerMapping 인터페이스의 구현체로 핸들러 메서드를 맵핑했다면 이 메서드에 요청을 넘겨서 실제로 메서드를 실행하는 역할도 필요하다. 이는 HandlerAdapter라는 인터페이스에 정의되어 있다.

그냥 DispatcherServlet에서 핸들러 메서드를 실행하면 되는게 아닌가 싶지만 핸들러 메서드는 스프링에서 다양한 방법으로 맵핑될 수 있기 때문에 특정 타입에 의존하는 것은 확장성을 저해한다. 그러므로 이 HandlerAdapter 인터페이스를 이용하여 간접적으로 핸들러 메서드를 실행하는 느슨한 결합(loosely coupling)을 유지하는 것이다.

핸들러 메서드를 등록하는 방식에 따라 HandlerAdapter도 여러 구현체가 있다. Controller 인터페이스를 구현한 클래스의 메서드를 활용하는 SimpleControllerHandlerAdapter, @Controller 어노테이션이 붙은 클래스의 @RequestMapping 어노테이션 핸들러 메서드를 활용하는 RequestMappingHandlerAdapter 등 구현 방식에 따른 서로 다른 구현체를 사용할 수 있다.

이런 구현체를 직접 설정할 일은 거의 없고 사용자는 Controller 인터페이스나 @Controller 어노테이션을 이용하여 컨트롤러 클래스를 설계한 후 요청을 처리하기 위한 핸들러 메서드만 잘 정의해주면 된다.

ViewResolver

HandlerAdapter에 의해 실행된 핸들러 메서드는 다양한 타입을 반환할 수 있다. 프론트엔드 페이지와 API로 통신하는 서버라면 별도의 뷰(View)를 렌더링할 필요가 없지만 MVC 기반으로 서버측에서 뷰를 렌더링할 경우 스프링은 핸들러 메서드의 반환값을 기반으로 렌더링할 뷰를 선택(resolve)할 수 있다.

핸들러 메서드는 다양한 값을 반환할 수 있지만 크게 문자열과 ModelAndView 객체로 구분할 수 있다. ModelAndView 객체는 MVC 패턴의 Model과 View를 핸들러 메서드에서 한 번에 반환할 수 있도록 담는 컨테이너 역할로 사용자에게 응답할 페이지의 이름(View)과 요청을 처리하여 얻은 데이터(Model)를 반환하여 페이지를 렌더링할 수 있다.

만약 핸들러 메서드에서 문자열만 반환한다면 스프링은 이를 뷰의 이름이라고 판단하고 ViewResolver를 통해 해당 이름의 템플릿 파일을 찾아 View 객체로 사용한다. 그리고 핸들러 메서드의 Model 파라미터로 전달된 데이터를 활용하여 렌더링한다.

옛날에는 JSP를 활용하여 뷰를 렌더링했지만 최근에는 Thymeleaf, Mustache 등 여러 템플릿 엔진이 추가됐고 스프링 부트에서는 아예 JSP를 기본으로 지원하지 않는다. 이런 템플릿 엔진을 ViewResolver로 등록하려면 application properties에 템플릿 파일의 디렉토리 위치 등 추가적인 설정이 필요하다.

전체적인 흐름

전자정부프레임워크 위키
이미지 출처

1~3: DispatcherServlet이 사용자의 요청을 받으면 HandlerMapping을 통해 해당 URI를 처리하는 핸들러 메서드를 탐색한다.
4~6: 핸들러 메서드 이전(preHandle)에 HandlerInterceptor가 적용되어 있을 시 이를 실행한다.
7~9: HandlerAdaptor를 이용해 사용자의 요청을 핸들러 메서드로 전달하여 처리한다.
10: 핸들러 메서드 이후(postHandle)에 HandlerInterceptor가 적용되어 있을 시 이를 실행한다.
11~12: 핸들러 메서드의 반환값을 기반으로 ViewResolver에서 뷰를 찾아 렌더링한다.

결론

DispatcherServlet은 스프링의 매우 중요한 컴포넌트기 때문에 사용자가 직접 건드릴 일이 없다. 하지만 다양한 인터페이스를 활용하여 DIP 원칙을 통해 여러 구현체를 유연하게 사용할 수 있는 모습에서 스프링 프레임워크가 가진 객체 지향 패러다임의 유연함을 느낄 수 있었다.

참고

HandlerAdapters in Spring MVC
전자정부프레임워크 위키

profile
YUKI.N > READY?

0개의 댓글