[Spring] Spring MVC

2025. 9. 15. 22:39·dev/backend

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. 왜 많이 사용할까

  1. 서비스를 3가지 모듈로 나눠서 개발을 하다 보니, 가독성이 좋음.
  2. 동일한 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
'dev/backend' 카테고리의 다른 글
  • [Python] pip 패키지 목록 자동생성
  • [Python] windows .venv 세팅
  • [SpringBoot] SpringBoot3 QueryDSL 정리
  • [SpringBoot] 3.x.x maven 빌더 Lombok cannot find symbol
hand-mk
hand-mk
  • hand-mk
    보조기억장치
    hand-mk
  • 전체
    오늘
    어제
    • 분류 전체보기 (27) N
      • 회고록 (2) N
      • 자격증 (1)
        • aws (1)
      • dev (24)
        • se (5)
        • algorithm (6)
        • ai (3)
        • scm (1)
        • backend (9)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
  • 공지사항

  • 인기 글

  • 태그

    Cloudflare
    exaone3.5
    python
    ubuntu
    vmware
    vectordb
    leetcode
    springboot
    KoNLPy
    codesignal
    폐쇄망
    linux
    코테
    queryDSL
    WSL
    telegraf
    워드클라우드
    docker
    java
    ollama
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
hand-mk
[Spring] Spring MVC
상단으로

티스토리툴바