출처 - https://github.com/jmxx219/CS-Study (opens in a new tab)
DispatcherServlet
DispatcherServlet이란?
- 스프링 MVC가 사용하는 핵심 개념
- HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러로 정의함
- 클라이언트로부터 어떤 요청이 들어오면
서블릿 컨테이너
가 요청을 받는데, 이때 공통된 작업은 디스패처 서블릿에서 처리하고, 이외 작업은 다른 세부 컨트롤러로 위임함
- 클라이언트로부터 어떤 요청이 들어오면
- 과거에는 모든 서블릿을 URL 매핑을 위해 web.xml에 모두 등록해주어야 했음
- DispatcherServlet의 등장으로 애플리케이션으로 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리하면서 web.xml의 의존을 낮출 수 있게 됨
상속 구조(계층 구조)
-
DispatcherServlet
➡FrameworkServlet
➡HttpServletBean
➡HttpServlet
➡GenericServlet
➡Servlet
public class DispatcherServlet extends FrameworkServlet { } public abstract class FrameworkServlet extends HttpServletBean { } public abstract class HttpServletBean extends HttpServlet { }
-
디스패처 서블릿도 서블릿 (opens in a new tab)의 일종으로, HttpServlet을 상속함
프론트 컨트롤러 패턴(Front Controller Pattern)
- 도입 배경
- MVC 패턴 중 컨트롤러가 많아지고, 컨트롤러에서 공통으로 처리해야 하는 부분이 증가한다면 코드 중복이 발생함
- 각 컨트롤러마다 공통 로직을 만들어야 했음
- 프론트 컨트롤러 도입 후에는 공통 로직은 프론트 컨트롤러에서 처리하고, 각자 처리해야하는 로직은 각 컨틀롤러에 처리함
- 공통의 관심사를 별도로 모으는 역할
- MVC 패턴 중 컨트롤러가 많아지고, 컨트롤러에서 공통으로 처리해야 하는 부분이 증가한다면 코드 중복이 발생함
- 특징
- 서블릿 하나로 클라이언트의 요청을 모두 받음
- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출함
- 공통 처리 가능
- 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨 ➡ 프론트 컨트롤러가 나머지 컨트롤러를 직접 호출해주기 때문
- 스프링 웹 MCV의 핵심으로,
DispatcherServlet
이 FrontController 패턴으로 구현됨
- 도입 이후 처리과정
- 클라이언트 - HTTP 요청
- Front Controller - URL 매핑 정보에서 컨트롤러 조회 후 컨트롤러 호출
- Controller - ModelAndView 반환
- Front Controller - ViewResolver 호출
- ViewResolver - View*반환
- Front Controller - 클라이언트에 View 응답을 전달
프론트 컨트롤러 패턴의 역할
- 클라이언트로 부터 받은 요청이 어떤 Controller에 대한 요청인지 맵핑
- 해당 Controller에 대한 요청을 처리 후 Response를 ModelView로 반환
Map<String, String> param = createParamMap(request):
ModelView mv = controller.process(param);
- 반환된 ModelView를 통해 적절한 View를 찾음(ViewResolver)
- View를 찾았다면 해당 View를 통해 render()를 호출해서 view를 렌더링함
어댑터 패턴(Adapter Pattern)
- 도입 배경
- 서블릿 컨테이너에서 요청이 오면 모두 디스패처 서블릿을 거쳐 세부 컨트롤러로 가는데, 각각의 세부 컨트롤러 인터페이스는 완전히 다른 인터페이스이기 때문에 서로 간의 호환이 불가능한 상태임
- 이때 어댑터 패턴을 사용하면 기존 구조를 유지하면서 프레임워크의 기능을 확장할 수 있음
- 기존에는 한 가지 방식의 인터페이스만 사용가능했지만, 어댑터 패턴을 이용하여 프론트 컨트롤러가 다양한 인터페이스를 처리할 수 있도록 함
- 특징
- 어댑터 패턴은 호환되지 않는 인터페이스들을 연결하는 디자인 패턴으로, 기존의 클래스를 수정하지 않고도 특정 인터페이스를 필요로 하는 코드에서 사용할 수 있게 도와줌
- 어댑터용 인터페이스를 만들어 처리할 수 있는 컨트롤러를 판단하여 그 컨트롤러를 호출함
스프링 MVC의 핸들러 어댑터(HandlerAdapter) 인터페이스
가 바로 어댑터 패턴을 사용함- 스프링 MVC가 사용하는 어댑터의 이름이 컨트롤러 어댑터가 아니라 핸들러 어댑터인 이유는 컨트롤러 뿐만 아니라, 어댑터가 지원하기만 하면 어떤 것이라도 URL에 매핑하여 사용할 수 있기 때문
- 어댑터 패턴은 호환되지 않는 인터페이스들을 연결하는 디자인 패턴으로, 기존의 클래스를 수정하지 않고도 특정 인터페이스를 필요로 하는 코드에서 사용할 수 있게 도와줌
- 도입 이후 처리과정
- 클라이언트 - HTTP 요청
- Front Controller
- Handler Mapping 정보에서 핸들러 조회
- 핸들러를 처리할 수 있는 Handler Adapter 조회
- 핸들러 어댑터의 handle(handler) 호출
- Handler Adapter
- handler(실제 Controller) 호출
- ModelView 반환
- Front Controller - ViewResolver 호출
- ViewResolver - View 반환
- Front Controller - 클라이언트에 View 응답을 전달
실제 내부 구현
실제 다음과 같은 인터페이스를 상속받은 어댑터가 사용된다.
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
@Deprecated
long getLastModified(HttpServletRequest request, Object handler);
}
어댑터의 종류는 다음과 같다.
RequestMappingHandlerAdapter는 @RequestMapping
어노테이션이 붙은 메소드를 처리하는 어댑터이다.
직접적으로 HandlerAdapter interface를 implements하고 있지는 않지만 HandlerAdapter를 implements 하고 있는 AbstractHandlerMethodAdapter를 상속받아서 AbstractHandlerMethodAdapter의 handle() 메서드에서 handleInternal()을 쓰는 방식인 Template Method Pattern을 사용하고 있다.
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
...
@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
prepareResponse(response);
}
}
return mav;
}
}
디스패처 서블릿의 동작 과정
DispatcherServlet
으로 클라이언트의 웹 요청(HttpServletRequest)이 들어옴- 웹 요청을
HandlerMapping
에 위임하여 해당 요청을 처리할Handler(Controller)
를 탐색 - 찾은 Handler를 실행할 수 있는
HandlerAdapter
를 탐색 - 찾은
HandlerAdapter
를 사용해서 Handler의 메서드를 실행 - 이때 Handler의 반환값은
Model
과View
임 - View 이름을
ViewResolver
에게 전달하고,ViewResolver
는 해당하는 View 객체를 반환함 DispatcherServlet
은 View 에게 Model을 전달하고 화면 표시를 요청하는데, 이때 Model이 null이면 View를 그대로 사용함- 반면 값이 있으면 View에 Model 데이터를 렌더링함
- 최종적으로 DispatcherServlet은 View 결과(HttpServletResponse)를 클라이언트에게 반환함
위 흐름은
@Controller
기준이고,@RestController
의 경우 6, 7과정이 생략된다.
즉, ViewResolver를 타지 않고 반환값에 알맞는MessageConverter
를 찾아 응답을 작성한다.
디스패처 서블릿 설정 방법
디스패처 서블릿을 구현하는 방법에는 2가지가 있다.
-
web.xml
에 DispatcherServlet 선언 방법-
xml에 DispatcherServlet을 선언하고 URI로 요청 경로를 매핑하여 사용할 수 있음
-
표준 J2EE 서블릿 설정 예시
<web-app> <servlet> <servlet-name>example</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>example</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> </web-app>
-
-
DispatcherServlet 상속 후
@Webservlet
애노테이션 사용 방법@WebServlet(name = "helloServlet", urlPatterns = "/hello") public class HelloServlet extends DispatcherServlet { }
- DispatcherServlet을 상속하여
@WebServlet
애노테이션에 urlPatterns를 지정하여 사용할 수 있음 @WebServlet
애노테이션을 사용하려면Httpservlet
을 상속해야 하는데 위에서 말했듯이 DispatcherServlet은 HttpServlet을 상속하기 때문에 서블릿 클래스에서 DispatcherServlet을 상속해도 문제 없음
- DispatcherServlet을 상속하여
Ref
- Spring DispatcherServlet(디스패처서블릿) 개념부터 동작 과정까지 (opens in a new tab)
- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (opens in a new tab)
- [Spring] 📚 Dispatcher Servlet 이해하기 (opens in a new tab)
- [Spring] Dispatcher-Servlet(디스패처 서블릿)이란? 디스패처 서블릿의 개념과 동작 과정 (opens in a new tab)
- DispatcherServlet - Part 1 (opens in a new tab)
- 자바 어댑터 패턴은 어떻게 쓰일까? (opens in a new tab)