SpringMVC启动流程分析

*基于3.1.2

我们知道web.xml是一个web应用的核心配置文件,也是我们认识应用的最佳途径,这次我们同样从web.xml入手.通过分析里面的配置进而了解整个spring的启动流程.

以目前正在经手的项目实例为参考,附上web.xml中的主要配置(仅附上涉及到spring的部分,业务相关部分已略去):

ContextPram

<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>
      classpath:conf/spring/spring-admin-res.xml
   </param-value>
</context-param>

listener:

<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

servlet:

<servlet>
   <servlet-name>uops</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:conf/spring/spring-servlet.xml</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
   <servlet-name>uops</servlet-name>
   <url-pattern>*.htm</url-pattern>
</servlet-mapping>

接下来结合代码探索整个启动的过程:

接触web应用的童鞋都知道在web.xml中的配置是有顺序的,listener>filter>servlet,我们这里也同样从listener开始看起:

ContextLoaderListener:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    private ContextLoader contextLoader;

    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = this.createContextLoader();
        if(this.contextLoader == null) {
            this.contextLoader = this;
        }

        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }

    /** @deprecated */
    @Deprecated
    protected ContextLoader createContextLoader() {
        return null;
    }

    /** @deprecated */
    @Deprecated
    public ContextLoader getContextLoader() {
        return this.contextLoader;
    }

    public void contextDestroyed(ServletContextEvent event) {
        if(this.contextLoader != null) {
            this.contextLoader.closeWebApplicationContext(event.getServletContext());
        }

        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

这个类实现ServletContextListner(附录1),当应用启动时,会调用其中的contextInitialized()方法:

扫描二维码关注公众号,回复: 5917725 查看本文章
public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = this.createContextLoader();
    if(this.contextLoader == null) {
        this.contextLoader = this;
    }

    this.contextLoader.initWebApplicationContext(event.getServletContext());
}

调用ContextLoader(附录2)的初始化容器的方法:

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!");
    } else {
        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 {
            if(this.context == null) {
                this.context = this.createWebApplicationContext(servletContext);
            }

            if(this.context instanceof ConfigurableWebApplicationContext) {
                this.configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, 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 var8) {
            logger.error("Context initialization failed", var8);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
            throw var8;
        } catch (Error var9) {
            logger.error("Context initialization failed", var9);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
            throw var9;
        }
    }
}

首先会在servletContext执行

servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

看是否已有根应用上下文存在,存在直接抛出异常,不存在的话开始初始化spring root webApplicationContext,同时在容器日志的控制台会输出:

"Initializing Spring root WebApplicationContext"

在应用控制台会输出:

"Root WebApplicationContext: initialization started"

接下来会进入

createWebApplicationContext(servletContext);
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    Class<?> contextClass = this.determineContextClass(sc);
    if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    } else {
        ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        return wac;
    }
}

可以看到这个方法的返回对象是一个WebApplicationContext(附录3),那么我们需要的容器就是在这个方法产生的了。

 

第一行可以看到:

Class<?> contextClass = this.determineContextClass(sc);

这个方法会决定具体使用哪个webApplicationContext类,首先从web.xml中<context-param>的<param-name>中找contextClass,如果配置了指定的类,那么使用这个对象,没有配置的话则使用默认的XmlWebApplicationContext:

protected Class<?> determineContextClass(ServletContext servletContext) {
    String contextClassName = servletContext.getInitParameter("contextClass");
    if(contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        } catch (ClassNotFoundException var4) {
            throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
        }
    } else {
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        } catch (ClassNotFoundException var5) {
            throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
        }
    }
}

默认的对象获取则是在这里:

contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

通过调试,可以很清楚的看到defaultStrategies的内容:

这个对象的值是在静态代码块执行的:

static {
    try {
        ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    } catch (IOException var1) {
        throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
    }

    currentContextPerThread = new ConcurrentHashMap(1);
}

附上ContextLoader.properties的内容:

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

确定使用的具体webApplicationContext类以后,我们回到contextLoader这个类:

this.context = this.createWebApplicationContext(servletContext);

刚刚我们就是从这个方法开始的,现在我们已经获取到了这个context对象,看下context在类中的声明:

private WebApplicationContext context;

在返回具体值以后,context的内容是这样的:

我们继续走下去:

this.configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);

在configureAndRreshWebApplicationContext()中完成了所有bean的解析,加载和注册,我们一起进这个方法看下它是怎么实现的:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if(ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter("contextId");
        if(idParam != null) {
            wac.setId(idParam);
        } else if(sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getServletContextName()));
        } else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    ApplicationContext parent = this.loadParentContext(sc);
    wac.setParent(parent);
    wac.setServletContext(sc);
    String initParameter = sc.getInitParameter("contextConfigLocation");
    if(initParameter != null) {
        wac.setConfigLocation(initParameter);
    }

    this.customizeContext(sc, wac);
    wac.refresh();
}
String initParameter = sc.getInitParameter("contextConfigLocation");

注意这行代码,还记得我们在web.xml中contextParam的配置么:

<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>
      classpath:conf/spring/spring-admin-res.xml
   </param-value>
</context-param>

之所以param-name要使用contextConfigLocation作为key就是因为这个原因,通过这个key,spring来找到对应的spring配置文件的地址。

通过:

wac.setConfigLocation(initParameter);

将configLocation设置webApplicationContext的configLocation属性,

 

接下来执行:

this.customizeContext(sc, wac);

这个方法没有仔细看,好像是给容器进行一个默认的配置,

重点是下面的这个方法:

wac.refresh();

这个refresh()是spring ioc容器创建的核心方法,这篇文章中对这部分内容不做具体介绍,会在ioc容器部分中专门分析这个refresh()方法。现在我们回到ContextLoader这个类,此时的context对象中已经有了再spring配置文件中注册的那个bean对象,我们简单的调试下看下:

在beanFactory中已经有了具体的beanDefinition(附录3)对象,我们暂时不管,继续走下面的流程:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

还记得最开始获取根上下文的地方么,我们是在这个地方放入servletContext中的,保存的key为:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。

 

至此,spring启动的流程结束。

 

 

附录:

1.ServletContextListener:

public interface ServletContextListener extends EventListener {
    void contextInitialized(ServletContextEvent var1);

    void contextDestroyed(ServletContextEvent var1);
}

ServletContextListener接口用来监听web应用的声明周期,具体来说当应用启动或者关闭时,都会触发一个ServletContextEvent事件,这个事件会在ServletContextListener中进行处理:

应用启动:void contextInitialized();

应用关闭:void contextDestroyed();

2.ContextLoader:

应用上下文加载器,这个类主要交由子类ContextLoaderListener来调用,完成根应用上下文的初始化工作.

3.webApplicationContext可以理解是一个持有beanFactory的实例,所有对beanFactory的操作都是通过webApplicationContext来进行。

4.beanDifinition可以里面bean在spring内部保存的形式。

 

猜你喜欢

转载自blog.csdn.net/qq_23585245/article/details/84144929