지난 시간에는
root application context
가 어떤 과정을 거쳐 적재되는지 알아보았습니다.
이번에는servlet application context
가 올라가는 과정을 알아보도록 하겠습니다.
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
dispatcherServlet
이라는 이름을 지닌 servlet을 org.springframework.web.servlet.DispatcherServlet
클래스를 참조하여 생성합니다.
또한 contextConfigLocation
이라는 이름을 지닌 파라미터 변수를 하나 생성합니다.
해당 변수는 /WEB-INF/dispatcherServlet-servlet.xml
을 String으로 들고 있습니다.
우선순위는 1로 설정합니다.
<context:component-scan base-package="com.nhnacademy.springmvc" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/resources/" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<constructor-arg name="prefix" value="/WEB-INF/views/" />
<constructor-arg name="suffix" value=".jsp" />
</bean>
Controller 어노테이션에 대해 컴포넌트 스캔을 진행하도록 설정하였습니다.
또한 <mvc:annotation-driven />
를 통해 Spring MVC 설정을 활성화하고 있습니다.
마지막으로, 컨트롤러가 뷰 이름을 반환할 경우를 대비하여 InternalResourceViewResolver
를 통해 해당 뷰를 보여줄 수 있도록 하였습니다.
해당 설정은 바로 적용되지 않고, 추후 WAS에 의해 dispatcherServlet
이 생성될 때 사용됩니다.
dispatcherServlet
은 FrameworkServlet
을 상속받아 생성됩니다.
Detects a "contextClass" parameter at the servlet init-param level, falling back to the default context class, XmlWebApplicationContext, if not found. Note that, with the default FrameworkServlet, a custom context class needs to implement the ConfigurableWebApplicationContext SPI.
FrameworkServlet
은 contextClass
라는 매개변수가 있다면 해당 컨텍스트 클래스를 내부적으로 설정합니다. 그러나 없다면, XmlWebApplicationContext
로 설정합니다.
현재 매개변수 설정이 되어있지 않기 때문에 기본값인 XmlWebApplicationContext
가 contextClass로 설정됩니다.
Passes a "contextConfigLocation" servlet init-param to the context instance, parsing it into potentially multiple file paths which can be separated by any number of commas and spaces, like "test-servlet.xml, myServlet.xml". If not explicitly specified, the context implementation is supposed to build a default location from the namespace of the servlet.
public void setContextConfigLocation(@Nullable String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
contextConfigLocation
에 xml에서 설정한 dispatcherServlet-servlet.xml
을 등록합니다.
protected final void initServletBean() throws ServletException {
...
try {
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
}
...
}
WebApplicationContext()
메서드를 동작시킵니다.
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
... // webApplicationContext이 null이기에 통과
}
if (wac == null) {
wac = this.findWebApplicationContext(); // null이 반환됩니다.
}
if (wac == null) {
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized(this.onRefreshMonitor) {
this.onRefresh(wac);
}
}
if (this.publishContext) {
String attrName = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName, wac);
}
return wac;
}
해당 메서드에서는 FrameworkServlet
의 상위 abstract class인 GenericServlet
의 getServletContext()
메서드를 통해 ServletContext
를 얻어와 전달합니다.
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
servletContext
를 통해 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
를 가져와 반환합니다. 해당 값은 org.springframework.web.context.WebApplicationContext.ROOT
로서, 이전에 살펴 본 root applicaion context인 것을 알 수 있습니다.
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = this.getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
} else {
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(this.getEnvironment());
wac.setParent(parent);
String configLocation = this.getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
this.configureAndRefreshWebApplicationContext(wac);
return wac;
}
}
getContextClass()
에서는 이전에 설정되었던 XmlWebApplicationContext
를 반환합니다.
XmlWebApplicationContext
는 ConfigurableWebApplicationContext
를 구현했기에 if문이 통과되고, 인스턴스를 생성한 후 wac에 대입합니다.
StandardServletEnvironment()
를 통해 기본 environment를 설정하고, root application context를 parent로 지정하며 parent의 environment와 merge합니다(같은 StandardEnvironment로 지정되어 있기에, 해당 시점에서 크게 변경되는 건 없을 것 같습니다).
그 후 지정한 /WEB-INF/dispatcherServlet-servlet.xml
을 읽어들여 ConfigLocation으로 설정합니다.
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
if (this.contextId != null) {
wac.setId(this.contextId);
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());
}
}
wac.setServletContext(this.getServletContext());
wac.setServletConfig(this.getServletConfig());
wac.setNamespace(this.getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());
}
this.postProcessWebApplicationContext(wac);
this.applyInitializers(wac);
wac.refresh();
}
FrameworkServlet
의 id가 정해지지 않았으므로, else문의 코드가 돌게 되고 컨텍스트의 id가 org.springframework.web.context.WebApplicationContext:/dispatcherServlet
이 됩니다.
servletContext와 servletConfig를 등록 후 컨텍스트가 새로고침될 때만 이벤트를 처리할 listener를 추가합니다.
Replace any stub property source instances acting as placeholders with real servlet context/config property sources using the given parameters.
stub property source를 servlet의 property source로 올리는 듯 합니다만, 정확한 사항은 모르겠습니다. 추후 추가하도록 하겠습니다. 아마 StandardServletEnvironment()
를 통해 만든 envirenment를 서블렛에 적용시키는 게 아닐까 생각합니다.
그 후 refresh를 통해 빈을 생성하고 DI를 수행합니다.
if (this.publishContext) {
String attrName = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName, wac);
}
마지막으로, dispatcherServlet의 컨텍스트명을 읽어와 servletConfig에 등록 후 서블릿을 실행시킵니다.
이로써 servlet application context이 생성되며 root application context를 부모 서블렛으로 설정하는 부분과 servlet context에 적재되는 과정에 대해 확인해 보았습니다.