1. 程式人生 > >springMVC初始化流程(二)

springMVC初始化流程(二)

首先通過時序圖來探索Spring原始碼解析 - springMVC核心程式碼(一)中最後一個問題,spring mvc 提供的控制器型別handlerMappings  是如何被初始化的。有原始碼可得:

其初始化是在DispatcherServlet類中的這個方法進行的,而這個初始化方法initHandlerMappings()又是什麼時候執行的呢?在該類中發現,是在下面方法中執行初始化的,同時發現好多元件都是在這個方法中執行,比如另一個重要的物件控制器介面卡

那麼歸根究底,其sprinngMVC的初始化是在onRefresh()這個方法中執行的。

因此下面來探索springMVC的初始化流程:(圖片來源於網路)

servlet初始化會呼叫 init 方法,換句話說就是springMVC進行初始化的時候首先會去執行HttpServletBean的init方法, 下面看看HttpServletBean的原始碼:

 public final void init() throws ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Initializing servlet '" + this.getServletName() + "'");
        }
       //從init引數設定bean屬性
        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                this.initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var4) {
                if (this.logger.isErrorEnabled()) {
                    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
                }

                throw var4;
            }
        }
       //讓子類做任何他們喜歡的初始化
        this.initServletBean();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully");
        }

    }

上面這段程式碼主要是在獲取你在web.xml中配置在<init-param>中的屬性(例如: namespace, contextConfigLocation)。 其中有一點值得注意,那就是 initServletBean() 這個方法是由其子類 FrameworkServlet 實現,因此, 接下來 FramworkServlet 會執行 initServletBean 這個方法,下面就繼續看看 initServletBean 方法原始碼:

protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started");
        }

        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = this.initWebApplicationContext();
            this.initFrameworkServlet();
        } catch (RuntimeException | ServletException var5) {
            this.logger.error("Context initialization failed", var5);
            throw var5;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms");
        }

    }

可以看到 initServletBean 方法中就呼叫了一個 initFrameworkServlet 方法和 initWebApplicationContext 方法,其中initFrameworkServlet方法是由子類實現,這個不多說,直接看 initWebApplicationContext 方法原始碼:

 protected WebApplicationContext initWebApplicationContext() {

        //此處的 rootContext 在你配置了ContextLoaderListener的時候注入的
        //通過分析ContextLoaderListenr的原始碼,可以看到
        //ContextLoaderListener通過ContextLoader根據ApplicationContext.xml的配置會建立一個xmlWebApplicationContext
        //如果沒有配置ContextLoaderListener,本處將為null,但不影響springMVC,為何?通過接下來的分析,就能看到原因
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
       
       //當webApplicationContext已經存在,那麼就直接使用,使用之前會先設定rootContext,為其跟。
       //配置完成之後refresh一次,refresh會涉及到IOC的內容,本處不做探討。

        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }

                    this.configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        //如果不存在webApplicationContext,那麼先去ServletContext中查詢
        if (wac == null) {
            wac = this.findWebApplicationContext();
        }
        //如果上述沒有查到,那麼就建立webApplicationContext
        if (wac == null) {
            wac = this.createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
             //此方法由DispatcherServlet呼叫
            this.onRefresh(wac);
        }
        //將webApplicationContext儲存在ServletContext
        if (this.publishContext) {
            //將上下文釋出為servlet上下文屬性。
            String attrName = this.getServletContextAttributeName();
            this.getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

上面程式碼呼叫了一次 createWebApplicationContext 方法, 下面就看看這個方法的原始碼:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        
       //此處的contextClass 可在web.xml 中的《init-param》中指定
       //如果沒有配置,那麼預設的是XmlWebApplicationContext.class
        Class<?> contextClass = this.getContextClass();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + this.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 '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
             //此處利用反射建立
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);
            String configLocation = this.getContextConfigLocation();
            if (configLocation != null) {
                wac.setConfigLocation(configLocation);
            }
            //refresh一次,這裡不多說
            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }

以上就是建立webApplicationContext的程式碼,註釋已給出。

還記得FrameworkServlet初始化的時候會呼叫onRefresh()方法嗎,這個方法是留給其子類DispatcherServlet呼叫的, 最後就該看看DispatcherServlet裡面的 onRefresh 方法了:

 protected void onRefresh(ApplicationContext context) {
        this.initStrategies(context);
    }
     //初始化此servlet使用的策略物件。
     //可以在子類中重寫以初始化其他策略物件
    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

可以看到onRefresh方法就一句話,呼叫initStrategies方法, 上面給出了 initStrategies 原始碼, 很直觀,就是在初始化springMVC 的一系列元件, 但是此處你要明白,SpringMVC的元件其實已經在webApplicationContext建立時就已經例項化了, 此處所謂的初始化只是在選擇合適的元件(每一個元件都對應了幾個不同的實現)。