1. 程式人生 > >【SpringMVC原始碼剖析】 前奏:夢開始的地方

【SpringMVC原始碼剖析】 前奏:夢開始的地方

SpringMVC原始碼剖析

縱覽:SpringMVC處理請求

本節作為SpringMVC原始碼剖析的基本,主要介紹以下三方面內容。

  • 1.核心DispatcherServlet的載入方式
  • 2.DispatcherServlet初始化過程
  • 3.SpringMVC接收請求並作出響應(粗粒度)

1.核心DispatcherServlet的載入方式

  • 1.1 傳統的JavaWeb工程,以web.xml來配置載入Servlet容器的上下文,這顯然有些麻煩。
  • 1.2 自從Servlet3.0+的出現,Spring-web模組提供了一種免配置檔案,在如Tomcat(支援Servlet3.0+)這樣的Servlet容器啟動時,自動呼叫了實現的WebApplicationInitializer(全路徑org.springframework.web.WebApplicationInitializer)介面的類的onStartup方法,並將容器的ServletContext往下傳遞。
public interface WebApplicationInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}
  • 1.3 從Spring-web模組的API中,找到子類AbstractAnnotationConfigDispatcherServletInitializer,它間接實現了WebApplicationInitializer介面,父類onStartup方法,首先建立RootWebApplicationContext並設定ContextLoaderListner監聽器;其次,註冊往servletContext註冊DispatcherServlet例項。
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);

        registerDispatcherServlet(servletContext);
    }
    protected
void registerDispatcherServlet(ServletContext servletContext) { //此處往servletContext新增DispatcherServlet例項 //併為DispatcherServlet新增過濾器 }
  • 1.4 由於AbstractAnnotationConfigDispatcherServletInitializer是抽象類,Spring容器不能注入。它重寫了父類建立根ApplicationContext與ServletApplicationContext方法,並抽象出兩個方法(用來建立根ApplicationContext和ServletApplicationContext容器時,需要注入的元件字類節陣列)。
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
        extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
            ...
    }
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        ...
    }
    //用來建立根ApplicationContext和ServletApplicationContext容器時,需要注入的元件字類節陣列
    protected abstract Class<?>[] getRootConfigClasses();
    protected abstract Class<?>[] getServletConfigClasses();

}
  • 1.5 因此 我們只需要編寫SpringMVCInitializer來繼承AbstractAnnotationConfigDispatcherServletInitializer類,讓它來幫我們完成對DispatcherServlet例項的載入即可。

2.DispatcherServlet初始化過程

  • 2.1 DispatcherServlet有個靜態塊,用於載入同包下DispatcherServlet.properties策略檔案的內容,程式碼如下
static {
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
        }
}

DispatcherServlet.properties 策略檔案的內容

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
  • 2.2 DispatcherServlet作為一個HttpServlet類,在載入時,會呼叫init方法,查詢或建立WebApplication容器並重新整理操作(如果使用WebApplicationInitilazer載入方式,RootWebApplicationContext是已經存在了)
public abstract class HttpServletBean extends HttpServlet
        implements EnvironmentCapable, EnvironmentAware {


    @Override
    public final void init() throws ServletException {
        initServletBean();
    }
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

    @Override
    protected final void initServletBean() throws ServletException {

        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()) {
                    ...
                    //重新整理,呼叫cwac.refresh()方法
                    //觸發了ContextRefreshListener.onApplicationEvent方法
                    //呼叫FrameworkServlet.this.onApplicationEvent方法
                    //呼叫子類,即DispatcherServlet.onRefresh方法
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        ...

        return wac;
    }

    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.refreshEventReceived = true;
        onRefresh(event.getApplicationContext());
    }


    protected void onRefresh(ApplicationContext context) {
        // 呼叫子類,即DispatcherServlet.onRefresh方法
    }

    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            FrameworkServlet.this.onApplicationEvent(event);
        }
    }
  • 2.3 呼叫DispatcherServlet.onRefresh方法,
@Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        //判斷Spring容器是否存在MultipartResolver,LocaleResolver,ThemeResolver,不存在列印日誌
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        //預設行為,載入容器的hadlerMappings,handlerAdapters,handlerExceptionResolvers,並排序
        //不存在則載入策略檔案
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        //容器中載入名為viewNameTranslator的解析器,
        //如果找不到,從策略檔案載入第一個Translator,即
        //org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
        initRequestToViewNameTranslator(context);
        /預設行為,載入容器的viewResolvers,並排序
        //不存在則載入策略檔案
        initViewResolvers(context);
        //容器中載入名為flashMapManager,
        //不存在的話,同上處理,找策略檔案中第一個
        initFlashMapManager(context);
    }

3.SpringMVC接收請求並作出響應(粗粒度)

  • 3.1 DispatcherServlet作為httpServlet,當接收到請求時,會呼叫doService方法
@Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //往request設定setAttribute,將當前環境(本地化解析器,主題解析器等)給request,方法mvc框架其他元件可以獲取到。
        ...
        try {
            //真實呼叫
            doDispatch(request, response);
        }
        finally {
            //為request設定WebAsyncManager屬性
            //如果是同步請求,恢復請求前的屬性
            ...
        }
    }
  • 3.2 DispatcherServlet的doDistpach方法,才是接收請求的核心方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                //判斷request頭部ContentType是否以"multipart/"開頭,來標記是否為附件上傳
                processedRequest = checkMultipart(request);
                //是否附件請求標記
                multipartRequestParsed = (processedRequest != request);

                //
                // 從handlerMappings(RequestMappingHanderMapping,策略檔案的handlerMapping類已經過時了),獲取HandlerExecutionChain
                //HandlerExecutionChain(包含一個handlerMethod,以及一堆handlerInterceptor)
                mappedHandler = getHandler(processedRequest);
                //找不到handlerMethod時,根據標是否丟擲異常,還是直接response.sendError
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 根據handerMethod找到HandlerAdapter(RequestMappingHandlerAdapter)
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // 修改 last-modified 頭部,如果支援的話.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 呼叫所有HandlerInterceptor的preHandle()方法,如果返回false,不處理請求 
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 核心:::::通過RequestMappingHandlerAdapter的handle方法,處理請求邏輯,返回ModelView
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                //從viewNameTranslator中獲取預設檢視名,並設定到mv中
                applyDefaultViewName(request, mv);
                //呼叫所有HandlerInterceptor的postHandle()方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            //渲染請求結果
            //從ModelView中獲取View
            //view.render(model,req,resp)渲染
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            //呼叫handerExecutionChain的afterCompletion
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            //呼叫handerExecutionChain的afterCompletion
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // 呼叫handerExecutionChain的applyAfterConcurrentHandlingStarted,實現非同步攔截
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // 如果為附件請求,清理現場
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

注:從DispatcherServlet.properties知道預設的HandlerMapping實現類已經註解了@Deprecated,從註釋頭部得知,建議使用RequestMappingHandlerMapping類。Spring-Webmvc模組中,提供了@EnableWebMvc,它匯入了@Import(DelegatingWebMvcConfiguration.class)DelegatingWebMvcConfiguration類,而DelegatingWebMvcConfiguration類繼承了WebMvcConfigurationSupport,WebMvcConfigurationSupport又注入了RequestMappingHandlerMapping例項,如下面程式碼。所以可以通過註解@EnableWebMvc,向容器注入RequestMappingHandlerMapping例項。

    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
        handlerMapping.setOrder(0);
        handlerMapping.setInterceptors(getInterceptors());
        //注入內容協商管理器handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());

        PathMatchConfigurer configurer = getPathMatchConfigurer();
        if (configurer.isUseSuffixPatternMatch() != null) {
            handlerMapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
        }
        if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
            handlerMapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
        }
        if (configurer.isUseTrailingSlashMatch() != null) {
            handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
        }
        if (configurer.getPathMatcher() != null) {
            handlerMapping.setPathMatcher(configurer.getPathMatcher());
        }
        if (configurer.getUrlPathHelper() != null) {
            handlerMapping.setUrlPathHelper(configurer.getUrlPathHelper());
        }

        return handlerMapping;
    }

注:RequestMappingHandlerMapping間接實現了InitializingBean介面,重寫afterPropertiesSet,程式碼如下

    @Override
    public void afterPropertiesSet() {
        if (this.useRegisteredSuffixPatternMatch) {
            this.fileExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
        }
        //呼叫父類
        super.afterPropertiesSet();
    }
    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();//初始化handlerMethods
    }
    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }

        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));
        //收集所有註解了@Controller或@RequestMapping的方法
        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
                    isHandler(getApplicationContext().getType(beanName))){
                detectHandlerMethods(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

下節將細化介紹DispatcherServlet的getHandler(HttpServletRequest request)方法為入口進行原始碼剖析,將瞭解到

  • 如何獲取到handlerMethod
  • 如何獲取handlerInterceptor
  • 如何建立handlerExecutionChain