還搞不清Spring 與 Spring MVC 容器之間的關係?

徐家三少發表於2017-04-06

新書Java併發程式設計系統與模型已上線,歡迎拜讀。

在使用Spring MVC的時候,標準的配置是如下這樣的:

1.ContextLoaderListener配置:

    <!-- Spring讀取Spring的配置檔案 -->
    <context-param>
        <!-- 名稱 -->
        <param-name>contextConfigLocation</param-name>
        <!-- 檔案的位置 -->
        <param-value>classpath:application-context.xml</param-value>
    </context-param>
<context-param>
    <param-name>webAppRootKey</param-name>
    <param-value>meipian</param-value>
</context-param>
    <!-- Spring 的監聽器配置 -->
    <listener>
        <!-- 在Spring-web包下 context -->
        <listener-class>org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>複製程式碼

2.DispatcherServlet的配置:

<servlet>
        <servlet-name>meipian</servlet-name>
        <!-- 配置SpringMVC核心控制器 -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>
        <servlet-mapping>
        <servlet-name>meipian</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>複製程式碼

ContextLoaderListener監聽器的作用就是啟動Web容器時,自動裝配ApplicationContext的配置資訊。因為它實現了ServletContextListener這個介面,在web.xml配置這個監聽器,啟動容器時,就會預設執行它實現的方法。
Spring MVC的使用的容器是WebApplicationContext。那麼他和ContextLoaderListener所初始化的ApplicationContext有什麼關係呢?

可以從DispatcherServlet的初始化過程說起:DispatcherServlet攔截了所有的請求,所以訪問任何一個介面都會初始化DispatcherServlet物件。

初始化DispatcherServlet只是做了一個很簡單的事:

public DispatcherServlet() {
        super();
        setDispatchOptionsRequest(true);
    }複製程式碼
其父類FrameworkServlet初始化什麼也沒有:複製程式碼
    public FrameworkServlet() {
    }複製程式碼

然後Tomcat會呼叫DispatcherServlet的init方法,在 Servlet 的生命期中,僅執行一次 init() 方法。它是在伺服器裝入 Servlet 時執行的。 可以配置伺服器,以在啟動伺服器或客戶機首次訪問 Servlet 時裝入 Servlet。 無論有多少客戶機訪問 Servlet,都不會重複執行 init() 。別問我為什麼會呼叫init方法,這是servlet的規範。DispatcherServlet的init方法是在父類FrameworkServlet的父類HttpServlet中實現的:

@Override
    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");
        }
    }複製程式碼

會呼叫DispatcherServlet的initServletBean()方法就是初始化WebApplicationContext的方法。這個方法在其子類FrameServlet方法中實現:

@Override
    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(); //初始化WebApplicationContext物件
            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");
        }
    }複製程式碼

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;
    }複製程式碼
會預設走到createWebApplicationContext方法:複製程式碼
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = getContextClass();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + getServletName() +
                    "' will try to create custom WebApplicationContext context of class '" +
                    contextClass.getName() + "'" + ", using parent context [" + parent + "]");
        }
        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");
        }
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        wac.setConfigLocation(getContextConfigLocation());

        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }複製程式碼

在沒有 contexloaderListener的情況下,parent是空的。
在有 contexloaderListener的情況下,發現parent不是空的。

而且在Spring MVC的配置中,如果你將Service的配置與mvc的配置寫在一起,有沒有contexloaderListener無所謂的。

這就說明了contexloaderListener作用是什麼了。

相關文章