본문 바로가기
Spring/MVC

[Spring MVC] 프론트 컨트롤러 패턴 도입부터 어댑터(V5), 로깅, 요청 매핑까지 완벽 정리

by coding_whale 2026. 5. 22.
반응형

1. 도입부

웹 애플리케이션 아키텍처가 발전함에 따라 서블릿과 JSP의 역할 분담을 넘어, 수많은 컨트롤러의 공통 관심사를 어떻게 효율적으로 처리할 것인가에 대한 고민이 깊어졌다. 과거 서블릿 기반 mvc 패턴에서는 클라이언트의 요청마다 전용 서블릿을 매핑해 사용했기 때문에 공통 로직 중복과 서블릿 종속성이라는 한계가 존재했다. 이를 해결하기 위해 등장한 혁신적인 디자인 패턴이 바로 프론트 컨트롤러(Front Controller) 패턴이다.

오늘날 스프링 MVC의 심장부인 DispatcherServlet이 바로 이 프론트 컨트롤러 패턴의 완성형이다. 서블릿 아키텍처가 어떤 구조적 문제를 거쳐 현대적인 스프링 MVC로 진화했는지 단계별로 파헤쳐본다.

 

[Spring] 역할 분담을 위한 MVC 패턴의 구조와 설계

1. 도입부 (Introduction)초기 웹 애플리케이션 개발 패러다임은 동적인 HTML 페이지를 빠르게 생성하는 것에 초점이 맞춰져 있었다. 자바 진영에서는 이를 위해 서블릿(Servlet)과 JSP(Java Server Pages)라는

myblog01150.tistory.com

 

 

2. 프론트 컨트롤러 패턴 소개

프론트 컨트롤러 도입 전 vs 도입 후

프론트 컨트롤러 도입 전 클라이언트가 각각의 기능(회원 가입, 회원 저장, 목록 조회 등)을 요청할 때마다 WAS는 개별 서블릿을 직접 호출한다. 이 구조는 모든 서블릿마다 공통 처리 로직(로깅, 인증, 에러 핸들링 등)을 중복해서 작성해야 하므로 코드의 수정과 유지보수가 극도로 어려워진다.

  • 프론트 컨트롤러 도입 후 클라이언트의 모든 요청을 전면부에 위치한 단 하나의 서블릿(Front Controller)이 대표로 받는다. 프론트 컨트롤러는 클라이언트가 요청한 URL을 분석하여 알맞은 컨트롤러를 찾아서 호출해 주는 매핑 및 위임 역할을 수행한다.

 

FrontController 패턴 특징

  • 단일 창구 역할: 프론트 컨트롤러 서블릿 하나로 클라이언트의 모든 요청을 일괄 접수한다.
  • 라우팅 기능: 프론트 컨트롤러가 요청 주소와 매핑 정보를 대조해 알맞은 컨트롤러를 호출한다.
  • 공통 처리 가능: 입구인 프론트 컨트롤러 내부에서 로깅, 인증, 다국어 처리 등 공통 비즈니스를 선행 수행할 수 있다.
  • 서블릿 종속성 제거: 프론트 컨트롤러를 제외한 나머지 하위 컨트롤러들은 HttpServlet의 상속이나 서블릿 API 인터페이스를 직접 사용하지 않아도 웹 기능을 구현할 수 있게 된다.

 

3. 핸들러 어댑터 (Handler Adapter) 구조

  • 도입 전 한계 (OCP 위반): 프론트 컨트롤러는 단 한 가지 종류의 컨트롤러 인터페이스만 직접 처리할 수 있다. 만약 다른 형태의 인터페이)를 수용하려 하면 프론트 컨트롤러 본연의 핵심 코드를 전면 수정해야 하므로 객체 지향 설계 원칙 중 하나인 개방-폐쇄 원칙(OCP)을 심각하게 위반한다.
  • 해결책 (유연한 중재자): 프론트 컨트롤러와 실제 컨트롤러 사이에 전압 변환기 역할을 하는 어댑터(Adapter) 추상 층을 삽입한다. 이 시점부터는 하나의 인터페이스 제약에서 완전히 벗어나므로 컨트롤러라는 명칭 대신 더 포괄적인 의미를 지닌 '핸들러(Handler)'로 명칭이 확장된다.
  • 동작 원리 핵심 2단계:
    1. 지원 여부 확인 (supports): 프론트 컨트롤러가 타겟 핸들러 객체를 전달하면, 어댑터는 instanceof 연산 등을 통해 자신이 이 핸들러를 가공하고 처리할 자격이 있는지 검증하여 응답한다.
    2. 응답 규격 정규화 (handle): 어댑터가 제어권을 위임받아 실제 핸들러를 해석하고 구동한다. 이때 실제 핸들러가 가공되지 않은 String이나 다른 커스텀 포맷을 리턴하더라도, 어댑터 내에서 프론트 컨트롤러가 최종 일괄 처리할 수 있는 표준 규격인 ModelView 객체로 포장 및 변환하여 리턴해 준다.
 

4. 스프링 MVC 인프라로의 확장 (로깅 및 매핑 전략)

프론트 컨트롤러와 어댑터 구조가 완전히 이해되었다면, 이제 스프링 MVC가 기본 제공하는 고급 인프라 영역으로 확장하여 실무 경쟁력을 갖춰야 한다.

로깅(Logging)의 기본 원리와 장점

실무에서는 운영 상태를 모니터링하기 위해 System.out.println() 대신 별도의 로깅 라이브러리(SLF4J, Logback)를 사용하여 이벤트를 수집한다.

선언 및 수준(Level) 설정

  • 로그 선언 방식: private final Logger log = LoggerFactory.getLogger(getClass());
  • 롬복 플러그인을 활용하면 클래스 상단에 @Slf4j 애노테이션 하나로 자동 주입할 수 있다.
  • 레벨 강도 순서: TRACE > DEBUG > INFO > WARN > ERROR
    • 개발 서버 환경: 보통 세밀한 디버깅을 위해 DEBUG 수준까지 화면에 노출한다.
    • 운영 서버 환경: 불필요한 디스크 I/O 리소스 낭비를 막고 주요 비즈니스 흐름만 트래킹하기 위해 INFO 혹은 그 이상으로 설정 범위를 제어한다.

 

올바른 로그 사용법 및 최적화 함정

로그를 호출할 때는 내부 문자열 더하기 연산이 실행 단계 이전에 선행되지 않도록 파라미터 바인딩 구조 형식을 엄격히 준수해야 한다.

  • 잘못된 사용 방식: log.debug("String concat log=" + name);
    • 이유: 로그 레벨이 INFO 상황이라 출력되지 않는 상황임에도 자바 문법 특성상 자원 더하기 연산(+)이 무조건 먼저 일어나 메모리 점유가 발생한다. 성능 저하의 원인이 된다.
  • 올바른 사용 방식: log.debug("data={}", data);
    • 이유: 메서드 파라미터 형태로 넘기므로 내부 조건 검사를 통해 로그가 비활성화 상태이면 아무런 오버헤드 연산이 발생하지 않는다.

 

로그의 궁극적인 장점

  • 쓰레드 정보, 발생 클래스 명칭 등 부가 텍스트 메타데이터를 통합 관찰할 수 있다.
  • 파일 디스크 분할 분기 정책(일별 Rolling, 용량별 분할), 네트워크 중앙 수집 서버 전송이 가능하다.
  • 멀티 스레드 및 커널 내부 버퍼링 구조 설계로 인해 System.out 성능보다 압도적으로 빠르다.

 

Spring MVC의 요청 매핑 기술 요약

@Controller vs @RestController의 메커니즘

  • @Controller: 리턴하는 문자열을 화면의 뷰 이름(View Name)으로 전제하여 인식한다. 결과적으로 뷰 리졸버가 구동되어 HTML 페이지가 렌더링된다.
  • @RestController: 반환하는 순수 데이터값 그대로 HTTP 응답 메시지 바디(Body) 영역에 직접 text/json 형태로 즉시 매핑 입력을 집행한다. API 통신 시 사용된다.

주요 매핑 애노테이션

@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {

    // 1. HTTP 메서드 축약 및 경로 변수 다중 처리
    @GetMapping("/{userId}/orders/{orderId}")
    public String findUserOrder(@PathVariable String userId, @PathVariable Long orderId) {
        // 최근 HTTP API에서 자원의 식별자를 리소스 경로에 넣는 트렌디한 방식 선호
        return "get userId=" + userId + ", orderId=" + orderId;
    }

    // 2. 특정 파라미터 조건부 매핑 (파라미터 조건 일치 시에만 동작)
    @GetMapping(value = "/mapping-param", params = "mode=debug")
    public String mappingParam() {
        return "ok";
    }

    // 3. 특정 헤더 조건부 매핑 (HTTP 요청 헤더에 지정 값이 있어야 동작)
    @GetMapping(value = "/mapping-header", headers = "mode=debug")
    public String mappingHeader() {
        return "ok";
    }

    // 4. 미디어 타입 조건 매핑 - Content-Type 검증 (소비자 관점 consumes)
    @PostMapping(value = "/mapping-consume", consumes = "application/json")
    public String mappingConsumes() {
        return "ok";
    }
}

 

 

5. 실무 팁 및 주의사항 (Tips)

  • 404 Not Found의 원인: 프론트 컨트롤러에서 빈번히 마주치는 예외 중 하나는 핸들러 매핑 테이블(handlerMappingMap)에 대상 URI를 등록하지 않았거나 어댑터 목록 조회가 실패할 때 발생한다. 요청 URI 체계와 서블릿 컨텍스트 루트가 일치하는지 항상 디버깅 지점으로 먼저 점검하자.
  • POJO 기반 설계의 의의: 컨트롤러 내에서 서블릿 API인 request, response 객체를 걷어내는 설계 흐름은 모킹(Mocking) 없이 순수한 비즈니스 로직 단위 테스트 코드를 짤 수 있는 발판이 된다. 프레임워크가 바뀔지라도 비즈니스 코드의 순수성은 훼손되지 않도록 격리하는 연습이 좋은 아키텍처를 만든다.

 

 

6. 마무리 (Conclusion)

스프링 MVC의 DispatcherServlet 내부에는 수십 년 동안 백엔드 거장들이 고민한 다형성과 OCP(개방-폐쇄 원칙) 설계 철학이 집약되어 있다. 내부 동작 원리를 소스 수준에서 추적하는 일은 라이브러리를 단순히 사용하는 차원을 넘어, 복잡한 비즈니스 엔터프라이즈 환경에서 유연하고 튼튼한 프레임워크급 설계를 직접 구성할 수 있는 핵심적인 인프라 역량으로 이어질 것이다.

반응형