spring mvc 上下文环境的创建过程

来源:互联网 发布:三菱系统攻丝编程实例 编辑:程序博客网 时间:2024/05/22 10:49

我们都知道web应用的本质上就是一个context容器,一般是一个web应用就有一个context容器,我们想要应用在服务器上运行就需要一个web应用管理容器,这就是tomcat了(当然也有其他的容器,如jetty)。

可能有人会问为什么会需要tomcat容器呢?web应用不能直接部署到服务器吗?web应用直接部署这个当然也是可以的,但是这样我们就需要直接和服务器的操作系统进行交互,每开发一个web应用就要求我们重新写一套和服务器系统交互的程序,这样不符合我们的初衷。而tomcat就能够替我们做这件简单而又繁琐的事,把和服务器交互的程序交给tomcat,而我们只要实现自己的web应用,然后将web应用交给tomcat容器管理即可。

tomcat容器能够统一对web应用进行管理,即能够初始化,维护,销毁web应用。关于tomcat容器如何对web应用进行管理的,以后再做讨论。有兴趣的可以自行研究下。

servlet API中有一个ServletContextListener接口,由它来监听web应用的声明周期。tomcat容器会持有一个ServletContextListener接口实现对象,当web应用启动或者终止时(这两个操作也是由tomcat进行的)会出发一个ServletContextEvent 事件,该事件由ServletContextListener接口实现对象处理。这是典型的观察者模式的应用。
ServletContextListener接口定义了处理ServletContextEvent 事件的两个方法。由实现类根据具体功能来实现。

public interface ServletContextListener extends EventListener {    /**     * 容器初始化方法--容器启动时初始化根上下文(加载bean)     */    public void contextInitialized(ServletContextEvent sce);    /**     *容器销毁方法()     */    public void contextDestroyed(ServletContextEvent sce);}

Root WebApplicationContext初始化

接下来进入正题,学习spring mvc项目容器的启动过程.先看配置文件web.xml.

<!-- 环境参数 加载文件时首先解析 -->     <context-param>          <param-name>contextConfigLocation</param-name>          <param-value>classpath:spring/applicationContext.xml</param-value>      </context-param>   <!-- spring的监听器 初始化根上下文-->     <listener>       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>       </listener>     <servlet>        <servlet-name>spring-servlet</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <init-param>            <param-name>contextConfigLocation</param-name>              <param-value>classpath:spring/applicationContext.xml</param-value>          </init-param>        <load-on-startup>1</load-on-startup>     </servlet>     <servlet-mapping>        <servlet-name>spring-servlet</servlet-name>        <url-pattern>/*</url-pattern>     </servlet-mapping>

web.xml配置文件由tomcat容器读取,读取顺序context-param –> listener –> filter –> servlet;
tomcat容器解析context-param标签将标签内容以键值对的形式存储在tomcat容器中(即ServletContext对象),在根上下文创建完毕后加载bean时使用.
接下来是加载监听器ContextLoaderListener,它实现了ServletContextListener接口,在容器启动时默认调用其
contextInitialized()方法对根上下文进行初始化。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {    /**     *      *      */    @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());    }}

我们发现最终调用了initWebApplicationContext()方法,这个方法是从父类ContextLoader继承过来的。其实现代码如下:

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!");        }        //...        try {            if (this.context == null) {                //创建根上下文                this.context = createWebApplicationContext(servletContext);            }            if (this.context instanceof ConfigurableWebApplicationContext) {                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;                if (!cwac.isActive()) {                    if (cwac.getParent() == null) {                        ApplicationContext parent = loadParentContext(servletContext);                        cwac.setParent(parent);                    }                    configureAndRefreshWebApplicationContext(cwac, servletContext);                }            }//将创建好的根上下文注册到servletContext        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);            //...            return this.context;        }

首先保证整个web应用只能有一个跟上下文,如果已经有根上下文存在则会直接抛出异常。
接下来根据servletContext创建一个根web应用上下文,并将其注册到servletContext中.追踪createWebApplicationContext(servletContext)方法:

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);    }

创建根上下文时会根据ServletContext决定其类型,并在创建完成后强制转型为ConfigurableWebApplicationContext对象。
回到ContextLoader.initWebApplicationContext()方法,接下来执行configureAndRefreshWebApplicationContext()方法,读取前面设置的servletContext属性加载spring配置文件(context-param标签配置的),完成对spring相关bean的解析、加载、初始化。

DispatchServlet初始化

在spring mvc框架中,DispatchServlet是核心的servlet,负责请求的分发。在
DispatchServlet的设计中大量使用模板方法模式,所以看源码时我们要小心翼翼的结合DispatchServlet的继承结构来阅读。
tomcat容器在创建一个servlet时会首先调用init()方法来初始化servlet,因此应当首先查找该方法。入口init()方法在父类HttpServletBean中:

public final void init() throws ServletException {        if (logger.isDebugEnabled()) {            logger.debug("Initializing servlet '" + getServletName() + "'");        }        // Set bean properties from init parameters.        try {            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));            initBeanWrapper(bw);            bw.setPropertyValues(pvs, true);        }        catch (BeansException ex) {            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);            throw ex;        }        // Let subclasses do whatever initialization they like.        initServletBean();        if (logger.isDebugEnabled()) {            logger.debug("Servlet '" + getServletName() + "' configured successfully");        }    }

忽略其他的一些初始化设置代码,直接看initServletBean()方法,在FrameworkServlet类中实现:

protected final void initServletBean() throws ServletException {        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");        if (this.logger.isInfoEnabled()) {            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");        }        long startTime = System.currentTimeMillis();        try {            this.webApplicationContext = initWebApplicationContext();            initFrameworkServlet();        }        catch (ServletException ex) {            this.logger.error("Context initialization failed", ex);            throw ex;        }        catch (RuntimeException ex) {            this.logger.error("Context initialization failed", ex);            throw ex;        }        if (this.logger.isInfoEnabled()) {            long elapsedTime = System.currentTimeMillis() - startTime;            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +                    elapsedTime + " ms");        }    }

可以看到核心代码是this.webApplicationContext = initWebApplicationContext(); 作用就是初始化上下文,具体实现:

protected WebApplicationContext initWebApplicationContext() {        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                    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);                    }                    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();        }        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.            onRefresh(wac);        }        if (this.publishContext) {            // Publish the context as a servlet context attribute.            String attrName = getServletContextAttributeName();            getServletContext().setAttribute(attrName, wac);            if (this.logger.isDebugEnabled()) {                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +                        "' as ServletContext attribute with name [" + attrName + "]");            }        }        return wac;    }

最终执行wac = createWebApplicationContext(rootContext);创建一个mvc上下文,创建过程和根上下文创建过程基本一样,最后也会进行spring配置文件bean的解析、加载、初始化。
debug查看代码执行过程
在完成后会发布一个上下文刷新事件,通知所有的监听器进行处理,最终会调用DispatcherServlet的onfresh()方法初始化mvc相关组件。

阅读全文
0 0