Spring MVC源碼解讀二

接上一篇:Spring MVC源碼解讀一

  • DispatchServlet:
    源碼位置:

DispatcherServlet是前置控制器,配置在web.xml文件中的。攔截匹配的請求,Servlet攔截匹配規(guī)則要自已定義,把攔截下來的請求,依據(jù)相應(yīng)的規(guī)則分發(fā)到目標(biāo)Controller來處理,是配置spring MVC的第一步

類繼承關(guān)系:



時序圖:


  • servlet初始化鏈路:
    通過時序圖可以看到,HttpServletBean中調(diào)用了initServletBean()方法,通過源代碼可以看到是在HttpServletBean的init()中調(diào)用了initServletBean()
    HttpServletBean的init()方法源碼如下,重寫了HttpServlet的init()方法:
    /**
     * Map config parameters onto bean properties of this servlet, and
     * invoke subclass initialization.
     * @throws ServletException if bean properties are invalid (or required
     * properties are missing), or if subclass initialization fails.
     */
    @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) {
            if (logger.isErrorEnabled()) {
                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");
        }
    }

問題: 1、HttpServlet的init()是什么時候調(diào)用的? 解答: 在前面解讀spring mvc在web.xml中的load-on-startup配置時已經(jīng)回答過了
FrameworkServlet中的initServletBean()源碼

/**
     * Overridden method of {@link HttpServletBean}, invoked after any bean properties
     * have been set. Creates this servlet's WebApplicationContext.
     */
    @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 (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");
        }
    }

可以看到initServletBean()中具體的處理都是通過調(diào)用initWebApplicationContext()方法實現(xiàn)的,具體源碼如下:

/**
     * Initialize and publish the WebApplicationContext for this servlet.
     * <p>Delegates to {@link #createWebApplicationContext} for actual creation
     * of the context. Can be overridden in subclasses.
     * @return the WebApplicationContext instance
     * @see #FrameworkServlet(WebApplicationContext)
     * @see #setContextClass
     * @see #setContextConfigLocation
     */
    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;
    }

initWebApplicationContext()正如方法名的直觀含義所表示的,它的作用就是初始化webApplicationContext,主要步驟有:
1、通過servletcontext獲取ContextLoaderListener中初始化的根WebApplicationContext
2、調(diào)用onRefresh(rootContext)來對webApplicationContext進(jìn)行初始化(主要是加載mvc相關(guān)的BeanDefinition到webApplicationContext中)
問題: ContextLoaderListener和FrameworkServlet類中都有initWebApplicationContext()方法,二者有什么區(qū)別? 解答: a) ContextLoaderListener是監(jiān)聽Servlet的啟動/結(jié)束,在啟動時調(diào)用initWebApplicationContext()初始化web的根應(yīng)用上下文; b) FrameworkServlet調(diào)用initWebApplicationContext()時,會先取ContextLoaderListener初始化的web根應(yīng)用上下文,對其進(jìn)行mvc相關(guān)的初始化:FrameworkServlet的initWebApplicationContext()方法通過調(diào)用DispatcherServlet的onRefresh()和initStrategies()方法實現(xiàn)對mvc相關(guān)的BeanDefinition加載(具體需要接下來看initStrategies()方法源碼)

由于DispatcherSerlvet的onRefresh直接調(diào)用了initStrategies()方法,我們看initStrategies()的源碼:

/**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

我們逐個解讀這些初始化方法,他們都是從webApplicationContext中通過getBean()方法獲取BeanDefinition的實例:
1.以initMultipartResolver為例:

    /**
     * Initialize the MultipartResolver used by this class.
     * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
     * no multipart handling is provided.
     */
    private void initMultipartResolver(ApplicationContext context) {
        try {
            this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
            }
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Default is no multipart resolver.
            this.multipartResolver = null;
            if (logger.isDebugEnabled()) {
                logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
                        "': no multipart request handling provided");
            }
        }
    }

initMultipartResolver從webApplicationContext獲取名為“multipartResolver”的Bean定義,如果spring的配置中沒有配置的話,webApplicationContext也獲取不到該Bean定義,則multipartResolver不生效

這些初始化方法分別對應(yīng)spring mvc配置中的這些Bean:
multipartResolver
localeResolver
themeResolver
handlerMapping:指定handlerMapping的處理類
handlerAdapter
handlerExceptionResolver
viewNameTranslator
viewResolver:指定視圖解析類
flashMapManager
例如:viewResolver配置


我們重點(diǎn)看下initHandlerMappings()的源碼,他是從mvc中重要的url mapping的基礎(chǔ):

    /**
     * Initialize the HandlerMappings used by this class.
     * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
     * we default to BeanNameUrlHandlerMapping.
     */
    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

這段源碼的主要作用是指定用于handlerMapping的具體處理類,并將其賦值給DispatcherServlet的handlerMappings字段,可以單步調(diào)試看下HandlerMapping的具體值:


可以看到handleMappings默認(rèn)值有3個: 1.RequestMappingHandlerMapping 2.BeanNameUrlHandlerMapping 3.SimpleUrlHandlerMapping

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 閱讀前提:1、理解IOC的一些概念,以及在Spring中的實現(xiàn)(上下文,BeanFactory,BeanDefin...
    測試你個頭閱讀 1,056評論 0 4
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • 在使用SpringMVC時會在Web.xml配置如下代碼: 先看看DispatcherServlet類的繼承關(guān)系 ...
    zlb閱讀 1,715評論 0 2
  • 一、環(huán)境搭建 創(chuàng)建一個web項目。 如果是maven項目,則直接在pom中加入springMvc依賴 如果不是就從...
    zxcvbnmzsedr閱讀 1,526評論 0 1
  • 今天朋友小何給我打來電話,說結(jié)婚之后一直比較忙碌,沒有來得急感謝我在他辦結(jié)婚酒時給他的幫忙,我說好朋友之間也沒不需...
    憤怒的老鳥閱讀 259評論 0 2