예제 프로젝트의 로딩 구조
프로젝트 구동 시 관여하는 xml은 web, root, servlet 파일이다.
이 중에 web.xml이 tomcat 구동과 관련된 설정이고, 나머지 두 파일은 스프링과 관련된 설정이다.
프로젝트 구동은 web xml에서 시작한다.
web.xml 상단에는 가장 먼저 구동되는 Context Listener가 등록되어있다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>에는 root-context.xml의 경로가 설정되어 있고 <listener>에는 스프링 MVC의 Context Listener가 등록 되어 있는 것을 볼 수 있다.
Context Loader Listener는 해당 웹 애플리케이션 구동 시 같이 동작하므로 해당 프로젝트를 실행하면 다음과 같이 가장 먼저 로그를 출력하면서 기록하는 것을 볼 수 있다.
INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started
INFO : org.springframework.web.context.support.XmlWebApplicationContext - Refreshing Root WebApplicationContext: startup date [Mon Aug 24 11:14:18 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/root-context.xml]
INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 463 ms
8월 24, 2020 11:14:19 오전 org.apache.catalina.core.ApplicationContext log
정보: Initializing Spring FrameworkServlet 'appServlet'
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'appServlet': initialization started
INFO : org.springframework.web.context.support.XmlWebApplicationContext - Refreshing WebApplicationContext for namespace 'appServlet-servlet': startup date [Mon Aug 24 11:14:19 KST 2020]; parent: Root WebApplicationContext
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/appServlet/servlet-context.xml]
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/],methods=[GET]}" onto public java.lang.String org.zerock.controller.HomeController.home(java.util.Locale,org.springframework.ui.Model)
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: WebApplicationContext for namespace 'appServlet-servlet': startup date [Mon Aug 24 11:14:19 KST 2020]; parent: Root WebApplicationContext
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: WebApplicationContext for namespace 'appServlet-servlet': startup date [Mon Aug 24 11:14:19 KST 2020]; parent: Root WebApplicationContext
INFO : org.springframework.web.servlet.handler.SimpleUrlHandlerMapping - Mapped URL path [/resources/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'appServlet': initialization completed in 729 ms
8월 24, 2020 11:14:20 오전 org.apache.jasper.servlet.TldScanner scanJars
정보: 적어도 하나의 JAR가 TLD들을 찾기 위해 스캔되었으나 아무 것도 찾지 못했습니다. 스캔했으나 TLD가 없는 JAR들의 전체 목록을 보시려면, 로그 레벨을 디버그 레벨로 설정하십시오. 스캔 과정에서 불필요한 JAR들을 건너뛰면, 시스템 시작 시간과 JSP 컴파일 시간을 단축시킬 수 있습니다.
8월 24, 2020 11:14:20 오전 org.apache.catalina.core.ApplicationContext log
정보: 1 Spring WebApplicationInitializers detected on classpath
8월 24, 2020 11:14:20 오전 org.apache.catalina.core.ApplicationContext log
정보: Initializing Spring root WebApplicationContext
INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Refreshing Root WebApplicationContext: startup date [Mon Aug 24 11:14:20 KST 2020]; root of context hierarchy
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Registering annotated classes: [class org.zerock.config.RootConfig]
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 387 ms
8월 24, 2020 11:14:21 오전 org.apache.catalina.core.ApplicationContext log
정보: Initializing Spring FrameworkServlet 'dispatcher'
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcher': initialization started
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Aug 24 11:14:21 KST 2020]; parent: Root WebApplicationContext
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Registering annotated classes: [class org.zerock.config.ServletConfig]
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/],methods=[GET]}" onto public java.lang.String org.zerock.controller.HomeController.home(java.util.Locale,org.springframework.ui.Model)
INFO : org.springframework.web.servlet.handler.SimpleUrlHandlerMapping - Mapped URL path [/resources/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Aug 24 11:14:21 KST 2020]; parent: Root WebApplicationContext
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcher': initialization completed in 532 ms
8월 24, 2020 11:14:21 오전 org.apache.coyote.AbstractProtocol start
정보: 프로토콜 핸들러 ["http-nio-8009"]을(를) 시작합니다.
8월 24, 2020 11:14:21 오전 org.apache.catalina.startup.Catalina start
정보: 서버가 [3,944] 밀리초 내에 시작되었습니다.
INFO : org.zerock.controller.HomeController - Welcome home! The client locale is ko_KR.
INFO : org.zerock.controller.HomeController - Welcome home! The client locale is ko_KR.
INFO : org.zerock.controller.HomeController - Welcome home! The client locale is ko_KR.
INFO : org.zerock.controller.HomeController - Welcome home! The client locale is ko_KR.
INFO : org.zerock.controller.HomeController - Welcome home! The client locale is ko_KR.
INFO : org.zerock.controller.HomeController - Welcome home! The client locale is ko_KR.
INFO : org.zerock.controller.HomeController - Welcome home! The client locale is ko_KR.
root-context.xml이 처리 되면 파일에 있는 (Bean) 설정들이 동작하게 된다.
root-context.xml에 정의된 객체들은 스프링 영역(Context) 안에 생성되고 객체들 간의 의존성이 처리된다.
root-context.xml이 처리된 후에는 스프링 MVC에서 사용하는 DispatcherServlet이라는 서블릿과 관련된 설정이 동작하게 된다.
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 모든 요청은 DISPACHER SERVLET이 받는다 -->
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
org.springframework.web.servlet.DispatcherServlet 클래스는 스프링 MVC 구조에서 가장 핵심적인 역할을 하는 클래스이다. 내부적으로 웹 관련 처리의 준비 작업을 진행하는데 이때 사용하는 파일이 servlet-context.xml이다. 프로젝트가 실행될 때 로그의 일부를 보면 아래와 같아.
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'appServlet': initialization started
INFO : org.springframework.web.context.support.XmlWebApplicationContext - Refreshing WebApplicationContext for namespace 'appServlet-servlet': startup date [Mon Aug 24 11:14:19 KST 2020]; parent: Root WebApplicationContext
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/appServlet/servlet-context.xml]
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/],methods=[GET]}" onto public java.lang.String org.zerock.controller.HomeController.home(java.util.Locale,org.springframework.ui.Model)
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: WebApplicationContext for namespace 'appServlet-servlet': startup date [Mon Aug 24 11:14:19 KST 2020]; parent: Root WebApplicationContext
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: WebApplicationContext for namespace 'appServlet-servlet': startup date [Mon Aug 24 11:14:19 KST 2020]; parent: Root WebApplicationContext
INFO : org.springframework.web.servlet.handler.SimpleUrlHandlerMapping - Mapped URL path [/resources/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'appServlet': initialization completed in 729 ms
DispatcherServlet에서 XmlWebApplicationContext를 이용해서 servlet-context.xml을 로딩하고 해석하기 시작한다. 이과정에서 등록된 객체들은 기존에 만들어진 객체들과 같이 연동하게 된다.
1. 사용자의 Request는 front-controller인 dispatcherServlet을 통해서 처리한다. 생성된 프로젝트의 web.xml을 보면 아래와 같이 request를 dispatcherServlet이 받도록 처리하고 있다.
2.3.HandlerMapping은 Request의 처리를 담당하는 컨트롤러를 찾기 위하여 존재한다.
HandlerMapping 인터페이스를 구현한 여러 객체들 중 RequestMappingHandlerMap-ping같은 경우는 개발자가
@RequestMapping 어노테이션이 적용된 것을 기준으로 판단하게 된다. 적절한 컨트롤러가 찾아졌다면 HandlerAdapter를 이용해서 해당 컨트롤러를 동작시킨다.
4. Controller는 개발자가 작성하는 클래스로 실제 Request를 처리하는 로직을 작성하게 된다. 이때 View에 전달해야 하는 데이터는 주로 Model이라는 객체에 담아서 전달한다. Controller는 다양한 타입의 결과를 반환하는데 이에 대한 처리는 ViewResolver를 이용하게 된다.
5.ViewResolver는 Controller가 반환한 결과를 어떤 View를 통해서 처리하는 것이 좋을지 해석하는 역할을 한다. 가장 흔하게 사용하는 설정은 servlet-context.xml에 정의된 Inter-nalResourceViewResolver이다.
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
6.7 View는 실제로 응답 보내야하는 데이터를 Jsp등을 이용해서 생성하는 역할을 하게 된다. 만들어진 응답은 DispatcherServlet을 통해서 전송된다.
정리 위의 그림을 보면 모든 Request는 DispatcherServlet을 통하도록 설계되는데 이런 방식은 Front-Controller 패턴이라고 한다.
Front-Controller 패턴을 이용하면 전체 흐름을 강제로 제한할 수 있다. 예를들어 HttpServlet을 상속해서 만든 클래스를 이용하는 경우 트정 개발자는 이를 활용할 수 있지만 다른 개발자는 자신이 원래 하는 식대로 HttpServlet을 그대로 상속해서 개발할 수도 있다. Front-Controller 패턴을 이요하는 경우에는 모든 Request의 처리에 대한 분배가 정해진 방식대로만 동작하기 때문에 좀 더 엄격한 구조를 만들어 낼 수 있다.