前言
前面介绍了: spring的启动流程、bean的生命周期、常见的扩展接口等知识,还提供了spring的简单使用示例方便读者阅读和理解spring源码。不过目前为止,spring给人的感觉是:
- 功能强大却又好像没啥用。
- 不知道如何将spring运用到实际项目中。
spring mvc就回应了上述疑问。它是spring在web开发上的运用,它能极大简化了web应用的开发工作量。在学习springmvc的启动流程前,先提供一个springmvc的简单使用示例,方便读者阅读和理解springmvc启动流程源码。
通过资料得知spring mvc的大致启动流程是:
- web容器(例如tomcat)启动时会加载web.xml中Servlet、Filter、Listener等组件,也就是会加载spring mvc自定义的DispatchServlet、ContextLoaderListener组件。
- web容器启动过程中,当ServletContext初始化之后,会调用ContextLoaderListener的contextInitialized(),spring mvc在contextInitialized()中会初始化一个根web应用上下文。
- DispatchServlet初始化过程中,会调用DispatchServlet的init(),spring mvc在init()会初始化一个子web应用上下文,并加载处理器映射、处理器适配、视图解析器等组件。
源码解析
目前已经知道ContextLoaderListener、DispatcherServlet的大致作用,接下来将深入这两个类的源码,知晓它们在spring mvc启动过程中的执行细节。ContextLoaderListener
ContextLoaderListener的继承结构
-
EventListener接口没定义接口方法,仅是标识的作用。
-
ServletContextListener是Servlet规范中定义的接口,定义了两个接口方法:
- contextInitialized:ServletContext初始化之后会调用该方法。
- contextDestroyed:ServletContext销毁之后会调用该方法。
-
ContextLoader类是spring定义的类,其作用是加载root application context,也就是根web应用上下文。至于ContextLoaderListener加载根web应用上下文的能力就依托ContextLoader类。
ContextLoaderListener的执行流程
- 调用了父类ContextLoader的initWebApplicationContext()。
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
-
ContextLoader的initWebApplicationContext()主要干了3件事:
- 创建根web应用上下文(如果不存在则创建)。
- 配置并刷新根web应用上下文。
- 将根web应用上下文和ServletContext进行绑定。
/**
* 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) {
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!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
// 根web应用上下文不存在则创建
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
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);
}
// 配置并刷新根web应用上下文
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将根web应用上下文和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.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
- 创建根web应用上下文的createWebApplicationContext()的逻辑是:根据ServletContext确定web应用上下文的class,默认是XmlWebApplicationContext,实例化一个web应用上下文对象。
/**
* Instantiate the root WebApplicationContext for this loader, either the
* default context class or a custom context class if specified.
* <p>This implementation expects custom contexts to implement the
* {@link ConfigurableWebApplicationContext} interface.
* Can be overridden in subclasses.
* <p>In addition, {@link #customizeContext} gets called prior to refreshing the
* context, allowing subclasses to perform custom modifications to the context.
* @param sc current servlet context
* @return the root WebApplicationContext
* @see ConfigurableWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
-
配置和刷新根web应用上下文的configureAndRefreshWebApplicationContext()的逻辑是:
- 设置根web应用上下文的id。
- 将ServletContext的configLocationParam参数值设置给根web应用上下文。
- 初始化根山下文的Environment。
- 执行ApplicationContextInitializer的initialize()
- 执行ApplicationContext的refresh()。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// 设置根web应用上下文的id
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);
// 将ServletContext中的configLocationParam设置给根web应用上下文
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);
}
// 执行ApplicationContextInitializer的initialize()
customizeContext(sc, wac);
wac.refresh();
}
总结
可见ContextLoaderListener的执行过程是:
DispatcherServlet
DispatcherServlet的继承结构
DispatchetServlet的初始化过程
DispatcherServlet初始化过程的核心逻辑在init的方法里。为了方便理解,我将DispacherServlet和其各层级父类区分来,用下图表示init方法的执行过程:
-
DispacherServlet最终会执行父类FrameworkServelt的initWebApplicationContext(), initWebApplicationContext()常规执行链路是:
- 将根web应用上下文作为入参调用createWebApplicationContext()创建子web应用上下文。
- 调用onRefresh方法。
- 将子web应用上下文和ServletContext绑定。
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
// 获取根web应用上下文
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
// web应用上下文还未刷新,则为其设置父应用上下文
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
// 3. 刷新应用上下文
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
// 将根web应用上下文作为入参调用createWebApplicationContext()创建子web应用上下文。
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
//
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
-
createWebApplicationContext()作用是:
- 创建子web应用上下文。
- 将根web应用上下文设置成子web应用上下文的parent。
- 配置并刷新子web上下文。
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {@link #setContextClass custom context class}, if set.
* <p>This implementation expects custom contexts to implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface. Can be overridden in subclasses.
* <p>Do not forget to register this servlet instance as application listener on the
* created context (for triggering its {@link #onRefresh callback}, and to call
* {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
* before returning the context instance.
* @param parent the parent ApplicationContext to use, or {@code null} if none
* @return the WebApplicationContext for this servlet
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 创建子web应用上下文
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 将根web应用上下文设置成子web应用上下文的parent
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 配置并刷新子web上下文
configureAndRefreshWebApplicationContext(wac);
return wac;
}
-
configureAndRefreshWebApplicationContext()做的事情是:
- 设置子web应用上下文的id。
- 加载子web应用上下文的Environment。
- 先执行全局的ApplicationContextInitializer、再执行当前DispathcetServlet指定的ApplicationContextInitializer。
- 调用ApplicationContext的refresh()。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// 设置子web应用上下文的id
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
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 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
// 加载子web应用上下文的Environment
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
//先执行全局的ApplicationContextInitializer、再执行当前DispathcetServlet指定的ApplicationContextInitializer
applyInitializers(wac);
//调用ApplicationContext的refresh()
wac.refresh();
}
总结
因此,前面的图可以扩展成: