당니의 개발자 스토리

Servlet 구조와 Front Controller 패턴 본문

Java, Spring

Servlet 구조와 Front Controller 패턴

clainy 2026. 4. 20. 16:46

기존 Servlet 방식의 한계와 Front Controller 패턴

처음에 서블릿으로 웹을 만들 때는 요청 하나마다 서블릿 하나씩 만드는 구조로 시작하게 된다.
예를 들어 /login, /join, /gugu 이런 식으로 요청이 늘어날 때마다 서블릿도 계속 늘어난다.

문제는 여기서부터 발생한다.

로그 처리, 파라미터 출력, 인증 체크 같은 공통 로직이 모든 서블릿에 반복된다.
그리고 요청 흐름도 서블릿마다 제각각이라 유지보수가 점점 어려워진다.

이걸 한 번에 해결하려고 나온 게 Front Controller 패턴이다.

핵심은 하나다.

모든 요청을 하나의 서블릿으로 몰아넣고, 그 안에서 분기한다.

예를 들어 /main 하나만 만들고 모든 요청을 여기에 보내는 방식이다.

 

@WebServlet("/main")
public class MainServlet extends HttpServlet {
 

이 상태에서 요청을 이렇게 받는다.

String action = request.getParameter("action");
 

그리고 action 값에 따라 분기한다.

switch(action) {
	case "gugu" -> gugu(request, response);
	case "login" -> login(request, response);
}
 

이 구조가 가지는 장점은 명확하다!!

 

첫 번째, 요청 흐름이 하나로 모인다.
두 번째, 공통 로직을 한 곳에서 처리할 수 있다.
세 번째, 새로운 기능 추가가 쉬워진다.


여기서 중요한 포인트 하나가 있다.

/main 하나로 모든 요청을 받으면 어떻게 구분할까?

여기에는 두 가지 방식이 있다.

 

첫 번째는 파라미터 기반이다.

/main?action=gugu
/main?action=login
 
 

두 번째는 URL 패턴 기반이다.

/main/gugu
/main/login
 

파라미터 방식(action)을 사용하고 있는 구조가 편하긴 하다..

그리고 이 구조에서 자주 같이 쓰는 게 ControllerHelper 같은 인터페이스다.

default String preProcessing(HttpServletRequest request, HttpServletResponse response)
 

아무래도 인터페이스이기 때문에 default 메서드로 정의해준다.

이 메서드는 하는 일은 딱 두 개!

  1. 요청 로그 찍기
  2. action 값 기본 처리
 
if (action == null || action.isBlank()) {
	action = "index";
}

즉, Front Controller에서 매번 반복되는 작업을 한 번에 처리하려고 만든 구조다.

 


doGet / doPost

Front Controller에서는 요청을 받는 메서드도 중요하다.

보통 doGet과 doPost에서 각각 action을 분기하게 된다.

 

protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    String action = request.getParameter("action");

    switch (action) {
        case "gugu" -> gugu(request, response);
    }
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    String action = request.getParameter("action");

    switch (action) {
        case "login" -> login(request, response);
    }
}

즉, 같은 /main으로 들어오더라도
요청 방식(GET / POST)에 따라 처리 흐름이 달라질 수 있다.

 

클라이언트 요청
 /main으로 요청 전달
→ MainServlet (Front Controller)
→ action 값 추출
→ switch로 분기
→ 실제 메서드 실행 (gugu, login 등)
→ 응답 반환

 

전체흐름을 정리하면 위와 같다.

 


여기까지 보면 구조가 꽤 깔끔해 보인다...

 

하지만 이 구조에도 한계가 있다!!!!

 

예를 들어 아래와 같은 경우를 생각해보자.

- 로그인 여부 체크
- 요청 로그 기록
- 보안 처리 (XSS, CSRF)

 

이런 기능들은 특정 메서드가 아니라 모든 요청에 공통으로 적용되어야 한다.

그런데 현재 구조에서는 각 메서드 안에서 직접 처리해야 한다.

if (!isLoginUser) {
    // 로그인 페이지로 이동
}

이렇게 되면 코드가 다시 중복되기 시작한다.

즉, ControllerHelper로 어느 정도 정리는 했지만
요청 전체 흐름을 통제하기에는 부족하다.

 

이 문제를 해결하기 위해 등장하는 개념이 바로 Filter다.

 

Filter에 대해서는 다음 글에서 다루어 보겠다.