Servlet 란?
디스패처 서블릿을 알아보기 전에 서블릿이 무엇인지 알아보자.
서블릿이란 Java에서 웹 요청을 처리하고 응답을 생성하는 서버 측 프로그램이다.
웹 서버(Tomcat 등)에서 동작하며, 클라이언트(웹 브라우저)로부터 요청을 받아 HTML,
JSON 등의 응답을 생성하여 반환하는 역할을 한다.
Dispatcher-Servlet이란?
Spring MVC에서 클라이언트의 모든 요청을 받아 적절한 컨트롤러로 전달하고는 프론트 컨트롤러(Front Controller)이다.
여기서 프론트 컨트롤러란 주로 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아서 처리해 주는
컨트롤러로서 , MVC 구조에서 함께 사용되는 디자인 패턴이다.
Dispatcher-Servlet의 장점
과거에는 모든 서블릿을 URL 매핑을 위해 web.xml에 모두 등록해주어야 했지만, dispatcher-servlet이 해당 애플리케이션으로
들어오는 모든 요청을 핸들링해 주고 공통 작업을 처리하면서 편리하게 이용할 수 있게 되었다.
따라서 우리는 컨트롤러를 구현만 해두면 디스패처 서블릿이 알아서 적합한 컨트롤러로 위임하는 구조가 되었다.
Dispatcher-Servlet의 동작 방식
Dispatcher-Servlet의 동작과정을 알아보면 아래와 같다.
1. 클라이언트의 요청을 디스패처 서블릿이 받음
(ex GET /users/1과 같은 요청 @WebServlet("/*") 설정으로 요청을 가로챔)
2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음
요청을 처리할 컨트롤러를 찾기 위해 Handler Mapping이 실행된다.
3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함
HandlerMapping을 통해 찾은 컨트롤러는 단순한 객체일 뿐이므로,
DispatcherServlet이 적절한 HandlerAdapter를 찾아서 컨트롤러를 실행한다.
4. 핸들러 어댑터가 컨트롤러로 요청을 위임함
5. 비즈니스 로직을 처리함
Service Layer, Repository Layer 호출
6. 컨트롤러가 반환값을 반환함
컨트롤러는 처리된 데이터를 ResponseEntity 또는 ModelAndView 형태로 반환한다.
7. 핸들러 어댑터가 반환값을 처리함
이때 반환된 객체를 HTTP 응답으로 변환하기 위한 추가적인 처리 수행.
(@RestController의 경우 JSON 응답으로 반환.)
8. 서버의 응답을 클라이언트로 반환함.
최종적으로 DispatcherServlet이 HTTP 응답을 클라이언트에 반환.
이제 각 단계별로 조금 더 자세하게 알아보자.
1. 클라이언트의 요청을 디스패처 서블릿이 받음
디스패처 서블릿은 Spring MVC의 프론트 컨트롤러 역할을 수행하며, 클라이언트의 요청을 가장 먼저 받아
적절한 컨트롤러로 전달한다.
위의 그림에서 볼 수 있듯 클라이언트의 요청이 발생하면, 요청은 Servlet Context(Web Context) 내에서 필터들을
거친 후 스프링 컨텍스트로 전달된다. 이때 요청을 처음으로 처리하는 것이 바로 디스패처 서블릿이다.
(실제로 Interceptor가 Controller로 요청을 위임하지는 않으므로, 그림은 처리 순서를 도식화한 것으로만 이해하면 된다.)
2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음
디스패처 서블릿은 클라이언트의 요청을 처리할 핸들러(컨트롤러)를 식별하고 해당 메서드를 호출하는 역할을 한다.
따라서 가장 먼저 요청을 처리할 적절한 컨트롤러를 찾아야 하는데, 이 과정에서 HandlerMapping이 중요한 역할을 한다.
스프링 MVC에서는 요청을 어떤 컨트롤러가 처리할지 매핑하는 방식이 다양할 수 있다.
- 최근에는 @Controller와 @RequestMapping 어노테이션을 사용하는 방식이 일반적이다.
- 예전에는 Controller 인터페이스를 직접 구현하는 방식도 존재했다.
이처럼 컨트롤러를 구현하는 방법이 여러 가지 존재하기 때문에 스프링은 HandlerMapping 인터페이스를 도입하여
요청을 적절한 컨트롤러에 매핑하는 역할을 수행한다.
현재 일반적으로 사용하는 @Controller 기반 방식의 요청 매핑은 RequestMappingHandlerMapping이 담당한다.
그 과정은 아래와 같다.
1. 서버 실행 시, RequestMappingHandlerMapping이 @Controller가 붙은 모든 컨트롤러 클래스를 탐색
2. 각 컨트롤러의 메서드에 선언된 @ReqeustMapping 정보를 분석하여 (요청 정보, 처리할 대상) 형태로 HashMap에 저장
- 여기서 처리할 대상은 HandlerMethod 객체로 저장된다.
- HandlerMethod 객체는 컨트롤러 클래스, 실행할 메서드 정보 등을 포함하고 있으며,
스프링은 이를 리플렉션(Reflection) 기법을 사용하여 실행한다.
3. 요청이 들어오면 (Http Method, URI)를 기반으로 요청 정보를 생성
4. HandlerMapping이 HashMap에서 해당 요청 정보를 찾아 적절한 HandlerMethod를 반환
5. 반환된 HandlerMethod는 HandlerExecutionChain으로 감싸져 DispatcherServlet으로 전달
- HandlerExecutionChain을 사용하는 이유는 컨트롤러 실행 전후에
인터셉터(Interceptor)등을 추가적으로 실행할 수 있도록 하기 위함
핵심
- HandlerMapping은 클라이언트의 요청을 적절한 컨트롤러 메서드로 매핑하는 역할
- RequestMappingHandlerMapping은 @Controller 기반 컨트롤러를 분석하여 요청-핸들러 매핑을 HashMap으로 관리
- 요청이 들어오면 HashMap에서 핸들러를 찾아 HandlerExecutionChain으로 감싸서 DispatcherServlet에 전달
- 인터셉터 실행을 위해 핸들러를 HandlerExecutionChain으로 감싸는 과정이 포함됨
3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함
디스패처 서블릿이 클라이언트의 요청을 적절한 컨트롤러로 위임해야 하는데 이를 직접 수행하지 않고
HandlerAdapter를 통해 위임한다.
그 이유는 컨트롤러의 구현방식이 다양하기 때문이다.
HandlerApdapter가 필요한 이유
스프링은 흐름에 맞게 발전해 왔는데 그 과정에 컨트롤러의 구현 방식도 아래와 같이 변화했다.
1. 초기에는 Controller 인터페이스를 직접 구현하여 컨트롤러를 작성
- 특정 메서드를 오버라이딩하여 요청을 처리하는 방식
2. Ruby on Rails가 등장하며, 어노테이션 기반 프로그래밍이 주목받음
- @Controller, @RequestMapping을 활용하여 요청을 처리하는 방식이 도입됨
이렇듯 컨트롤러가 다양한 방식으로 구현될 수 있다 보니, 요청을 일관된 방식으로 처리하는 것이 어려워짐.
따라서 스프링은 어댑터 패턴(Adapter Pattern)을 적용하여 HandlerApdater 인터페이스를 도입함
4. 핸들러 어댑터가 컨트롤러로 요청을 위임함.
HandlerApdapter가 컨트롤러에 요청을 위임하기 전후로 여러 가지 공통적인 전/후처리 과정이 필요하다.
스프링에서는 이를 위해 인터셉터(Interceptor), ArugumentResolver, ReturnValueHandler 등의 컴포넌트를 활용한다.
컨트롤러 실행 전 (전처리)
1. 인터셉터 실행
- 요청을 가로채어 전처리할 수 있는 HandlerInterceptor가 실행된다.
2. ArgumentResolver를 활용한 요청 데이터 처리
- @RequestParam, @RequestBody 등의 어노테이션을 통해 전달된 요청 파라미터를 적절한 객체로 변환
- 대표적인 ArgumentResolver:
RequestParamMethodArgumentResolver : @RequestParam을 처리
RequestResponseBodyMethodProcessor : @RequestBody를 처리
3. 컨트롤러 실행(HandlerMethod 호출)
- 요청을 처리할 컨트롤러 메서드(HandlerMethod)를 찾고 실행
- 리플렉션(Reflection)을 활용하여 해당 컨트롤러 메서드를 호출
컨트롤러 실행 후(후처리)
4. ReturnValueHandler를 활용한 응답 처리
- 컨트롤러에서 반환된 값(ResponseEntity 등)을 적절한 형태로 변환
- 대표적인 ReturnValueHandler:
RequestResponseBodyMethodProcessor : @ResponseBody를 처리
ModelAndViewMethodReturnValueHandler : ModelAndView를 처리
5. 인터셉터 후처리
- 응답을 가로채 후처리 할 수 있는 HandlerInterceptor가 실행됨
6. 최종적으로 디스패처 서블릿이 응답을 클라이언트로 반환
5. 비즈니스 로직을 처리함
이후에 컨트롤러는 서비스를 호출하고 우리가 작성한 비지니스 로직들이 실행된다.
6. 컨트롤러가 반환값을 반환함
비즈니스 로직이 처리된 후에는 컨트롤러가 반환값을 반환한다.
반환되는 데이터 형태는 두 가지로 나뉠 수 있다.
1. ResponseEntity <T>를 사용하여 HTTP 상태 코드와 함께 JSON 데이터 등을 반환한다.
2. String으로 특정 View의 이름을 반환한다.
요즘은 프론트와 백엔드를 분리하고, MSA로 가는 추세이므로 주로 ResponseEntity로 반환한다.
7. 핸들러 어댑터가 반환값을 처리함
컨트롤러에서 반환된 응답은 HandlerAdapter를 통해 후처리 과정을 거친 후, 최종적으로 디스패치 서블릿으로 전달된다.
반환값의 형태에 따라 처리 방식이 다르다.
1. ResponseEntity <T>를 반환한 경우
- HttpEntityMethodProcessor가 반환값을 처리
- 내부적으로 MessageConverter를 이용하여 객체를 직렬화
- HTTP 상태 코드(HttpStatus)와 함께 응답 데이터가 설정
2. View 반환한 경우
- ViewResolver가 반환값을 해석하여 적절한 View(HTML)로 매핑
8. 서버의 응답을 클라이언트로 반환함
디스패처 서블릿을 통해 반환되는 응답은 다시 필터들을 거쳐 클라이언트에게 반환
1. 응답이 데이터인 경우
- 그대로 반환
2. 응답이 화면인 경우
- ViewResolver가 적절한 화면을 내려줌
(참고)
https://mangkyu.tistory.com/18
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
'Back-end > Spring' 카테고리의 다른 글
[Spring MVC] Bean Validation(검증) (2) | 2024.09.05 |
---|---|
[Spring] 빈 생명주기 콜백 (3) | 2024.08.18 |
[Spring] 싱글톤 컨테이너&싱글톤 패턴 (2) | 2024.08.13 |