1. 서론
매 프로젝트 때마다 Spring MVC 기반으로 개발을 했는데 정작 이걸 왜 쓰는지는 왜 좋은건지는 모르고 썼던 것 같다.
더 깊이 있는 공부를 위해 이게 무엇인지 보다 왜 편한지에 중점을 둬서 찾아봤고 이를 기반으로 정리했다.
2. 본론
2.1. MVC 패턴?

MVC 는 Model, View, Controller 로 구분하여 서비스를 개발하는 디자인 패턴이다.
- View: 화면
- Model : DB 와 통신하며 비즈니스 로직 수행
- View, Controller Class 의존 하면 X
- Controller : 클라이언트 요청을 직접 받는 Endpoint
- Model 에겐 Model 에서 정의한 비즈니스 로직에 Dto (Data Transport Object) 송/수신
- 해당 View 에게 Data 송신
2.2. 왜 많이 사용할까
- 서비스를 3가지 모듈로 나눠서 개발을 하다 보니, 가독성이 좋음.
- 동일한 Model 을 여러 View에서 사용할 수 있어서 유연성 향상
예를 한번 들어보자,
해당 홈페이지에는 메인페이지(/mainPage) 와 상세페이지(/detailsPage)가 있다. 두 페이지에는 동일하게 매출액과 사원수를 표출하고 있지만, 각 페이지 별로 데이터를 조회하는 로직을 별도로 호출 중이다.
갑자기 고객사나 감리가 와서 DB에 데이터는 변동하지 않고 사원수와 매출액을 +10 해서 보여주고 싶은 상황이 발생했을 때, 메인페이지 조회 로직만 +10 을 수정했다면 같은 홈페이지 내에서 페이지별로 회사의 매출액과 사원수가 다르게 찍힐 것이다.

만약 앞선 유연성의 장점을 살려서 Model 계층에서 조회 함수들을 만들고 각 페이지 별로 Model 계층의 함수들을 호출한다면 2번 수정해야 할 거 한번만 수정하면 된다는 얘기다.

2.3. Spring MVC 구조
위에서 봤던 구조를 어노테이션 추가 하나로 구현하기 위해 Spring 에서는 Spring MVC 를 사용하고 있다.

2.3.1. DispatcherServlet, HandlerMapping, HandlerAdapter
DispatcherServlet: Spring 어플리케이션으로 들어오는 모든 요청을 가장 먼저 받고 적절한 컨트롤러에 핸들링 하는 클래스. 실제 클래스를 뜯어보면 보면 DispatcherServlet → FrameworkServlet → HttpServletBean → HttpServlet 순으로 상속 구조가 되어있다. 결국 Servlet 이라는 말이다.
public class DispatcherServlet extends FrameworkServlet {
}
public abstract class FrameworkServlet extends HttpServletBean {
}
public abstract class HttpServletBean extends HttpServlet {
}
DispatcherServlet은 왜 좋은가?
DispatcherServlet 이 없을 때에는 HttpServlet을 상속한 Servlet class 를 정의하고 web.xml 에서 매핑 설정을 해야만 Servlet Container 가 작동했다.
이걸 @RestController 또는 @Controller 와 같은 스페셜 빈을 정의하면 알아서 찾아서 매핑한다는 게 아주 큰 장점이다.
실제 코드로 비교해보면,
Servlet 을 사용했을 때
MainServlet.java
@WebServlet("/main")
public class MainServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>Main Page</h1>");
}
}
@WebServlet("/details")
public class DetailsServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>Details Page</h1>");
}
}
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>MainServlet</servlet-name>
<servlet-class>com.example.MainServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MainServlet</servlet-name>
<url-pattern>/main</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>DetailsServlet</servlet-name>
<servlet-class>com.example.DetailsServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DetailsServlet</servlet-name>
<url-pattern>details</url-pattern>
</servlet-mapping>
</web-app>
Spring MVC 를 사용하면
@Controller
@RequestMapping("/main")
public class MainController {
@GetMapping
public String getMainInfo(Model model) {
model.addAttribute("message", "Main Page");
return "main";
}
}
어노테이션 하나로 매핑 과정을 생략하는 것을 볼 수 있다.
HandlerMapping, HandlerAdapter: DispatcherServlet 은 해당하는 controller 를 찾는 과정을 HandlerMapping 과 HandlerAdapter 에 역할을 위임한다.
DispatcherServlet 클래스를 뜯어보면 controller를 찾는 과정을 이해하기 쉽다.
DispatchServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 요청 처리 관련 코드...
// Handler 찾기
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
// 핸들러를 찾지 못한 경우 처리
noHandlerFound(request, response);
return;
}
// 찾은 핸들러로 핸들러 어댑터 찾기
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 핸들러 실행
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// ModelAndView 처리 및 뷰 렌더링
processDispatchResult(request, response, mappedHandler, mv);
}
doDispatch() 를 통해 Controller 를 찾는데, getHandler() 를 호출 하는 것을 볼 수 있고, 찾은 Handler 로 HandlerAdapter 를 찾는걸 볼 수 있다. (Handler 결과값 -> HandlerAdapter 전달)
HandlerMapping.java 구현체
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
getHandler() 구현체는 이렇게 되어 있는데, for 문 안에 List<HandlerMapping> 를 탐색하는 로직이 핵심이다.
@RestController 나 @Controller 로 빈을 등록하면 HandlerMapping 객체 리스트에 해당 클래스가 포함되고 등록된 HandlerMapping 중 request 에 걸맞는 핸들러(controller) 를 가져오는 방식이다.
HandlerAdapter.java 구현체
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
Iterator var2 = this.handlerAdapters.iterator();
while(var2.hasNext()) {
HandlerAdapter adapter = (HandlerAdapter)var2.next();
if (adapter.supports(handler)) { // 해당 핸들러가 adapter 를 지원하면 메서드 호출
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
adapter.supports() 에서 해당 handler 가 adapter 를 지원하면 해당 핸들러 (컨트롤러) 메서드를 반환한다.
이런 Spring MVC 에서 제공하는 모듈들로, 우리는 쉽게 MVC 패턴을 사용할 수 있는 것이다.
'dev > backend' 카테고리의 다른 글
| [Python] BeautifulSoup 크롤링 (0) | 2025.12.24 |
|---|---|
| [Python] pip 패키지 목록 자동생성 (0) | 2025.12.24 |
| [Python] windows .venv 세팅 (1) | 2025.12.22 |
| [SpringBoot] SpringBoot3 QueryDSL 정리 (0) | 2025.01.05 |
| [SpringBoot] 3.x.x maven 빌더 Lombok cannot find symbol (2) | 2024.12.12 |
