우리가 사용하는 Spring Boot 프로젝트를 만들면 내부적으로 이와 같은 흐름이 작동한다.
1. 사용자 요청
2. DispatcherServlet (서블릿)
3. 컨트롤러(@Controller)
4. 서비스
5. 응답
즉, Spring은 우리가 직접 doGet()이나 doPost()를 구현하지 않아도 되도록
DispatcherServlet이 모든 HTTP 흐름을 위임받아 처리하게 된다.
DispatcherServlet의 설정은
Spring Boot에서는 자동으로 등록되기 때문에 직접 설정하지 않아도 된다.

1. 클라이언트의 요청을 Dispatcher Servlet이 가장 먼저 받음
2. DispatcherServlet은 핸들러 매핑(Handler Mapping)을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 탐색
3. 요청을 컨트롤러로 위임할 핸들러 어댑터 (Handler Adapter)를 찾아서 전달
4. 핸들러 어댑터가 컨트롤러로 요청을 위임하고 호출
5. 우리가 작성한 비즈니스 로직을 처리
6. 컨트롤러가 반환값을 반환
7. 핸들러 어댑터에 컨트롤러의 응답이 돌아오면 ModelAndView로 응답을 가공해 반환
8. 서버의 응답을 클라이언트로 반환합니다.


@Nullable
private MultipartResolver multipartResolver;
// 파일 업로드 요청을 처리하는 역할 (ex: 이미지 업로드)
@Nullable
private LocaleResolver localeResolver;
// 사용자의 언어 설정을 처리하는 역할 (국제화 i18n)
@Nullable
private ThemeResolver themeResolver;
// 뷰 테마(스킨) 처리에 사용됨
@Nullable
private List<HandlerMapping> handlerMappings;
// 어떤 URL 요청을 어떤 컨트롤러가 처리할지 매핑해주는 역할
@Nullable
private List<HandlerAdapter> handlerAdapters;
// 실제로 컨트롤러를 실행할 수 있게 도와주는 어댑터 (ex: @RequestMapping, @RestController 등 처리)
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;
// 컨트롤러에서 예외가 발생했을 때 어떻게 처리할지 결정 (ex: @ExceptionHandler)
@Nullable
private RequestToViewNameTranslator viewNameTranslator;
// 뷰 이름을 명시하지 않은 경우, 요청 URL로부터 뷰 이름을 추론하는 기능
@Nullable
private FlashMapManager flashMapManager;
// 다이렉트 시 데이터를 잠시 저장해주는 기능 (RedirectAttributes 내부 동작과 관련)
@Nullable
private List<ViewResolver> viewResolvers;
// 논리적인 뷰 이름(String)을 실제 물리 뷰(JSP, Thymeleaf 등)로 변환
// ...