서블릿
웹 서버는 요청에 대해 정적 웹 페이지로 응답하는 방식의 서버이다. 여기에 동적인 응답을 내려주기 위해서 서블릿을 붙인 서버를 WAS(Web Application Server)라고 한다. 하지만 정적 페이지를 내려주기 위한 요청까지 처리하기에는 WAS에 부하가 걸릴 수 있으므로, 현재는 WAS 앞에 WS(웹 서버)를 붙여서 4 tier 아키텍처의 구조로 서버를 구성하는 것이 일반적이다.
그렇다면 서블릿을 붙였다는 것이 무슨 의미일까? HTTP 헤더에는 HTTP Method, Content-type, User-Agent, Host, Connection, Date, Keep-Alive 등의 다양한 옵션들이 존재한다. 만약 개발자들이 이러한 옵션들을 텍스트로 파싱하고 규약에 따라서 응답을 내려주어야 한다면 그것만으로 꽤나 번거로운 과정이 될 것이다.
이를 보완하기 위해서 서블릿(Servlet)을 사용할 수 있다. 서블릿이란 WAS가 동적인 페이지를 제공할 수 있도록 도와주는 컴포넌트를 말한다.
웹 서버가 동적 요청을 받으면 WAS는 가장 먼저 Servlet 객체의 service() 메서드를 실행한다. 그리고 서블릿 컨테이너는 해당 요청에 대한 HttpServletRequest, HttpServletResponse 객체를 생성하고, 해당 요청을 실행할 적절한 서블릿을 찾는다.
서블릿은 싱글톤으로 관리되기 떄문에, 만약 서블릿 인스턴스가 존재한다면 재사용하고, 존재하지 않는다면 init() 메서드를 통해서 서블릿 인스턴스와 쓰레드를 생성한 후 만들어둔 HttpServletRequest, HttpServletResponse 객체를 인자로 넣어 서비스 코드를 호출하게 된다.
하지만 이러한 패턴은 각 요청에 대해서 서블릿 인스턴스가 존재해야 하기 때문에, 서블릿마다 요청 처리 과정에 대한 공통 코드가 중복으로 존재하게 된다. 이러한 비효율성을 해결하기 위해서 도입한 방법이 바로 Front Controller 패턴이다.
Front Controller
사용자 인터페이스에서 비즈니스 로직과 뷰의 분리를 위해서 MVC패턴을 사용했다. 이때 Controller는 뷰에서 요청이 들어왔을 때 요청을 받아 처리하고, 뷰에서 필요한 적절한 데이터를 모델로 전달한다.
그런데 하나의 웹 어플리케이션에서 각각의 기능들에 대해서 컨트롤러가 각각 따로 존재하여 독립적으로 동작하면, 공통 기능을 갖는 메소드들이 중복하여 존재하게 된다. 이러한 문제를 해결하기 위해 스프링은 모든 컨트롤러 호출 이전에 공통 기능을 처리하는 컨트롤러를 새로 만들어 사용하는데, 이 컨트롤러를 '프론트 컨트롤러', 이러한 패턴을 '프론트 컨트롤러 패턴'이라고 한다.
스프링 웹 MVC의 핵심 또한 DispatcherServlet이라는 프론트 컨트롤러이다.
스프링 MVC에서 request를 받고 response를 전송하는 앞단에는 'DispatcherServlet'이 프론트 컨트롤러의 역할을 하는 패턴으로 구현되어 있다. 실제 동작 순서는 다음과 같으며, DispatcherServlet이 해당 작업들을 주관한다.
1. 핸들러 조회: url에 매핑된 핸들러(컨트롤러의 메서드)를 조회한다.
2. 핸들러 어댑터 조회: 조회한 컨트롤러를 처리할 수 있는 핸들러 어댑터를 조회한다.
3~4. 핸들러 어댑터/핸들러 실행: 조회한 핸들러 어댑터를 실행시키면, 실행된 핸들러 어댑터가 실제 핸들러(컨트롤러)를 실행한다
== 뷰 처리 ==
5. ModelAndView 반환: 핸들러가 반환하는 정보를 ModelAndView로 변환하여 반환한다.
6. 뷰 리졸버 호출 : 뷰 리졸버를 실행한다.
7. View 반환: 뷰 리졸버가 렌더링 역할을 하는 뷰 객체를 반환한다.
8. 뷰 렌더링: 뷰를 통해서 렌더링한다.
예시)
@Controller
public class Controller {
@GetMapping("url")
public String r(@RequestBody Data data) {
//logic
return "path";
}
}
[1~4] 먼저 클라이언트의 request가 들어오면, DispatcherServlet이라는 프론트 컨트롤러가 URI 매핑된 컨트롤러를 찾고, 요청 주소에서 들어오는 http요청(@RequestBody, @ModelAttribute, @RequestParam 등)의 content-type 미디어 타입을 지원하는지 등의 여부로 적절한 핸들러 어댑터를 찾고 그 어댑터가 그 컨트롤러에서 적절한 비즈니스 로직을 실행한다.
[5] 컨트롤러는 비즈니스 로직을 실행한 이후에 처리 결과를 핸들러 어댑터로 반환하고, 어댑터는 반환받은 결과를 ModelAndView형태로 변환하여 DispatcherServlet에게 반환한다.
이 때 ModelAndView에는 컨트롤러에서 처리 후 응답할 뷰와, 뷰에 전달할 값이 저장된다.
(상단 코드에서는 path라는 문자열이 ModelAndView꼴로 변환되어 반환될것이다)
[6~7] DispatcherServlet은 뷰 리졸버에게 ModelAndView객체를 넘기고, 뷰 리졸버가 ModelAndView객체 안에 저장된 뷰 객체를 찾고 suffix, prefix등을 추가하여 물리 주소로 변환하여 뷰 객체를 반환해준다.
만약 @ResponseBody(@RestController) 애노테이션이 존재한다면 뷰 리졸버가 실행되지 않고, 문자열이 화면에 그대로 뿌려진다.
(상단 코드에서는 ModelAndView의 path에 suffix와 prefix를 추가한 물리 주소에 매핑되는 뷰가 반환될 것이다)
[8] 뷰 리졸버가 찾거나 만들어서 반환한 뷰 객체가 클라이언트에 전달되면 response가 종료된다.
정리
따라서 스프링으로 웹 요청을 처리하면 스프링 MVC에서 제공하는 DispatcherServlet과 웹 요청 처리 관련 구현체들을 사용할 수 있게 되어 개발자들이 비즈니스 로직(핸들러)에만 최대한으로 신경 쓸 수 있다.
'[ Backend ] > Spring Web' 카테고리의 다른 글
[Spring MVC] 메세지, 국제화 (0) | 2022.07.08 |
---|---|
[Spring MVC] URL 매핑, 데이터 전송/조회 (0) | 2022.02.19 |
[Spring MVC] 웹 서버, WAS, 서블릿 (0) | 2022.02.08 |
[Spring MVC] @~Mapping 사용시 참고 (0) | 2022.02.04 |
[Spring MVC] 역할의 분리와 디자인 패턴 - Spring MVC 동작 (0) | 2022.02.01 |