스프링 부트의 동작 구조
스프링 부트의 기본 의존성 중 하나인 spring-boot-starter-web 모듈을 사용하면,
기본적으로 내장 톰캣(Tomcat)을 사용하는 스프링 MVC 구조를 기반으로 동작한다.
클라이언트의 요청이 들어오면 서블릿이 이를 처리해야 하는데, 서블릿은 서블릿 컨테이너에서 관리하고,
톰캣이 서블릿 컨테이너의 역할과 WAS(Web Application Server)의 역할을 담당한다.
서블릿(Servlet)은 클라이언트의 요청을 처리하고, 결과를 반환하는 자바의 웹 프로그래밍 기술이다.
서블릿 컨테이너의 특징
- 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리한다.
- 서블릿 객체는 싱글톤 패턴으로 관리된다.
- 멀티 스레딩을 지원한다.
Dispatcher Servlet 이란?
스프링에서는 DispatcherServlet이 서블릿의 역할을 담당한다.
클라이언트의 요청을 받아서 서블릿 컨테이너에서 관리되는 DispatcherServlet이 요청을 처리하고 반환하는 것이다.
DispatcherServlet의 동작
1. 클라이언트 요청
클라이언트에서 요청(HttpServletRequest)이 들어오면, 서블릿 컨테이너는 DispatcherServlet으로 이를 전달한다.
2. Handler 조회
DispatcherServlet은 핸들러 매핑(HandlerMapping)을 통해 요청 URI에 매핑된 핸들러(Controller)를 탐색한다.
3. Handler Adapter 조회
조회한 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
4. Handler Adapter 실행
핸들러 어댑터(HandlerAdapter)를 통해 핸들러(Controller)를 호출한다.
5. Handler(Controller) 실행
핸들러(Controller)를 실행하여 컨트롤러에서 요청을 처리하고, 응답을 다시 핸들러 어댑터로 반환한다.
6. ModelAndView 반환
핸들러 어댑터는 이 응답을 ModelAndView로 가공하여 반환한다.
7-1. @Controller 사용 시
1) View Resolver를 찾고 실행한다.
2) View Resolver는 View의 논리 이름을 물리 이름으로 바꾸고, 랜더링 역할을 담당하는 View 객체를 반환한다.
3) View를 랜더링 하여 클라이언트에 반환한다.
7-2. @RestController 사용 시
1) View와 ViewResolver를 거치지 않는다.
2) Controller로 부터 반환 받은 데이터를 MessageConverter를 거쳐서 Json 형식으로 변환한다.
3) Json을 ResponseBody로 응답한다.
뷰 리졸버를 사용하는 방식
REST API 방식 (MessageConverter)
HandlerMapping, HandlerAdapter 자동 등록
SpringBoot가 핸들러 매핑과 핸들러 어댑터를 자동으로 등록해준다.
HandlerMapping
1순위 : RequestMappingHandlerMapping
- @RequestMapping 을 보고 자동으로 매핑해준다.
2순위 : BeanNameUrlHandlerMapping
- Spring Bean 이름으로 핸들러를 찾아서 매핑한다.
HandlerAdapter
1순위 : RequestMappingHandlerMapping
- @RequestMapping 을 보고 자동으로 매핑해준다.
2순위 : HttpRequestHandlerAdapter
- HttpRequestHandler 처리
DispatcherServlet
부모 클래스에서 HttpServlet을 상속받아서 사용하고, Servlet으로 동작한다.
SpringBoot는 DispatcherServlet을 이용하여 모든 경로( urlPatterns="/" )에 대해 매핑한다.
doDispatch()
DispatcherServlet의 핵심이다.
> IntelliJ에서 doDispatch() 검색 방법
SpringBoot를 사용하는 프로젝트 어디서나 Ctrl + Shift + f로 검색창을 열어서 "범위(S)"를 선택한 뒤 doDispatch를 검색하면 찾을 수 있다.
정상 진행 로직 이외에 예외 처리 등은 생략하였음.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
Exception dispatchException = null;
//핸들러 조회 Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//핸들러 어댑터 조회 Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//실제로 핸들러 어댑터 실행 Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//-> ModelAndView 반환
//doDispatch() 정상 진행
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
//생략
//뷰 랜더링 호출
render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
//뷰 리졸버를 통해서 뷰 찾아서 뷰 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
//뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
View를 사용하는 방법과 REST방식의 차이 (코드)
@Controller 방식 응답
@Controller
public class ViewController {
@Autowired
private MyService myService;
static class ResponseObj{
private int num;
private String msg;
//생성자 생략
}
@GetMapping("/view")
public String useViewResolver(Model model){
ResponseObj resp = new ResponseObj(1, myService.getHello());
model.addAttribute("obj", resp);
return "home";
}
}
위와 같이 model.addAttribute 메서드를 이용하여 model에 객체를 담으면,
ViewResolver가 해당 객체를 View에 담아 응답한다.
@RestController 방식 응답
@RestController
public class RestApiController {
@Autowired
private MyService myService;
static class ResponseObj{
private int num;
private String msg;
//생성자 생략
}
@GetMapping("/rest")
public ResponseEntity<?> useViewResolver(){
ResponseObj resp = new ResponseObj(1, myService.getHello());
return ResponseEntity.ok(resp);
}
}
위와 같이 반환하고자 하는 객체를 그대로 반환한다.
ResponseEntity 클래스는 객체를 JSON으로 변환하여 응답해 준다.
이 글은 '스프링 부트 핵심 가이드' 책을 참고하여 썼습니다.
'컴퓨터 공학 > 프로그래밍 이론' 카테고리의 다른 글
[CS] 객체지향 4대 특성(캡슐화, 상속, 다형성, 추상화) (0) | 2024.02.02 |
---|---|
[Computer Science/디자인 패턴] 전략 패턴(strategy pattern) (0) | 2024.01.25 |
[Spring / 스프링 부트 핵심 가이드] 디자인 패턴 정리 (0) | 2023.10.01 |
[ComputerScience / 디자인 패턴] 브릿지 패턴(Bridge Pattern, 가교 패턴) (0) | 2023.10.01 |
[Spring / 스프링 부트 핵심 가이드] 레이어드 아키텍처, SpringMVC (0) | 2023.10.01 |