HJW's IT Blog

프론트 컨트롤러와 DispatcherServlet 본문

SPRING

프론트 컨트롤러와 DispatcherServlet

kiki1875 2025. 1. 9. 15:35

 

들어가며

DispatcherServlet은 Spring의 핵심 요소중 하나이다. DispatcherServlet은 Spring의 프론트 컨트롤러로서, 모든 웹 요청의 진입점 역할을 담당한다. HTTP 요청을 가장 먼저 받아 적절한 컨트롤러로 위임하는 것이 주 역할이다. DispatcherServlet과 Spring의 동작 과정을 잘 이해하기 위해선 프론트 컨트롤러 패턴의 개념과 장점에 대해 확실하게 짚고 넘어가야 한다.

1. 초기 웹 어플리케이션 개발의 문제점

초기 웹 어플리케이션 개발에선, 각 요청에 대해 개별적으로 처리하는 서블릿 혹은 JSP 를 작성하는 것이 일반적이었다. 한번 JSP 코드를 한번 보고 넘어가자.

 

<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    MemberRepository memberRepository = MemberRepository.getInstance();
    List<Member> members = memberRepository.findAll();
%>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/index.html">메인</a>
<table>
    <thead>
    <th>id</th>
    <th>username</th>
    <th>age</th>
    </thead>
    <tbody>
    <%
        for (Member member : members) {
            out.write(" <tr>");
            out.write(" <td>" + member.getId() + "</td>");
            out.write(" <td>" + member.getUsername() + "</td>");
            out.write(" <td>" + member.getAge() + "</td>");
            out.write(" </tr>");
        }
    %>
    </tbody>
</table>
</body>
</html>
 

코드의 절반은 비즈니스 로직, 나머지 절만은 HTML 로 렌더링 하기 위한 View 영역이다. 또한 이러한 jsp 페이지 하나당 각각 별도의 컨트롤러를 거쳐야 하는데, 이런 식으로 반환되는 jsp 에는 비즈니스 로직, 데이터베이스 접근, 중복된 코드 등 여러 문제가 있다.

 

프론트 컨트롤러 도입 전

1.1 문제 1 → 중복된 코드

각각의 서블릿에서 로깅, 인증, 인코딩과 같은 공통 기능이 반복적으로 작성되는 문제가 있다. 이는 단순히 코드 중복을 넘어서 심각한 유지보수 문제를 야기한다. 코드가 전혀 모듈화 되어있지 않고, 여러 로직이 혼재된 상태의 코드탓에 여러 공통 로직이 여러 서블릿에 반복적으로 작성되기 쉽다. 이 경우, 만약 공통 기능의 수정이 필요하다 가정해 보자. 관련된 모든 서블릿을 수정해야 하는 상황이 벌어질 것이다.

1.2 문제 2 → 유지 보수의 어려움

다르게 말해 관심사의 분리가 전혀 되지 않는다. 만약 위와 같이 비즈니스 로직과 View 가 혼재된 코드를 유지보수해야 하는 일이 발생한다 생각해 보자. 위의 코드는 사실 짧기 때문에 문제가 없어보이지만, 만약 몇천줄의 jsp 코드를 유지보수해야 한다면 상상만으로 아찔해 진다.

1.3 문제 3 → 확장성 부족

어플리케이션이 커지며 새로운 요구사항이 발생한다면, 적용하기 매우 까다롭다. 예를 들어 새로운 보안 정책이 적용된다면? 관련 jsp 를 하나하나 뒤져가며 수정해야 한다.

2. 프론트 컨트롤러 패턴의 등장

이러한 문제들을 해결하기 위해 등장한 것이 바로 프론트 컨트롤러 패턴이다. 프론트 컨트롤러 패턴의 핵심 아이디어는 모든 요청을 단일 진입점에서 처리하도록 설계된 패턴이다. 마치 건물의 안내 데스크 처럼, 요청을 우선 받아들이고 적절한 처리자에게 전달하는 역할을 하게 된다.

2.1 중앙화된 요청 처리

어플리케이션의 모든 요청이 하나의 컨트롤러로 들어온다면 어떤 장점이 있을까?

  • 요청 프름의 표준화 : 모든 요청이 동일한 처리 흐름을 따르므로, 어플리케이션의 동작 방식을 이해하기 쉬워진다.
  • 공통 로직의 중앙화 : 공통 기능을 프론트 컨트롤러에 집중시켜 중복을 줄일 수 있다. 예를 들어 인증, 권한검사, 로깅 등을 모두 프론트 컨트롤러에서 처리할 수 있다.
  • 일관된 에러 처리 : 예외 혹은 에러응답에 대한 표준화가 가능해진다.

2.2 확장성과 유연성 증가

  • 요청 매핑의 유연성 : URL 과 핸들러의 매핑을 중앙에서 관리하여 새로운 기능 추가, 라우팅 변경등이 용이하다
  • 미들웨어 방식의 처리 : 요청 처리 전 후로 다양한 기능을 사용할 수 있다
  • 다양한 응답 형식 지원 : 어떠한 응답 포맷을 가질 것인지를 중앙에서 관리할 수 있다.

2.3 유지보수성 향상

  • 공통 코드의 집중화 : 변경이 필요할 때 한 곳만 수정하면 되므로 유지보수가 용이하다
  • 책임의 명확한 분리 : 각 컴포넌트의 역할이 명확해져, 코드 관리가 용이하다.
  • 설정의 중앙화 : 보안, 리소스 등의 설정을 중앙에서 처리할 수 있다.

이러한 장점들로 인해 현대의 많은 웹 프레임워크들이 프론트 컨트롤러 패턴을 사용하고 있다. 특히 이번에 다룰 DispatcherServlet 은 Spring 의 프론트 컨트롤러이다.

3. 프론트 컨트롤러 구조 Overview

3.1 Front Controller

모든 클라이언트 요청의 단일 진입점으로, 요청에 대한 분석후 적절한 컨트롤러로 전달하거나 로직을 처리한다.

  • 모든 클라이언트 요청의 단일 진입점 역할
  • 요청에 대한 선처리 작업 수행
  • 요청을 분석하고 적절한 처리 흐름으로 전달

3.2 Dispatcher

프론트 컨트롤러의 요청 라우팅 기능을 담당하여, 요청을 적절한 핸들러 혹은 view 로 반환한다.

  • 요청 라우팅의 핵심 엔진
  • 다음과 같은 역할 수행:

3.3 Handler / Controller

특정 요청을 처리하는 컴포넌트로, 여기서 비즈니스 로직의 수행이 일어난다

  • 실제 비즈니스 로직을 처리하는 컴포넌트
  • 주요 책임:

3.4 View

클라이언트에 반환될 응답 데이터를 표시하는데, HTML, JSON, XML 등 다양한 형식을 지원한다.

  • 클라이언트에게 전달될 최종 응답을 생성
  • 다양한 응답 형식 지원:
  • 뷰 렌더링 로직 캡슐화

3.5 Flow

  • 요청 접수
  • 공통 처리
  • 요청 위임
  • 비즈니스 처리
  • 응답 생성

4. Spring의 DispatcherServlet

DispatcherServlet 은 Spring 의 프론트 컨트롤러로, 핵심 구성 요소중 하나이다. 즉, 클라이언트로부터 오는 모든 요청을 이 DispatcherServlet 이 수신하여, 적절한 handler 로 넘겨주는 것이다.

4.1 DispatcherServlet과 ApplicationContext

DispatcherServlet 은 WebApplicationContext 와 연동된다. 초기화 과정은 다음과 같다.

  1. Spring Boot 어플리케이션이 실행되면, 내부적으로 SpringApplication.run(...) 이 실행되는데, 이때 Spring Boot 은 여러 Auto-Configuration 을 시도한다.
  2. DispatcherServlet 이 Servlet Container 에 등록된다. 이때 / 경로에 매핑된다. (모든 HTTP 요청을 가로챈다)
  3. DispatcherServlet 이 생성되면, ApplicationContext 와 연동되는데, 이때 WebApplicationContext 를 사용해 웹 관련 bean을 찾아 활용한다. (WebApplicationContext 는 ApplicationContext를 상속받는다)
  4. 이후 모든 필요한 의존성 주입이 완료되면, DispatcherServlet 은 HTTP 요청을 처리할 준비가 된 것이다.

4.2 DispatcherServlet 요청 처리 흐름

이제 DispatcherServlet 이 초기화 된 후, 실제 사용자가 HTTP 요청을 보내면 어떻게 처리되는지에 대해 알아보자.

  1. HTTP 요청 수신 → 사용자의 요청이 들어오면, 우선 서블릿 컨테이너가 이 요청을 DispatcherServlet 에 전달한다. ( 이전 초기화 과정에서 / 경로로 매핑되었기 때문)
  2. 핸들러 매핑 → DispatcherServlet 은 내부적으로 Bean 들을 순회하며, 현재 요청을 처리할 수 있는 Handler를 찾는다.
  3. Handler Mapping 이 완료되었다면, 해당 핸들러를 실행하기 위한 적절한 Hander Adapter 를 찾는다.
  4. Handler , 즉 비즈니스 로직을 호출하여 필요한 작업을 한다
  5. 비즈니스 로직의 처리 결과를 View 로 반환한다

5. 결론

Spring MVC 어플리케이션에서 DispatcherServlet은 단순히 HTTP 요청을 분배하는 분배기가 아니다. Spring 의 Auto-Configuration 과정을 통해 등록되는 이 서블릿은 HandlerMapping, HanderAdapter, ViewResolver 등 여러 컴포넌트를 협력시키는 중요한 역할을 한다.

이러한 구조적에 Spring MVC 는 높은 유연성과 확장성을 확보할 수 있게 되는데, 앱 규모가 커지고 요구사항이 복잡해 지더라도, DispatcherServlet 을 중심으로 각 구성 요소를 쉽게 교체, 확장 혹은 대응할 수 있게 된다.