티스토리 뷰
[Spring] 디스패처 서블릿(Dispatcher Servlert)이 HTTP 요청을 처리하는 방법과 핸들러 메소드(HandlerMethod)
망나니개발자 2021. 8. 5. 10:00이번에는 디스패처 서블릿(Dispatcher Servlert)이 HTTP 요청을 처리하는 방법에 대해서 알아보도록 하자
1. 디스패처 서블릿(Dispatcher Servlert)이 HTTP 요청을 처리하는 방법과 핸들러 메소드(HandlerMethod)
[ 핸들러 메소드(HandlerMethod)란? ]
HandlerMethod는 @RequestMapping과 그 하위 어노테이션(@GetMapping, @PostMapping 등)이 붙은 메소드의 정보를 추상화한 객체이다. HandlerMethod는 그 자체가 실행가능한 객체가 아니라 메소드를 실행하기 위해 필요한 참조 정보를 담고 있는 객체로써 다음과 같은 정보들을 가지고 있다.
- 빈 객체
- 메소드 메타정보
- 메소드 파라미터 메타정보
- 메소드 어노테이션 메타정보
- 메소드 리턴 값 메타정보
디스패처 서블릿은 애플리케이션이 실행될 때 모든 컨트롤러 빈의 메소드를 살펴서 매핑 후보가 되는 메소드들을 추출한 뒤, 이를 HandlerMethod 형태로 저장해둔다. 그리고 실제 요청이 들어오면 저장해 둔 목록에서 요청 조건에 맞는 HandlerMethod를 참조해서 매핑되는 메소드를 실행한다.
[ RequestMappingHandlerMapping ]
여기서 HTTP 요청을 HandlerMethod 객체로 변환하는 작업하거나 해당 요청에 매핑되는 HandlerMethod를 반환하는 작업은 RequestMappingHandlerMapping이 담당한다. 그래서 애플리케이션 컨텍스트가 초기화되면 RequestMappingHandlerMapping에 접근하여 저장된 매핑 정보와 핸들러 메소드 목록을 확인할 수 있다.
만약 새롭게 API를 개발하였는데 404 에러가 발생하는 경우에 RequestMappingHandlerMapping를 사용하면 도움을 받을 수 있다.
[ 디스패처 서블릿(Dispatcher Servlert)이 HTTP 요청을 처리하는 방법 ]
앞서 설명한대로 디스패처 서블릿은 적합한 컨트롤러와 메소드를 찾아 요청을 위임해야 합니다. Dispatcher Servlet의 처리 과정을 살펴보면 다음과 같습니다.
- 클라이언트의 요청을 디스패처 서블릿이 받음
- 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음
- 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함
- 핸들러 어댑터가 컨트롤러로 요청을 위임함
- 비지니스 로직을 처리함
- 컨트롤러가 반환값을 반환함
- HandlerAdapter가 반환값을 처리함
- 서버의 응답을 클라이언트로 반환함
아래에서는 디스패처 서블릿의 동작 과정을 조금 구체적으로 살펴보도록 하겠습니다. 만약 아래의 내용을 완벽히 이해하기 어렵다면 "디스패처 서블릿을 통해 요청을 처리할 컨트롤러를 찾아서 위임하고, 그 결과를 받아오는구나" 정도로만 이해해도 괜찮습니다.
1. 클라이언트의 요청을 디스패처 서블릿이 받음
앞서 설명하였듯 디스패처 서블릿은 가장 먼저 요청을 받는 프론트 컨트롤러입니다. 서블릿 컨텍스트(웹 컨텍스트)에서 필터들을 지나 스프링 컨텍스트에서 디스패처 서블릿이 가장 먼저 요청을 받게됩니다. 이를 그림으로 표현하면 다음과 같습니다. 실제로는 Interceptor가 Controller로 요청을 위임하지는 않으므로 아래의 그림은 처리 순서를 도식화한 것으로만 이해하면 됩니다.
2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음
디스패처 서블릿은 요청을 처리할 핸들러(컨트롤러)를 찾고 해당 객체의 메소드를 호출합니다. 따라서 가장 먼저 어느 컨트롤러가 요청을 처리할 수 있는지를 식별해야 하는데, 해당 역할을 하는 것이 바로 HandlerMapping입니다.
최근에는 @Controller에 @RequestMapping 관련 어노테이션을 사용해 컨트롤러를 작성하는 것이 일반적입니다. 하지만 예전 스펙을 따라 Controller 인터페이스를 구현하여 컨트롤러를 작성할 수도 있습니다. 즉, 컨트롤러를 구현하는 방법이 다양하기 때문에 스프링은 HandlerMapping 인터페이스를 만들어두고, 다양한 구현 방법에 따라 요청을 처리할 대상을 찾도록 되어 있습니다.
오늘날 흔한 @Controller 방식은 RequestMappingHandlerMapping가 처리합니다. 이는 @Controller로 작성된 모든 컨트롤러를 찾고 파싱하여 HashMap으로 <요청 정보, 처리할 대상> 관리합니다. 여기서 처리할 대상은 HandlerMethod 객체로 컨트롤러, 메소드 등을 갖고 있는데, 이는 스프링이 리플렉션을 이용해 요청을 위임하기 때문입니다.
그래서 요청이 오면 (Http Method, URI) 등을 사용해 요청 정보를 만들고, HashMap에서 요청을 처리할 대상(HandlerMethod)를 찾은 후에 HandlerExecutionChain으로 감싸서 반환합니다. HandlerExecutionChain으로 감싸는 이유는 컨트롤러로 요청을 넘겨주기 전에 처리해야 하는 인터셉터 등을 포함하기 위해서입니다.
3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함
이후에 컨트롤러로 요청을 위임해야 하는데, 디스패처 서블릿은 컨트롤러로 요청을 직접 위임하는 것이 아니라 HandlerAdapter를 통해 위임합니다. 그 이유는 앞서 설명하였듯 컨트롤러의 구현 방식이 다양하기 때문입니다.
스프링은 꽤나 오래 전에(2004년) 만들어진 프레임워크로, 트렌드를 굉장히 잘 따라갑니다. 프로그래밍 흐름에 맞게 스프링 역시 변화를 따라가게 되었는데, 그러면서 다양한 코드 작성 방식을 지원하게 되었습니다. 과거에는 컨트롤러를 Controller 인터페이스로 구현하였는데, Ruby On Rails가 어노테이션 기반으로 관례를 이용한 프로그래밍을 내세워 혁신을 일으키면서 스프링 역시 이를 도입하게 되었습니다.
그래서 다양하게 작성되는 컨트롤러에 대응하기 위해 스프링은 HandlerAdapter라는 어댑터 인터페이스를 통해 어댑터 패턴을 적용함으로써 컨트롤러의 구현 방식에 상관없이 요청을 위임할 수 있도록 하였습니다.
4. 핸들러 어댑터가 컨트롤러로 요청을 위임함
핸들러 어댑터가 컨트롤러로 요청을 위임한 전/후에 공통적인 전/후처리 과정이 필요합니다. 대표적으로 인터셉터들을 포함해 요청 시에 @RequestParam, @RequestBody 등을 처리하기 위한 ArgumentResolver들과 응답 시에 ResponseEntity의 Body를 Json으로 직렬화하는 등의 처리를 하는 ReturnValueHandler 등이 핸들러 어댑터에서 처리됩니다. ArgumentResolver 등을 통해 파라미터가 준비 되면 리플렉션을 이용해 컨트롤러로 요청을 위임합니다.
요청을 처리할 대상 정보인 HandlerMethod 객체에는 컨트롤러와 메소드 객체가 있으므로 리플렉션의 메소드 객체를 invoke 합니다. 사실 실제로는 HandlerMethod에 컨트롤러 빈 이름과 메소드, 빈 팩토리가 있어서 빈 팩토리에서 컨트롤러 빈을 찾습니다. 그리고 해당 컨트롤러 빈 객체로부터 리플렉션을 사용하는데, 그렇게 중요하지는 않으므로 따로 관심이 있는 사람들은 이 글을 참고해주세요.
5. 비지니스 로직을 처리함
이후에 컨트롤러는 서비스를 호출하고 우리가 작성한 비지니스 로직들이 진행됩니다.
6. 컨트롤러가 반환값을 반환함
비지니스 로직이 처리된 후에는 컨트롤러가 반환값을 반환합니다. 응답 데이터를 사용하는 경우에는 주로 ResponseEntity를 반환하게 되고, 응답 페이지를 보여주는 경우라면 String으로 View의 이름을 반환할 수도 있습니다. 요즘 프론트엔드와 백엔드를 분리하고, MSA로 가고 있는 시대에서는 주로 ResponseEntity를 반환합니다.
7. HandlerAdapter가 반환값을 처리함
HandlerAdapter는 컨트롤러로부터 받은 응답을 응답 처리기인 ReturnValueHandler가 후처리한 후에 디스패처 서블릿으로 돌려줍니다. 만약 컨트롤러가 ResponseEntity를 반환하면 HttpEntityMethodProcessor가 MessageConverter를 사용해 응답 객체를 직렬화하고 응답 상태(HttpStatus)를 설정합니다. 만약 컨트롤러가 View 이름을 반환하면 ViewResolver를 통해 View를 반환합니다.
8. 서버의 응답을 클라이언트로 반환함
디스패처 서블릿을 통해 반환되는 응답은 다시 필터들을 거쳐 클라이언트에게 반환됩니다.
'Spring' 카테고리의 다른 글
[Spring] TDD로 멤버십 등록 API 구현 예제 - (3/5) (24) | 2021.08.22 |
---|---|
[Spring] TDD 연습문제 소개와 요구사항 분석 및 SpringBoot 프로젝트 설정 - (2/5) (15) | 2021.08.19 |
[Spring] 캐시(Cache) 추상화와 사용법(@Cacheable, @CachePut, @CacheEvict) (3) | 2021.08.03 |
[Spring] 활성 프로파일(Profile)의 관리를 위한 @Profile과 @ActiveProfiles (0) | 2021.08.01 |
[Spring] BeanFactoryPostProcessor 타입의 빈(PropertySourcesPlaceholderConfigurer) 생성 메소드를 static으로 선언해야 하는 이유 (0) | 2021.08.01 |