Spring 在 WEB 容器中的启动过程
描述
-
对于一个
WEB
应用来说,它需要部署在WEB容器
中,且WEB容器
会提供一个全局的上下文环境ServletContext
,也就是SpringIOC容器
的宿主环境。 -
WEB容器
启动时,会加载web.xml
中提供的contextLoaderListener
监听器,会触发容器初始化事件,contextLoaderListener
会监听到该事件,其contextInitialized
方法会被调用。在该方法中,Spring
会初始化一个启动上下文
,也被称为根上下文
,即WebApplicationContext
,确切说,其实际实现类是XmlWebApplicaitonContext
。其实它就是Spring IOC 容器
,其对应的Bean
定义配置,由web.xml
中context-param
标签指定。在Spring IOC容器
初始化完毕后,Spring
以WebApplicationContext.ROOTWEBAPPLICATIONEXTATTRIBUTE
为属性key,将其存储到ServletContext
中servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
。
3.contextLoaderListener
监听器初始化完毕后,开始初始化web.xml
中配置的Servlet
,且Servlet
可以配置多个,以最常见的DispatcherServlet
为例,这个Servlet
实际上是一个标准的前端控制器
,用以 转发
匹配
处理
每个servlet 请求。
4.DispatcherServlet
上下文在初始化的时候会建立自己的 IOC容器上下文
,用以持有Spring MVC
相关的Bean
。在创建上下文时,会根据WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE
该Key
,从ServletContext
中获取之前的根上下文
(WebApplicationContext
),来作为自己上下文的 Parent 上下文
。有了该Parent 上下文
后,再初始化自己持有的上下文。通过initStrategies
方法中可以看到,DispatcherServlet
初始化自己上下文
大概的工作,就是初始化处理器映射
视图解析等等
,默认实现类也是 XmlWebApplicationContext
。
5.DispatcherServlet
上下文在初始化完成后,Spring
会将与Servlet
的名字相关的属性,作为key,将其存到ServletContext
中。这样每个 Servlet
都持有自己的上下文,即拥有自己独立的 Bean
空间,同时各个Servlet
共享相同的Bean
,即根上下文定义的Bean
。
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register 控制台信息打印 [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] :Mapped "{[/appEdition/list.do]}" onto public org.springframework.web.servlet.ModelAndView com.hengtn.jiang.controller.AppEditionController.list(com.hengtn.jiang.entity.query.AppEditionQuery,javax.servlet.http.HttpSession,java.lang.Integer)
Spring容器在Web容器中的创建及初始化
SpringIOC
是一个独立的模块,并不是直接在Web容器
中发挥作用的,假若要在Web
环境使用IOC容器
的话,需要Spring
为IOC
设计一个启动过程,将IOC容器
导入,并在Web容器
中建立起来。
web.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<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>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>180</session-timeout>
</session-config>
</web-app>
contextConfigLocation
对应的value
是Spring配置文件
的绝对路径
ContextLoaderListener
监听器主要用来对Servlet容器
(这里指Tomcat
)的行为进行监听
ContextLoaderListener
源码
/**
* Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
* Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
*
* <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
* application context via the {@link #ContextLoaderListener(WebApplicationContext)}
* constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
* See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 17.02.2003
* @see #setContextInitializers
* @see org.springframework.web.WebApplicationInitializer
*/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
/**
* Create a new {@code ContextLoaderListener} that will create a web application
* context based on the "contextClass" and "contextConfigLocation" servlet
* context-params. See {@link ContextLoader} superclass documentation for details on
* default values for each.
* <p>This constructor is typically used when declaring {@code ContextLoaderListener}
* as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
* required.
* <p>The created application context will be registered into the ServletContext under
* the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
* and the Spring application context will be closed when the {@link #contextDestroyed}
* lifecycle method is invoked on this listener.
* @see ContextLoader
* @see #ContextLoaderListener(WebApplicationContext)
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener() {
}
/**
* Create a new {@code ContextLoaderListener} with the given application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based
* registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
* API.
* <p>The context may or may not yet be {@linkplain
* org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
* (a) is an implementation of {@link ConfigurableWebApplicationContext} and
* (b) has <strong>not</strong> already been refreshed (the recommended approach),
* then the following will occur:
* <ul>
* <li>If the given context has not already been assigned an {@linkplain
* org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #customizeContext} will be called</li>
* <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
* specified through the "contextInitializerClasses" init-param will be applied.</li>
* <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
* </ul>
* If the context has already been refreshed or does not implement
* {@code ConfigurableWebApplicationContext}, none of the above will occur under the
* assumption that the user has performed these actions (or not) per his or her
* specific needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* <p>In any case, the given application context will be registered into the
* ServletContext under the attribute name {@link
* WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
* application context will be closed when the {@link #contextDestroyed} lifecycle
* method is invoked on this listener.
* @param context the application context to manage
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
由源码得出,
ContextLoaderListener
继承自ContextLoader
,并且还实现了ServletContextListener
,而且它的构造函数中,需要传入了一个WebApplicationContext
,它是继承自ApplicationContext
接口的高级IOC容器
ServletContextListener 源码
public interface ServletContextListener extends EventListener {
/**
** Notification that the web application initialization process is starting.
* All ServletContextListeners are notified of context initialization before
* any filter or servlet in the web application is initialized.
* @param sce Information about the ServletContext that was initialized
*/
public void contextInitialized(ServletContextEvent sce);
/**
** Notification that the servlet context is about to be shut down. All
* servlets and filters have been destroy()ed before any
* ServletContextListeners are notified of context destruction.
* @param sce Information about the ServletContext that was destroyed
*/
public void contextDestroyed(ServletContextEvent sce);
}
ServletContextListener
是 Servlet
中比较重要的一个接口,用于监听 Servlet
容器的启动
销毁
事件,所以在 ContextLoaderListener 中:
contextInitialized(ServletContextEvent sce)
:参数为所要监听的ServletContextEvent
,也就是Tomcat
启动加载完web.xml
会产生的事件,ServletContextEvent
持有从web.xml
加载的初始化配置ServletContext 上下文
contextDestroyed(ServletContextEvent sce)
:在Tomcat
关闭的时候执行该方法
启动时,
ServletContextListener
的执行顺序与web.xml
中的配置顺序一致,停止时执行顺序正相反
流程梳理
当
Servlet容器
启动事件发生时,将被ContextLoaderLister
监听,此时ContextLoaderListener
会调用contextInitialized(ServletContextEvent sce)
方法,该方法为ContextLoaderLister
实现ServletContextListener
接口的方法,并将在web.xml
加载初始化完成后,获取的ServletContext
传入initWebApplicationContext()
方法中,进行IoC容器的初始化
initWebApplicationContext()
方法从 ContextLoader
继承而来,进入ContextLoader
源码中查看
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
//private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
ContextLoader
类中的静态代码块
创建
ClassPathResource
对象,同时把ContextLoader.properties
作为参数传入,易知ContextLoader.properties
文件与ContextLoader
类是在同一个目录下,ContextLoader.properties
文件内容如下:org.springframework.web.context.WebApplicationContext=org.springframework. web.context.support.XmlWebApplicationContex
得到一个 Properties 对象,后面将根据类名来创建对应的 ApplicationContext 容器
因此可知Spring
默认初始化容器,是XmlWebApplicationContext
容器
initiWebApplicationContext()
方法
/**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
//从servletContext中获取ApplicationContext容器,若容器存在,则抛处初始化失败异常
//需要检查web.xml中是否定义了多个IOC容器的加载器
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
//将容器存储在本地变量,保证servletContext销毁之后,还能获取到它
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
//检查创建的ApplicationContext实例,是否实现了ConfigurableWebApplicationContext接口
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
//把当前容器设为根容器,并存放在servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
initWebApplicationContext()流程梳理
1.当调用
ContextLoaderListener
中的initWebApplicationContext()
方法,并且将获取到的servletContext
作为参数传入。
2.initWebApplicationContext()
首先会尝试从servletContext
中获取根容器,如果容器不为空,则容器初始化失败,因为web.xml
中可能定义了多个IOC容器
的加载器。
3.若此时容器还未初始化,则调用createWebApplicationContext()
方法创建一个容器。
4.创建完容器之后,将会调用一个非常重要的configureAndRefreshWebApplicationContext()
方法,在执行该方法的时候,会将从ApplicationContext.xml
配置文件中获取的内容,配置到已经创建好了的XmlWebApplicationContext
容器中去,并调用refresh()
方法来完成容器的初始化。
5.将已经完成初始化的XmlWebApplicationContext
容器注册到servletContext
中去
其实在Web容器中,ServletContext为Spring的IoC容器提供了宿主环境,对应的建立起一个IoC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象、数据存取对象、资源、事务管理器等各种中间层对象。在这个上下文的基础上,与Web MVC相关还会有一个上下文来保持控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。因为在initWebApplicationContext方法中我们可以看到其实创建ApplicationContext容器的工作是交由createWebApplicationContext方法来实现的,下面我们来看看这个方法
createWebApplicationContext源码
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//要创建的ApplicationContext的类型
Class<?> contextClass = determineContextClass(sc);
//如果获取到的ApplicationContext,没有实现ConfigurableWebApplicationContext接口,那么容器创建失败
//因此要创建的ApplicationContext,必须实现ConfigurableWebApplicationContext接口
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
//实例化Spring容器
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
功能:
决定要创建的ApplicationContext类型
实例化一个ApplicationContext
那么它是如何决定要创建的ApplicationContext类型的呢?
由determineContextClass()
方法决定的
protected Class<?> determineContextClass(ServletContext servletContext) {
//从web.xml获取要创建的IOC容器名称
//public static final String CONTEXT_CLASS_PARAM = "contextClass";
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
//如果获取到的类名不为空,则创建该容器的class对象
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
//否则创建默认的容器Class对象,即org.springframework.web.context.support.XmlWebApplicationContext
//在创建ContextLoader时,defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
//已经准备好默认的容器类
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
完成IOC容器
的创建后,在initWebApplicationContext()
中将调用configureAndRefreshWebApplicationContext()
初始化该容器
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
为创建好的
IOC容器
,设置Web应用
的上下文,以便二者整合
为同一个IOC容器
,设置配置文件的绝对路径
调用IOC容器
的refresh()
函数对其进行初始化