본문 바로가기

Java/Spring

Spring 5 입문: Chapter 10. 스프링 MVC 프레임워크 동작 방식

더보기

이 프로젝트의 개발 환경

  • 개발 언어 및 개발 환경
    • OpenJDK 12
    • Spring: spring-webmvc 5.0.2.RELEASE
    • Tomcat: tomcat-jdbc: 8.5.27
    • Gradle 7.3
  • 기타 환경
    • macOS Sonoma 14.1.1
    • IntelliJ IDEA 2020.3 Ultimate Edition

스프링 MVC 핵심 구성 요소

스프링 MVC 구현은 DispatcherServlet을 구성하고 컨트롤러와 JSP를 작성하는 과정입니다.

단계 비고
1 HTTP 요청 DispatcherServlet은 요청을 처리하기 위한 컨트롤러를 검색합니다.
2 요청 URL과 매핑되는 컨트롤러 검색 이때 컨트롤러를 검색하는 주체는 HandlerMapping입니다.
3 컨트롤러 어댑터에 처리 요청 요청 경로와 매핑되는 컨트롤러가 존재하면 HandlerAdapter를 호출합니다.
4 컨트롤러 실행 HandlerAdapter는 DispatcherServlet 대신 여러 유형의 컨트롤러를 실행합니다.
5 컨트롤러 결과 리턴 HandlerAdapter는 요청 경로와 매핑되는 컨트롤러의 메소드를 실행하고 결과를 리턴 받습니다.
6 결과를 ModelAndView로 변환 후 리턴 컨트롤러의 실행 결과는 HandlerAdapter에 의해서 ModelAndView로 변환되어 리턴됩니다.
7 뷰 검색 ViewResolver는 뷰 이름을 사용해 View 객체를 검색합니다.
8 응답 생성 요청 DispatcherServlet은 View 객체에게 응답 결과 생성을 요청합니다.
9 응답 생성 View 객체는 일치하는 JSP를 실행합니다.

컨트롤러와 핸들러

클라이언트의 요청을 실제로 처리하는 것은 컨트롤러(Controller)입니다.

  • DispatcherServlet은 단순히 요청을 처리할 컨트롤러를 찾기 위해서 HandlerMappping을 사용합니다.
  • 이때 컨트롤러를 찾아주는 객체의 이름은 ControllerMapping이 아닌 HandlerMapping입니다.
  • 스프링 MVC는 범용 프레임워크로써 다양한 유형의 클래스를 사용하여 웹 요청을 처리합니다.
    • @Controller 어노테이션을 등록한 컨트롤러
    • Controller 인터페이스를 구현하는 컨트롤러
    • HttpRequestHandler 인터페이스를 구현하는 클래스 등
  • 따라서 DispatcherServlet이 웹 요청 처리하는 과정에서 호출하는 객체를 핸들러(Handler)라고 표현하고 이를 찾는 객체를 HandlerMapping이라고 부릅니다.
  • DispatcherServlet은 단순히 ModelAndView 객체를 리턴 받는 것에만 신경씁니다.
  • 실제 구현 타입에서 필요로 하는 동작은 HandlerMapping과 HandlerAdapter가 대신하기 때문에 가능합니다.
  • 핸들러 객체의 구현 타입과 일치하는 HandlerMapping 및 HandlerAdapter가 스프링 빈으로 등록되어야 합니다.
  • 스프링이 제공하는 설정 기능을 따르면 이 두 가지 빈을 직접 등록하지 않아도 됩니다.

DispatcherServlet과 스프링 컨테이너

web.xml 파일에서는 스프링 설정 클래스 목록을 전달합니다.

<init-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		chap09.config.MvcConfig
		chap09.config.ControllerConfig
	</param-value>
</init-param>
코드 비고
Line 2 <param-name>contextConfigLocation contextConfiguration 초기화 파라미터를 사용해 스프링 설정 클래스를 전달합니다.
Line 4:5 chap09.config.MvcConfig 스프링 설정 클래스 목록을 FQCN(Full Qualified Class Name)으로 지정합니다.
chap09.config.ControllerConfig

DispatcherServlet은 지정된 설정 클래스 목록을 사용해서 스프링 컨테이너를 생성합니다.

이때 생성되는 컨테이너에서 HandlerMapping HandlerAdapter 컨트롤러 ViewResolver 등의 빈을 구하므로 DispatcherServlet에서 이들에 대한 정의가 포함되어야 합니다.

더보기

DispatcherServlet은 스프링 컨테이너를 생성하고, 그 컨테이너로부터 필요한 빈 객체를 구합니다.

@Controller를 위한 HandlerMapping과 HandlerAdapter

DipatcherServlet은 웹 브라우저의 요청을 처리할 핸들러 객체를 찾기 위해 HandlerMapping을 사용합니다.

그리고 컨트롤러-핸들러를 실행하기 위해서 HandlerAdapter를 사용합니다.

스프링 컨테이너에서는 핸들러에 알맞는 HandlerMapping 빈과 HandlerAdapter 빈이 스프링 설정에 등록되어 있어야합니다.

이를 간략하게 처리하기 위해서 @EnableWebMvc를 사용합니다.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer
{

}

@EnableWebMvc를 사용하면 @Controller 타입의 핸들러 객체를 처리하기 위한 클래스도 포함됩니다.

org.springframework.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

RequestMappingHandlerMapping@Controller 어노테이션이 등록된 컨트롤러 객체의 @GetMapping을 사용해 웹 브라우저의 요청을 처리하기 위한 컨틀롤러 빈을 찾습니다.

RequestMappingHandlerAdapter는 컨트롤러의 메소드를 실행하고 결과를 ModelAndView 객체로 변환하여 DispatcherServlet에 리턴합니다.

@Controller
public class HelloController
{
	@GetMapping("/hello")
	public String hello(Model model,
	                    @RequestParam(value="name", required=false) String name)
	{
		model.addAttribute("greeting", "안녕하세요, " + name);
		return "hello";
	}
}

이때 리턴하는 ModelAndView는 메소드의 인자로 전달된 Model 객체를 포함합니다.

예시의 코드에서 메소드가 리턴하는 문자열 hello는 뷰 이름으로 사용됩니다.

WebMvcConfigurer 인터페이스와 설정

@EnableWebMvc 어노테이션을 사용하면 @Controller 어노테이션을 등록한 컨트롤러를 위한 설정을 생성합니다.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer
{
	@Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
	{
		configurer.enable();
	}
	
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry)
	{
		registry.jsp("/WEB-INF/view/", ".jsp");
	}
}

@Configuration 어노테이션을 등록한 클래스 역시 빈으로 등록되므로 MvcConfig 크래스는 WebMvcConfig 타입의 빈이 됩니다.

@EnableWebMvc는 WebMvcConfigurer 타입의 빈 객체의 메소드를 사용해 MVC 설정을 추가합니다.

예를 들어 ViewResolver 설정을 추가하기 위해서 configureViewResolver() 메소드를 호출합니다. 이와 관련해서는 메소드를 오버라이드하여 애플리케이션에 맞는 설정을 지정합니다.

JSP를 위한 ViewResolver

컨트롤러에서 비즈니스 로직을 수행하고 결과 값은 Model 객체에 입력됩니다.

@Controller
public class HelloController
{
	@GetMapping("/hello")
	public String hello(Model model,
	                    @RequestParam(value="name", required=false) String name)
	{
		model.addAttribute("greeting", "안녕하세요, " + name);
		return "hello";
	}
}
코드 비고
Line 8 model.addAttribute() Model 객체에 결과 값을 저장합니다.
Line 9 return "hello" 컨트롤러가 HandlerAdapter에 응답하는 뷰 이름입니다.

컨트롤러가 리턴한 뷰 이름에 따라서 ViewResolver는 뷰 객체를 찾습니다.

뷰 객체는 Model 객체를 포함하는데 JSP에서 Model 객체에 접근하여 뷰를 완성합니다.

인사말: ${greeting}

디폴트 핸들러와 HandlerMapping의 우선 순위

예시의 web.xml에서는 DispatchServlet에 대한 매핑 경로를 /으로 지정하고 있습니다.

<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

매핑 경로가 /인 경우 .jsp로 끝나는 요청을 제외한 모든 요청을 DispatcherServlet이 처리하게 됩니다.

/index.html이나 /css/bootstrap.css와 같은 모든 확장자가 처리 될 수 있습니다.

더보기

실제로는 @EnableWebMvc가 등록하는 HandlerMapping에서는 @Controller가 등록된 빈 객체가 처리 가능한 요청 경로만 대응합니다.

예를 들어 빈으로 등록된 컨트롤러가 @GetMapping("/hello") 하나 뿐이라면 /hello 경로의 요청만 처리 할 수 있습니다.

그 외의 요청은 404 Not Found가 발생하게 됩니다.

만약 /index.html이나 /css/bootstrap.css 같은 리소스 경로를 취급하려면 컨트롤러 객체를 직접 구현하는 것보다는 configureDefaultServletHandling()을 구현하는 것이 간편합니다.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer
{
	@Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
	{
		configurer.enable();
	}
}

위 설정에서 configurer.enable()은 다음 두 개의 빈 객체를 스프링에 추가합니다.

  • DefaultServletHttpRequestHandler
  • SimpleUrlHandlerMapping

DefaultServletHttpRequestHandler는 클라이언트의 모든 요청을 WAS가 제공하는 디폴트 서블릿에 전달합니다.

예를 들어 /index.html에 대한 처리를 DefaultServletHttpRequestHandler에 요청하면 이 요청을 다시 디플트 서블릿에 전달해서 처리하도록 합니다.

그리고 SimpleUrlHandlerMapping을 이용해서 모든 경로 /**를 DefaultServletHttpRequestHandler에 요청하도록 합니다.

더보기

@EnableWebMvc의 RequestMappingHandlerMapping의 적용 우선 순위가 SimpleUrlHandlerMapping보다 높습니다.

정리 및 복습

  • 스프링 MVC에서 웹 브라우저의 HTTP 요청은 DispatcherServlet이 가장 먼저 처리합니다.
  • DispatcherServlet은 연관된 다른 요소를 사용하여 요청을 처리합니다.
  • HandlerMapping은 요청 경로와 매핑되는 컨트롤러를 검색합니다.
  • HandlerAdapter는 DispatcherServlet을 대신해 컨트롤러를 실행합니다.
  • 컨트롤러는 Model 객체를 사용해 결과를 저장하고 View 이름을 리턴합니다.
  • HandlerAdapter는 컨트롤러의 실행 결과를 ModelAndView로 변환하여 DispatcherServlet에 리턴합니다.
  • ViewResolver는 View 이름을 사용하여 View 객체를 검색합니다.
  • DispatcherServlet은 View 객체에 응답 결과 생성을 요청합니다.
  • View 객체는 일치하는 JSP를 실행하여 응답합니다.