Ⅰ. Spring MVC 구조

1. 요청이 오면 Controller에서 파라미터 정보 확인하여 비지니스 로직을 실행한다.
2. 비지니스 로직의 결과 Data 를 Model에 담아서 View에 전달해준다.
3. View는 모델의 Data를 참조하여 화면을 그려준다.

- `DispatcherServlet` : Spring의 프론트 컨트롤러
- `View` : 인터페이스로 구성되어 있다, 확장성을 가지고 있다.
📚 Spring MVC의 프론트 컨트롤러는 Dispatcher Servlet(Servlet의 한 종류)이다.Ⅱ. Dispatcher Servlet
→ Spring MVC의 핵심 기능

1. Dispatcher Servlet은 HttpServlet을 상속 받아서 사용하고 Servlet의 한 종류이다.
2. Spring Boot는 Dispatcher Servlet을 서블릿으로 자동으로 등록(내장 Tomcat WAS를 실행하면서 등록한다)하고 모든 URL 경로에 대해서 Mapping 한다. → `(urlPatterns=”/”)`
3. 더 자세한 URL 경로가 높은 우선순위를 가진다.
- 개발자가 만들 Servlet이 항상 우선순위가 높아서 실행된다.
service()
1. Servlet이 호출되면 HttpServlet이 제공하는 `service()`가 호출된다.
2. Spring MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service()를 Override 해두었다.
3. `FrameworkServlet.service()`를 시작으로 여러 메서드가 호출됨과 동시에 가장 중요한`DispatcherServlet.doDispatch()`가 호출된다.

protected void doDispatch() {
...
// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response); // NotFound 404
}
// 2. 핸들러 어댑터 조회 : 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행
// 4. 핸들러 어댑터를 통해 핸들러 실행
// 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 여기 안에서 render
processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);
...
}
// processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
if (mv != null && !mv.wasCleared()) {
// View Render 호출
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
}
// render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
// 6. ViewResolver를 통해 View 조회
// 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. View Rendering
view.render(mv.getModelInternal(), request, response);
}
📚 Controller Interface를 implements 하여 구현하게되면 개발자가 원하는 Controller(Handler)를 사용할 수 있게됩니다. 💡 추후 강의에 등장할 현대에 사용하는 Annotation 기반 Spring의 `@Controller`와는 역할이 비슷하지만 연관은 없습니다.Ⅲ. Controller Interface
package com.example.springbasicmvc.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
// Spring Bean 이름을 URL로 설정
@Component("/example-controller")
public class ExampleController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("example-controller가 호출 되었습니다.");
return null;
}
}http://localhost:8080/example-controller 로 HTTP 요청을 하게 되면 응답결과가 반환된다.
- 출력 결과

ExampleController는 어떻게 호출되는 것일까요?
❗ Spring MVC 구조에서 배운 Handler Mapping, Handler Adapter가 해당 컨트롤러(핸들러)가 호출되도록 만들어 줍니다.

Handler Mapping
→ Spring Bean의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
Handler Adapter
→ Controller Interface를 실행할 수 있는 Handler Adapter를 찾고 실행한다.
Spring Boot를 사용하면 이미 개발에 필요한 HandlerMapping과 HandlerAdapter를 대부분 구현되어 있어서 개발자가 직접 HandlerMapping과 HandlerAdapter를 구현하는 일은 거의 없습니다.
📚 Spring Boot를 사용하면 개발에 필요하여 자동으로 등록되는 HandlerMapping과 HandlerAdapter들이 있다. 💡 HandlerMapping, HandlerAdapter 모두 우선순위대로 조회한다.Ⅳ. Handler Mapping, Handler Adapter
HandlerMapping
@RequestMapping에 사용HandlerAdapter
@RequestMapping에서 사용@RequestMapping 은 가장 높은 우선순위의 HandlerMapping인 RequestMappingHandlerMapping 과 가장 높은 우선순위의 HandlerAdapter인 RequestMappingHandlerAdapter 두가지를 사용하며 현대에 사용하는 Annotation 기반의 컨트롤러를 지원한다.
HttpRequestHandler로 알아보는 Spring MVC 동작 순서
기존 방식에서 사용하는 Servlet과 가장 유사한 Handler이다.
예시코드
// 인터페이스
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
}
// 구현체
@Component("/request-handler")
public class ExampleRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("request-handler Controller 호출");
// 구현 로직
}
}



HandlerMapping 으로 핸들러 조회
HandlerAdapter 조회
HttpRequestHandlerAdapter.supports()
HandlerAdapter 실행
HttpRequestHandlerAdapter.handle()
- DispatcherServlet에서 호출 → `ha.handle()`


Ⅴ. View Resolver
반환된 ModelAndView 객체를 알맞은 View로 전달하기 위해 DispatcherServlet에서 ViewResolver를 호출하여 View 정보를 설정하는 역할을 수행한다.
package com.example.springbasicmvc.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
// Spring Bean 이름을 URL로 설정
@Component("/view-controller")
public class ViewController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("view-controller가 호출 되었습니다.");
// "test"는 논리적인 ViewName이다. ViewResolver가 물리적인 이름으로 변환해야 한다.
return new ModelAndView("test");
}
}
webapp/WEB-INF/form.JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>블로그 포스트 작성 페이지</title>
</head>
<body>
<h1>블로그 글쓰기</h1>
<form action="save" method="post">
title: <input type="text" name="title" placeholder="제목" />
content: <input type="text" name="content" placeholder="내용" />
<button type="submit">저장</button>
</form>
</body>
</html>
spirng.mvc.view.prefix=/WEB-INF/views/
spirng.mvc.view.suffix=.jsp


@Component("/error-controller")
public class WhitelabelErrorController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("error-controller가 호출 되었습니다.");
// viewName "sparta"는 존재하지 않는다.
return new ModelAndView("sparta");
}
}


Spring Boot를 사용하면 개발에 필요하여 자동으로 등록되는 ViewResolver들이 있다.
// 아래 코드를 자동으로 해주는것과 마찬가지이다.
@Bean
InternalResourceViewResolver internalResourceViewResolver() {
return new InternalResourceViewResolver("/WEB-INF/views", ".jsp");
}
Ⅶ. InternalResourceViewResolver로 알아보는 Spring MVC 동작 순서
application.properties 설정 파일에 등록한 prefix, suffix 설정 정보를 사용하는 ViewResolver

1. **HandlerAdapter 호출**
- HandlerAdapter를 통해 `“test”` 논리 View Name 얻음
2. **ViewResolver 호출**
- `”test”` 이라는 View Name으로 viewResolver를 우선순위 대로 호출
- BeanNameViewResolver는 View를 찾지 못한다.
- InternalResourceViewResolver 호출
3. **InternalResourceViewResolver**
- `InternalResourceViewResolver.buildView(String viewName)`
→ **InternalResourceView** 반환


`renderMergedOutputModel()` → Model을 Request로 바꾼다.

view.render()
view.render()를 호출 후 ****RequestDispatcher를 가져와 forward()한다.→ 매우 복잡한 구조를 가지고 있으니 모두 찾아볼 필요가 없습니다.
Thymeleaf는 View와 Resolver가 이미 존재한다. 라이브러리 의존성만 추가해주면 SpringBoot가 모두 자동으로 해준다. 즉, return “viewName”; 만으로 View가 Rendering 된다.