SpringMVC源碼解析(一)——初始化

前言

    本系列文章順延「Spring源碼解析」,是在「父容器」建立完成後,對「子容器」(SpringMVC)建立,以及請求處理的解析。java

 

源碼解讀

    提及 SpringMVC,DispatcherServlet 應該是最熟悉的類之一。它幾乎掌控着整個請求的分發以及最終響應,咱們就從它來追溯「子容器」建立的源頭。web

public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {
    // getInitParameter可獲取配置的 <init-param>
    // getServletName可獲取配置的 <servlet-name>
    private transient ServletConfig config;

    // SpringMVC啓動入口
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        // 子類實現
        this.init();
    }
}

    該類就是 DispatcherServlet 的最頂層抽象父類了,它實現了 Servlet.init 方法,經過 Servlet 規範咱們能夠了解到,根據 <load-on-startup> 配置的不一樣,調用時機也不一樣。mvc

  • ≥0 :會在 Servlet 被實例化後執行;
  • <0:在該 Servlet 被第一次請求時才調用,不填默認走該邏輯
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {

    // GenericServlet.init調用
    @Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // 將 <init-param>鍵值對封裝成 PropertyValue
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                // 將自身(DispatcherServlet)封裝成 BeanWrapper,方便 Spring注入
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                // 註冊解析器,對於 Resource類型的屬性用 ResourceEditor解析
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                // 屬性注入:第二個參數會忽略未找到的屬性
                bw.setPropertyValues(pvs, true);
            } catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // 子類擴展實現
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }
}

    這一層主要是針對配置屬性相關的代碼,若是看過以前「實例建立」章節,對 bw.setPropertyValues(pvs, true) 應該不陌生,就是爲 Servlet 作屬性填充。接下來 initServletBean 方法實現了幾乎「子容器」建立的大部分工做,由 FrameworkServlet 實現:app

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

    // 可用 <init-param>:contextClass指定
    private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
    // 默認 XmlWebApplicationContext
    public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;

    @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();
            // 空實現:可自定義擴展
            initFrameworkServlet();
        }....// 省略 catch

		....// 省略日誌
    }

    protected WebApplicationContext initWebApplicationContext() {
        // 獲取父容器:經過調用 ServletContext.getAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)
        // 設置邏輯見容器啓動: ContextLoader.initWebApplicationContext
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        // 是經過構造器傳入的方式會走這個分支(見 SpringBoot)
        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                // 調用 refresh會置 active爲 true
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        // 設置父容器
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // 查找 contextAttribute是否已綁定的上下文 key
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // 若是尚未找到上下文,建立一個
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // 子容器(SpringMVC),在容器刷新完畢後執行
            onRefresh(wac);
        }

        if (this.publishContext) {
            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;
    }

    protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
        return createWebApplicationContext((ApplicationContext) parent);
    }

    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {

        // 默認 XmlWebApplicationContext
        // 可用<init-param>:contextClass指定
        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);
        // 獲取 <servlet>中配置的 <init-param>:contextConfigLocation
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            // 設置「mvc」配置文件所在路徑
            wac.setConfigLocation(configLocation);
        }
        // 和父容器類似的套路
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // 設置 ID
            if (this.contextId != null) {
                wac.setId(this.contextId);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
            }
        }

        // Servelt相關的設置
        wac.setServletContext(getServletContext());
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());

        // 註冊事件監聽器:被 SourceFilteringListener包裹,僅在「子容器」(mvc)中觸發
        // ContextRefreshListener:觸發 FrameworkServlet.onApplicationEvent
        // onApplicationEvent進而調用:onRefresh
        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

        // 任何狀況下 initPropertySources都在上下文刷新時被調用
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
        }

        // 空實現
        postProcessWebApplicationContext(wac);

        // ApplicationContextInitializer執行,由如下屬性指定
        // <init-param>:globalInitializerClasses、contextInitializerClasses指定
        applyInitializers(wac);

        // 容器刷新:AbstractApplicationContext實現(同父容器)
        wac.refresh();
    }
}

    拋開一些設置屬性的步驟不談,上面的邏輯主要能夠總結爲:容器實例化(反射調用構造器)——>將<init-param>指定的「mvc」配置文件設置到容器,以便隨後的解析——>而後註冊了一個「容器刷新」事件監聽器,以便回調——>調用 AbstractApplicationContext.refresh 刷新容器——>最後回調 onRefresh框架

    這裏的 onRefresh 在 FrameworkServlet 中是空實現,由子類定製實現。來看看 DispatcherServlet 的實現吧。ide

public class DispatcherServlet extends FrameworkServlet {

    // 在 refresh中的 finishRefresh中會發布「刷新」事件
    // 會觸發 ContextRefreshListener監聽器,繼而調用該方法
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    // 主要初始化了 mvc中所必須的成員變量 
    protected void initStrategies(ApplicationContext context) {
        // 若是定義了 MultipartResolver,實例化
        initMultipartResolver(context);

        // 若是定義了 LocaleResolver,實例化
        // 默認使用 AcceptHeaderLocaleResolver
        initLocaleResolver(context);

        // 若是定義了 ThemeResolver,實例化
        // 默認使用 FixedThemeResolver
        initThemeResolver(context);

        // 初始化全部的 HandlerMapping:具體處理器
        // 初始化參數 detectAllHandlerMappings設爲 false能夠只加載指定的bean
        initHandlerMappings(context);

        // 初始化全部的 HandlerAdapter
        // 初始化參數 detectAllHandlerAdapters設爲 false能夠只加載指定的bean
        initHandlerAdapters(context);

        // 初始化全部的 HandlerExceptionResolver(異常處理)
        initHandlerExceptionResolvers(context);

        // 若是定義了 RequestToViewNameTranslator,實例化
        // 默認使用 DefaultRequestToViewNameTranslator
        initRequestToViewNameTranslator(context);

        // 初始化全部的 ViewResolver
        // 默認使用 InternalResourceViewResolver
        initViewResolvers(context);

        // 若是定義了 FlashMapManager,實例化
        // 默認使用 DefaultFlashMapManager
        initFlashMapManager(context);
    }
}

    以上的這些就是「子容器」和「父容器」差別之處了,支持了「mvc」框架例如文件上傳請求映射異常處理視圖解析等功能。咱們會在以後的章節展開講解。post

 

總結

    本篇主要梳理了「子容器」(SpringMVC)的初始化脈絡,頂層父類留下可擴展的「口子」,讓子類去實現具體的初始化邏輯。ui

相關文章
相關標籤/搜索