- ···
- 21.
[멀티캠퍼스] 풀스택 개발자 아카데미 (21) - Spring Framework(1) - 22.[멀티캠퍼스] 풀스택 개발자 아카데미 (22) - Spring Framework(2)현재
- 23.
[멀티캠퍼스] 풀스택 개발자 아카데미 (23) - Spring Framework(3) - ···
1. 체계적인 웹 요청 처리의 시작
지난 글에서는 스프링 프레임워크의 핵심 철학인 IoC(제어의 역전) 와 DI(의존성 주입) 에 대해 알아보았다. 컨테이너가 객체의 생성과 관계 설정을 책임짐으로써 개발자는 비즈니스 로직에 더욱 집중할 수 있게 되고, 코드 간의 결합도가 낮아져 유연하고 확장 가능한 애플리케이션 개발이 가능해진다는 것을 확인했다.
이제 스프링이 제공하는 강력한 기능 중 웹 계층을 담당하는 Spring MVC 프레임워크를 본격적으로 살펴볼 차례이다. 서블릿/JSP 기반의 MVC Model 2 구조를 더욱 체계적이고 효율적으로 구현할 수 있게 도와주는 Spring MVC의 중심에는 바로 DispatcherServlet이 있다.
이번 글에서는 Spring MVC의 중심이 되는 DispatcherServlet이 무엇이며, 클라이언트의 요청을 받아 응답을 보내기까지 어떤 과정을 거치는지 그 동작 원리를 상세히 파헤쳐 본다. 또한, 이 과정을 설정하는 핵심 파일인 web.xml과 servlet-context.xml의 역할과 주요 설정 내용을 알아보고, @Controller와 @RequestMapping 어노테이션을 이용한 기본적인 요청 처리 방법을 다룬다.
마지막으로, 웹 개발 시 자주 마주치는 POST 방식 한글 깨짐 문제를 해결하기 위한 WebFilter 활용법까지 다뤄본다.
2. Spring MVC 아키텍처
Spring MVC는 서블릿 기반의 MVC Model 2 패턴을 구현한 프레임워크이다. 요청 처리 과정을 여러 컴포넌트(구성 요소)가 역할을 분담하여 처리하는 잘 짜인 구조를 가지고 있다. 마치 각 공정을 담당하는 기계들이 협력하여 제품을 만들어내는 공장과 같다.
주요 컴포넌트와 그 역할은 다음과 같다.
DispatcherServlet: 모든 클라이언트 요청을 가장 먼저 받는 프론트 컨트롤러(Front Controller) 이다. 요청을 분석하여 실제 처리를 담당할 컨트롤러에게 작업을 분배하고, 최종 결과를 받아 뷰(View)에게 전달하는 등 전체 흐름을 제어하는 핵심 엔진이다.HandlerMapping: 요청 URL, HTTP 메소드 등의 정보를 바탕으로, 해당 요청을 처리할 핸들러(Handler), 즉 컨트롤러(의 메소드)를 찾아주는 역할을 한다. 다양한 종류의HandlerMapping구현체가 존재한다.Controller:HandlerMapping에 의해 선택되어 실제 요청 처리 로직(비즈니스 로직 호출, 데이터 가공 등)을 수행한다. 처리 결과를 담은 모델(Model) 데이터와 다음에 보여줄 뷰(View)의 논리적인 이름(Logical View Name)을DispatcherServlet에게 반환한다. 스프링에서는@Controller어노테이션이 붙은 클래스가 이 역할을 담당하며, 일반적으로 요청을 처리하는 특정 메소드를 핸들러(Handler) 라고 부른다.ModelAndView: 컨트롤러가 처리한 결과 데이터(Model) 와 그 결과를 보여줄 뷰(View) 이름을 함께 담아DispatcherServlet에게 반환하는 객체이다. (최근에는String으로 뷰 이름만 반환하고, 모델 데이터는Model객체에 담는 방식도 많이 사용된다.)ViewResolver: 컨트롤러로부터 받은 논리적인 뷰 이름을 실제 뷰 리소스(예:/WEB-INF/views/home.jsp)로 해석(Resolve) 해주는 역할을 한다. 컨트롤러는 구체적인 뷰 기술(JSP 등)에 상관없이 논리적인 이름만 반환하면 되므로 유연성이 높아진다.View:ViewResolver에 의해 선택되어 최종 응답 화면을 생성하는 역할을 한다. 컨트롤러로부터 전달받은 모델 데이터를 사용하여 사용자에게 보여줄 HTML 등을 생성한다. JSP, JSON 등 다양한 형태가 될 수 있다.
3. DispatcherServlet의 역할
DispatcherServlet은 Spring MVC 애플리케이션의 단일 진입점(Single Entry Point) 역할을 수행한다. 웹 브라우저로부터 들어오는 모든 HTTP 요청은 가장 먼저 DispatcherServlet을 통과하게 된다. 마치 공항의 관제탑처럼, 들어오는 모든 비행기(요청)를 적절한 게이트(컨트롤러)로 안내하고 이륙(응답)까지 관리하는 중앙 통제 시스템인 셈이다.
DispatcherServlet은 단순히 요청을 전달만 하는 것이 아니라, 요청 처리 과정 전반에 걸쳐 필요한 컴포넌트들과 상호작용하며 전체 흐름을 조율한다.
4. Request Lifecycle
클라이언트가 특정 URL로 요청을 보냈을 때, Spring MVC 내부에서는 다음과 같은 과정을 거쳐 응답이 생성된다.
- 요청 접수: 클라이언트의 HTTP 요청이 웹 서버를 통해
DispatcherServlet에 도착한다. - 핸들러 조회: DispatcherServlet은 등록된 HandlerMapping들에게 요청 정보(URL, HTTP 메소드 등)를 전달하여 이 요청을 처리할 핸들러(일반적으로
@RequestMapping이 붙은 컨트롤러의 메소드)를 찾아달라고 요청한다.
- 핸들러 실행:
DispatcherServlet은 찾은 핸들러를 실행하기 위해 적절한HandlerAdapter에게 요청을 위임한다. (HandlerAdapter는 다양한 형태의 핸들러를DispatcherServlet이 일관된 방식으로 호출할 수 있도록 중간에서 변환해주는 역할을 한다.) - 컨트롤러 로직 수행: 해당 컨트롤러 메소드가 실행된다. 이 과정에서 필요하다면 Service 객체를 호출하여 비즈니스 로직을 수행하고, DB와 상호작용(Model)하여 데이터를 조회하거나 가공한다.
ModelAndView반환: 컨트롤러는 처리 결과 데이터(Model)와 뷰의 논리적인 이름(View Name)을 담아ModelAndView객체 (또는String뷰 이름과Model객체)를DispatcherServlet에게 반환한다.- 뷰 리졸빙: DispatcherServlet은 반환된 논리적인 뷰 이름을 ViewResolver에게 전달하여 실제 사용할 View 객체로 변환하도록 요청한다. ViewResolver는 설정된 규칙(prefix, suffix 등)에 따라 JSP 파일 경로 등을 찾아 View 객체를 생성하여 반환한다.
- 뷰 렌더링: DispatcherServlet은 View 객체에게 모델 데이터를 전달하며 렌더링(응답 화면 생성)을 요청한다.
- 응답 전송:
View객체는 모델 데이터를 사용하여 HTML 등 최종 응답을 생성하고,DispatcherServlet은 이 응답을 클라이언트에게 전송한다.
이러한 명확한 단계별 처리를 통해 Spring MVC는 유연하고 확장 가능한 웹 애플리케이션 구조를 제공한다.
5. web.xml과 servlet-context.xml
DispatcherServlet과 여러 컴포넌트들이 제대로 동작하려면 스프링 컨테이너에게 필요한 설정 정보를 알려줘야 한다. 전통적인 XML 기반 설정에서는 주로 두 개의 파일이 사용된다.
5.1. 웹 애플리케이션의 시작점 web.xml
web.xml 파일은 Java Servlet 표준 명세에 따른 웹 애플리케이션의 **배포 서술자(Deployment Descriptor)**이다. 톰캣과 같은 WAS(웹 컨테이너)가 웹 애플리케이션을 시작할 때 가장 먼저 참조하는 설정 파일이다. Spring MVC에서는 주로 다음과 같은 역할을 한다.
DispatcherServlet등록 및 URL 매핑: 모든 요청(url-pattern을/로 설정)을DispatcherServlet이 받도록 서블릿을 등록하고 매핑한다.- Spring ApplicationContext 로드:
ContextLoaderListener를 사용하여DispatcherServlet외의 다른 빈(Bean)들(주로 Service, Repository 등 비즈니스 로직 관련)을 관리하는 루트(Root) 애플리케이션 컨텍스트 설정 파일(root-context.xml)의 위치를 지정한다. (하지만 간단한 설정에서는servlet-context.xml에서 모든 빈을 관리하기도 한다.) - Filter 등록: 문자 인코딩 필터(
CharacterEncodingFilter) 등 애플리케이션 전반에 적용될 필터를 등록한다.
web.xml 예시 (Jakarta EE 6.0 기준)
Root vs Servlet 컨텍스트 > ContextLoaderListener가 로드하는 root-context.xml은 루트(Root) 애플리케이션 컨텍스트를 생성한다.
이는 애플리케이션 전반에서 공통으로 사용될 Bean을 정의하는 데 사용된다.
반면, DispatcherServlet이 로드하는 servlet-context.xml은
서블릿 애플리케이션 컨텍스트를 생성하며, 웹 계층과 관련된 Bean을 정의한다.
이 둘은 부모-자식관계를 형성하며, 자식 컨텍스트(Servlet)는 부모 컨텍스트(Root)를 참조할 수 있으나, 그 반대는 불가능하다. 이러한 계층 구조를 통해 비즈니스 로직과 웹 로직을 명확히 분리할 수 있다.
5.2. 웹 계층 Bean들의 집합소 servlet-context.xml
servlet-context.xml 파일은 DispatcherServlet이 직접 사용하는 웹 애플리케이션 컨텍스트 설정 파일이다. web.xml의 <init-param>을 통해 위치가 지정된다. 여기에는 주로 웹 계층과 관련된 빈(Bean)들이 정의된다.
HandlerMapping,ViewResolver빈 등록: 어떤HandlerMapping과ViewResolver구현체를 사용할지 정의한다. (예:InternalResourceViewResolver16)<context:component-scan>:@Controller,@Service,@Repository등의 어노테이션이 붙은 클래스를 자동으로 스캔하여 빈으로 등록하도록 지시한다.<mvc:annotation-driven />:@RequestMapping과 같은 어노테이션 기반의 컨트롤러 작성을 가능하게 하고, 여러 웹 관련 기능(메시지 컨버터 등)을 활성화하는 중요한 설정이다.- 정적 자원(Resource) 매핑: CSS, JavaScript, 이미지 파일 등 정적 자원에 대한 요청을
DispatcherServlet이 처리하지 않고 직접 접근할 수 있도록 경로를 설정한다.
servlet-context.xml 예시:
6. Controller와 RequestMapping
Spring MVC에서는 POJO 클래스에 간단한 어노테이션을 붙이는 것만으로 웹 요청을 처리하는 컨트롤러를 만들 수 있다.
@Controller: 클래스 레벨에 붙여 해당 클래스가 Spring MVC의 컨트롤러임을 나타낸다.@Component의 특수화된 형태로, 스프링 컨테이너는 이 어노테이션이 붙은 클래스를 스캔하여 빈으로 등록한다 (<context:component-scan>설정 필요).@RequestMapping: 메소드 레벨 또는 클래스 레벨에 붙여 특정 URL 요청(및 HTTP 메소드)을 어떤 컨트롤러 또는 메소드가 처리할지 매핑(Mapping) 하는 역할을 한다.value또는path속성: 매핑할 URL 경로 지정 (예:"/members","/members/{id}")method속성: 허용할 HTTP 메소드 지정 (예:RequestMethod.GET,RequestMethod.POST)
간단한 컨트롤러 예제:
이제 /WEB-INF/views/ 디렉터리에 home.jsp와 hello.jsp를 만들면 해당 URL 요청 시 각각의 JSP 페이지가 렌더링된다.
7. 한글 깨짐 방지 필터
HTML 폼에서 POST 방식으로 데이터를 전송할 때, 별도 설정을 하지 않으면 한글 데이터가 깨져서 서버로 전달되는 경우가 많다. 이는 요청 데이터의 인코딩 방식이 서버에서 기대하는 방식(주로 UTF-8)과 다르기 때문이다.
Spring에서는 이를 간편하게 해결할 수 있는 CharacterEncodingFilter를 제공한다. 이 필터를 web.xml에 등록하면 모든 요청에 대해 지정된 인코딩(UTF-8)을 강제로 적용하여 한글 깨짐 문제를 해결할 수 있다.
web.xml에 필터 등록 (Jakarta EE 기준):
이렇게 설정하고 WAS를 재시작하면, 이후 POST 방식으로 전송되는 한글 데이터도 컨트롤러에서 정상적으로 수신할 수 있게 된다.
8. 결론 및 평가
이번 편에서는 Spring MVC의 핵심 엔진인 DispatcherServlet의 역할과 그 주변 컴포넌트(HandlerMapping, Controller, ViewResolver 등)와의 상호작용을 통해 요청이 처리되는 전체 흐름을 살펴보았다. 또한, web.xml과 servlet-context.xml을 통한 설정 방법과 @Controller, @RequestMapping을 이용한 기본적인 요청 처리 방법을 익혔다. 마지막으로 CharacterEncodingFilter를 이용해 실무에서 자주 발생하는 한글 인코딩 문제를 해결하는 방법까지 다뤄보았다.
DispatcherServlet 중심의 중앙 집중식 요청 처리 방식은 서블릿/JSP Model 2를 직접 구현할 때보다 훨씬 구조적이고 편리하며, 각 컴포넌트의 역할을 명확히 분리하여 유연성과 확장성을 높여준다. XML 기반 설정은 스프링의 동작 방식을 이해하는 데 도움이 되지만, 최근에는 어노테이션과 Java Config를 이용한 설정이 더 많이 사용되는 추세이다(이는 Spring Boot에서 더욱 두드러진다).
Spring MVC의 요청 처리 흐름을 이해하는 것은 웹 애플리케이션 개발의 기본기를 다지는 중요한 과정이다. 다음 글에서는 Spring 환경에서 데이터베이스 연동을 더욱 편리하게 해주는 MyBatis 프레임워크를 Spring MVC와 통합하는 방법에 대해 다뤄볼 예정이다.