[Web, Spring] Dispatcher Servlet, Spring MVC의 Request Flow

344th·2024년 2월 11일

Develop

목록 보기
5/8

DispatcherServlet

DispatcherServlet
: 요청 처리를 위한 프론트 컨트롤러 이다. 실제 작업은 적합한 컨트롤러에 의해 수행된다.

: 서블릿 컨테이너의 가장 앞단에서 HTTP 프로토콜로 들어오는 모든 요청을 먼저 받아서 적합한 컨트롤러에 위임(Delegate request)해주는 프론트 컨트롤러(Front Controller) 이다.

  • DispatcherServlet은 모든 서블릿과 마찬가지로 서블릿 Java configuration을 사용하여 서블릿 사양을 따르거나 web.xml에서 매핑되어야 한다.
  • DispatcherServletSpring configuration을 사용하여 request mapping, view resolution, exception handling 등에 필요한 위임 구성 요소를 검색한다.

다음 Java configuration 예는 Servlet 컨테이너에 의해 자동 감지되는 DispatcherServlet을 등록하고 초기화한다

public class MyWebApplicationInitializer implements WebApplicationInitializer {

	@Override
	public void onStartup(ServletContext servletContext) {

		// Load Spring web application configuration
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		context.register(AppConfig.class);

		// Create and register the DispatcherServlet
		DispatcherServlet servlet = new DispatcherServlet(context);
		ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
		registration.setLoadOnStartup(1);
		registration.addMapping("/app/*");
	}
}

다음 web.xml configuration 예는 DispatcherServlet을 등록하고 초기화한다.

<web-app>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/app-context.xml</param-value>
	</context-param>

	<servlet>
		<servlet-name>app</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value></param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>app</servlet-name>
		<url-pattern>/app/*</url-pattern>
	</servlet-mapping>

</web-app>

DispatcherServlet 장점

Spring MVCDispatcherServlet이 등장함에 따라 web.xml의 역할을 상당히 축소시켜주었다. 과거에는 모든 서블릿을 URL 매핑을 위해 web.xml에 모두 등록해주어야 했지만, dispatcher-servlet이 해당 어플리케이션으로 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리면서 상당히 편리하게 이용할 수 있게 되었다.

우리는 컨트롤러를 구현해두기만 하면 디스패처 서블릿이 알아서 적합한 컨트롤러로 위임을 해주는 구조가 되었다.

DispatcherServlet의 정적 자원 처리

DispatcherServlet이 요청을 Controller로 넘겨주는 방식은 효율적으로 보인다. 하지만 DispatcherServlet이 모든 요청을 처리하다보니 이미지나 HTML/CSS/JavaScript 등과 같은 정적 파일에 대한 요청마저 모두 가로채는 까닭에 정적자원(Static Resources)을 불러오지 못하는 상황도 발생하곤 했다. 

이러한 문제를 해결하기 위해 개발자들은 2가지 방법을 고안했다.

  1. 정적 자원 요청과 애플리케이션 요청을 분리
  2. 애플리케이션 요청을 탐색하고 없으면 정적 자원 요청으로 처리

1. 정적 자원 요청과 애플리케이션 요청을 분리

클라이언트의 요청을 2가지로 분리하여 구분한다.

  • /apps 의 URL로 접근하면 DispatcherServlet이 담당한다.
  • /resources 의 URL로 접근하면 DispatcherServlet이 컨트롤할 수 없으므로 담당하지 않는다.

이러한 방식은 괜찮지만 상당히 코드가 지저분해지며, 모든 요청에 대해서 저런 URL을 붙여주어야 하므로 직관적인 설계가 될 수 없다. 그래서 이러한 방법의 한계를 느끼고 다음의 방법으로 처리를 하게 되었다.

2. 애플리케이션 요청을 탐색하고 없으면 정적 자원 요청으로 처리

두번째 방법은 DispatcherServlet이 요청을 처리할 컨트롤러를 먼저 찾고, 요청에 대한 컨트롤러를 찾을 수 없는 경우에, 2차적으로 설정된 자원(Resource) 경로를 탐색하여 자원을 탐색하는 것이다. 이렇게 영역을 분리하면 효율적인 리소스 관리를 지원할 뿐 아니라 추후에 확장을 용이하게 해준다는 장점이 있다.

DispatcherServlet 동작

  1. DispatcherServlet이 클라이언트의 모든 요청을 받는다.
  2. 요청 정보에 대해 HandlerMappinng에 위임하여 처리할 Handler(Controller)을 찾는다.
  3. 2번에서 찾은 Handler을 수행할 수 있는 HandlerAdapter를 찾는다.
    • HandlerAdapter가 요청을 컨트롤러로 위임한다. (DispatcherSevlet이 직접 요청을 컨트롤러에 위임하지 X)
    • 다양하게 작성되는 컨트롤러에 대응하기 위해 스프링은 HandlerAdapter라는 어댑터 인터페이스를 통해 어댑터 패턴을 적용함으로써 컨트롤러의 구현 방식에 상관없이 요청을 위임할 수 있도록 하였다.
  4. HandlerAdapterController에 비즈니스 로직 처리를 호출한다.
  5. Controller는 비즈니스 로직을 수행하고, 처리 결과를 Model에 설정하며 HandlerAdapterview name을 반환한다.
    • 모델을 반환하면 View가 렌더링이 되고, 그렇지 않은 경우(ex. @RestController 등) View가 렌더링이 되지 않는다.
  6. 5번에서 반환받은 view nameViewResolver에게 전달하고, ViewResolver은 해당하는 View 객체를 반환한다.
  7. DispatcherServletView에게 Model을 전달하고 화면 표시를 요청한다.
  8. 최종적으로 서버의 응답을 클라이언트에게 반환한다.

DispatcherServlet 내부 구조


DispatcherServrlet은 Spring MVC의 핵심 요소로 위 사진과 같은 계층 구조를 가지고 있다.

DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet -> GenericServlet -> Servlet

Servlet

  • [Servlet](https://velog.io/@dltnals1210/Spring-Web-%EC%84%9C%EB%B8%94%EB%A6%BFServlet#%EC%84%9C%EB%B8%94%EB%A6%BFservlet)은 웹 서버 내에서 실행되는 자바 프로그램이다.
  • HTTP 통신을 사용하여 웹 클라이언트로부터 요청을 받고 응답하는 기술이다.
  • 일반적인 서블릿을 상속하는 GenericServlet, HTTP 서블릿을 상속하는 HttpServlet이 존재한다.
  • 서블릿의 생명주기 - init(), service(), destory()

GenericServlet

  • 프로토콜에 독립적인 일반 서블릿이다.
  • 웹에서 사용할 HTTP 서블릿을 작성하려면 HttpServlet을 상속받는다.

HttpServlet

  • 웹 사이트에 적합한 HTTP 서블릿 이다.
  • HttpServlet의 하위 클래스는 아래 메서드 중 최소 하나의 메서드는 재정의해야한다.
    • doGet(), doPost(), doPut(), doDelete()
    • init(), destory(): 서블릿 라이프 사이클의 초기화 및 파기
    • getServletInfo(): 서블릿에 대한 정보

HttpServletBean

  • HttpServlet의 단순 확장 클래스로 Spring이 구현한 모든 서블릿 유형에 적합한 서블릿 (spring 패키지)

FrameworkServlet

  • 스프링 웹 프레임워크의 기본 서블릿이다.
  • 하위 클래스는 요청을 처리하기 위해 doService() 메서드를 구현해야 한다.

DispatcherServlet

  • HTTP 요청을 처리하는 중앙 집중형 Dispatcher (Front-Controller)
  • 적절한 어댑터 클래스를 통해 어떠한 workflow와도 사용이 가능할 만큼 매우 유연하다.
  • Mapping, Adapter, Exception 관련 클래스는 다음과 같다.
    • HandlerMappingRequestMappingHandlerMapping
    • HandlerAdapterRequestMappingHandlerAdapter
    • HandlerExceptionResolverExceptionHandlerExceptionResolver

DispatcherServlet 요청 처리 흐름(Request Flow)

1. HttpServlet: 서블릿의 Request/ResponseHttpServletRequest/Response로 변환
2~5. FrameworkServlet: 클라이언트 요청의 Http Method에 따라 분기 처리 - doXXX 메서드
6. DispatcherServlet: 요청 정보에 대해 이를 처리할 Handler를 찾고, HandlerAdapter를 통해 Controller에 요청 위임
6-1. 요청에 매핑되는 HandlerMapping(HandlerExecutionChain) 조회
6-2. Handler을 수행할 수 있는 HandlerAdapter 조회
6-3. HandlerAdapter를 통해 실제 Controller의 비즈니스 로직 호출

Spring MVC의 Request Flow

Spring MVC의 핵심은 DispatcherServlet 이다.

  1. 모든 요청에 대한 서블릿 필터가 실행된다 (설정되어있다면)
  2. 모든 요청은 DispatcherServlet에게 전달된다.
  3. DispatcherServlet 은 받은 요청을 분석한다. (Common Service)
    • Locale, Theme, Multipart

HandlerMapping이 반환하는 HandlerExecutionChain객체

  1. DispatcherServlet 은 HandlerMapping 에게 위임하여 요청을 처리할 Handler(Controller) 를 찾는다.
    • HandlerMapping 은 요청 URL을 보고 Handler를 찾아서 Handler 의 이름과 함께 반환한다.
    • 이때 반환되는 것은 HandlerExecutionChain타입이다. (handler과 인터셉터 관련 상태를 가지고 있다.)
  2. DispatcherServlet 은 찾아낸 Handler 를 실행할 수 있는 HandlerAdapter 를 찾는다.
  3. 찾아낸 HandlerAdapter 를 사용해서 Handler 를 실행한다.
    • 실행 전에 전처리, 후처리로 실행해야할 인터셉터 목록을 결정하고 실행시킨다.
    • Handler 를 실행하면서 비즈니스 로직 또한 실행한다.
    • Handler의 리턴값 : View(뷰의 파일명), Model(비즈니스 로직 처리한 후의 데이터)
  4. DispatcherServlet 은 ViewResolver 에게 View 의 이름을 전달하고 View 객체를 얻는다.
    • 뷰 이름에 해당하는 뷰을 찾는 단계
    • View Resolver 는 전략 객체이며 view name 뿐 아니라 헤더 정보(accept)도 전달된다.
    • View Resolver 는 전달된 정보를 바탕으로 사용자에게 보여줄 View 가 무엇인지 결정한다.
  5. DispatcherServlet 은 View 객체에게 Model 과 함께 화면 표시를 의뢰한다.
  6. View 는 해당하는 뷰를(ex. JSP, Thymleaf..) 호출하며, Model 객체에서 화면 표시에 필요한 정보를 가져와 화면 표시를 처리한다.
    • 찾은 뷰에 모델 데이터를 랜더링하고 요청의 응답값을 생성한다.
  7. DispatcherServlet 은 View 로부터 받은 결과를 클라이언트에게 반환한다.

DispatcherServlet의 doDispatch를 보면 try-catch문을 볼 수 있다.

즉, 요청중 발생하는 예외는 DispatcherServletcatch를 해서 처리해준다.

참조

  1. https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html
  2. https://zzang9ha.tistory.com/441
  3. https://mangkyu.tistory.com/18
  4. https://mozzi-devlog.tistory.com/8
  5. https://github.com/binghe819/TIL/blob/master/Spring/MVC/Spring MVC flow.md
profile
새싹 개발자

0개의 댓글