Spring系列(六) Spring Web MVC 應用構建分析

DispatcherServlet

DispatcherServlet 是Spring MVC的前端控制器名稱, 用戶的請求到達這裏進行集中處理, 在Spring MVC中, 它的做用是爲不一樣請求匹配對應的處理器, 將結果傳遞給視圖解析器最終呈現給客戶端.前端

前端控制器模式(Front Controller Pattern)是用來提供一個集中的請求處理機制,全部的請求都將由一個單一的處理程序處理。該處理程序能夠作認證/受權/記錄日誌,或者跟蹤請求,而後把請求傳給相應的處理程序。java

Servlet WebApplicationContext 和 Root WebApplicationContext

Spring MVC 存在兩個應用上下文, 分別爲Servlet WebApplicationContext和Root WebApplicationContext. 他們分別初始化不一樣類型的bean.ios

下圖來自Spring官方文檔web

在DispatcherServlet啓動的時候, 它會建立Spring上下文Servlet WebApplicationContext, 其中包含Web相關的Controller,ViewResolver,HandlerMapping等.spring

另一個上下文Root WebApplicationContext是由ContextLoaderListener建立的, 包含除了Web組件外的其餘bean, 好比包含業務邏輯的Service, 還有數據庫相關的組件等.數據庫

代碼(JavaConfig方式的配置代碼)

下面是用JavaConfig方式實現的配置代碼, 咱們先搭建好一個Spring MVC 項目,而後結合源碼分析Spring如何註冊DispatcherServlet實例的.mvc

// 繼承AbstractAnnotationConfigDispatcherServletInitializer並重寫其中的三個方法
public class MvcWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    // 指定Root上下文的配置類
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ RootConfig.class };
    }

    // 指定Web上下文的配置類
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{ WebConfig.class };
    }

    // url映射
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

經過重寫AbstractAnnotationConfigDispatcherServletInitializer的三個方法完成配置, WebConfig用來配置Web組件, RootConfig用來配置非Web組件.app

@EnableWebMvc // 啓用MVC
@ComponentScan(basePackages = {"com.xlx.mvc.web"}) // 啓用組件掃描,只掃描web相關的組件
@Configuration
public class WebConfig implements WebMvcConfigurer {

    // 視圖解析器,jsp
    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return  resolver;
    }

    // 重寫以啓用默認的處理器, 用來處理靜態資源
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
        configurer.enable();
    }

}

@Configuration
@ComponentScan(basePackages = {"com.xlx.mvc"},  excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = EnableWebMvc.class)
}) // 掃描包, 但排除EnableWebMvc註解的類
public class RootConfig {

}

源碼分析

Servlet 3.0 旨在支持基於代碼的方式配置Servlet容器, 當3.0兼容的servlet容器啓動的時候會在ClassPath查找並調用實現了接口ServletContainerInitializer的類的onStartup()方法, Spring中提供了這個接口的一個實現類SpringServletContainerInitializer. 其啓動方法的代碼以下:webapp

@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
        throws ServletException {

    List<WebApplicationInitializer> initializers = new LinkedList<>();
    // 應用中WebApplicationInitializer的bean生成到一個列表中.
    if (webAppInitializerClasses != null) {
        for (Class<?> waiClass : webAppInitializerClasses) {
            if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                    WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                try {
                    initializers.add((WebApplicationInitializer)
                            ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                }
                catch (Throwable ex) {
                    throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                }
            }
        }
    }

    if (initializers.isEmpty()) {
        servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        return;
    }

    servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
    AnnotationAwareOrderComparator.sort(initializers);

    // 遍歷全部WebApplicationInitializer, 並調用其onStartup方法
    for (WebApplicationInitializer initializer : initializers) {
        initializer.onStartup(servletContext);
    }
}

在上面方法的最後, 能夠看到其將控制權交給WebApplicationInitializer的實例並遍歷調用了onStartup()方法, 而咱們定義的類MvcWebAppInitializer 就是它的子類. 完整的繼承關係爲jsp

WebApplicationInitializer <--
AbstractContextLoaderInitializer <--
AbstractDispatcherServletInitializer <--
AbstractAnnotationConfigDispatcherServletInitializer <--
MvcWebAppInitializer

在類 AbstractDispatcherServletInitializer 中實現了onStartup()方法, 最終調用registerDispatcherServlet()方法完成註冊, 兩個方法的代碼以下:

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

protected void registerDispatcherServlet(ServletContext servletContext) {
    // 獲取Sevlet名稱, 這個方法返回了默認值"dispatcher"
    String servletName = getServletName();
    Assert.hasLength(servletName, "getServletName() must not return null or empty");

    // 此處調用的方法是抽象方法, 由子類AbstractAnnotationConfigDispatcherServletInitializer實現, 其最終調用了自定義類的getServletConfigClasses()方法獲取配置信息(源碼附在本段後面). 用來生成Servlet上下文.
    WebApplicationContext servletAppContext = createServletApplicationContext();
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

    // 生成dispatcherServlet實例
    FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

    // 註冊DispatcherServlet
    ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    if (registration == null) {
        throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
                "Check if there is another servlet registered under the same name.");
    }

    registration.setLoadOnStartup(1);
    registration.addMapping(getServletMappings());
    registration.setAsyncSupported(isAsyncSupported());

    Filter[] filters = getServletFilters();
    if (!ObjectUtils.isEmpty(filters)) {
        for (Filter filter : filters) {
            registerServletFilter(servletContext, filter);
        }
    }

    customizeRegistration(registration);
}

下面附讀取Servlet配置類的代碼: 類AbstractAnnotationConfigDispatcherServletInitializer實現了createServletApplicationContext(), 能夠看到代碼中調用了方法getServletConfigClasses(), 這是個抽象方法, 聲明爲protected abstract Class<?>[] getServletConfigClasses();. 最終的實現正是在咱們自定義的子類MvcWebAppInitializer中.

@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    // 讀取配置類
    Class<?>[] configClasses = getServletConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        context.register(configClasses);
    }
    return context;
}

上面完成了DispatcherServlet的註冊和啓動, 接下來能夠定義Controller了.

請求映射

在此以前須要瞭解下關於URL映射的Servlet規範, 注意這是Servlet的規範, 固然也適用於DispatcherServlet, 代碼中咱們爲DispatcherServlet映射爲"/", 規範中"/"爲使用"default"Servlet, 也就意味着全部的請求默認經過DispatcherServlet處理.

爲了處理靜態資源, 在WebConfig中覆蓋了方法configureDefaultServletHandling()已啓用靜態資源處理器DefaultServletHttpRequestHandler, 它的優先級是最低, 這意味着在匹配不到其餘handler的時候,servlet會將請求交給這個handler處理.

規則按順序執行,匹配到就直接返回.

  1. 精確匹配, url徹底與模式匹配
  2. 最長路徑匹配, 查找模式中路徑最長的匹配項, 例如/user/list/1匹配模式/user/list/, 而不是/user/
  3. 擴展名匹配
  4. 默認Servlet

代碼

@Controller
@RequestMapping(value = "/home")
public class HomeController {
    @RequestMapping(value = "/default",method = RequestMethod.GET)
    public String home(){
        return "home";
    }
}

源碼分析

咱們的Controller以註解(@RequestMapping,@GetMapping等)方式定義, RequestMappingHandlerMapping用來生成請求url與處理方法的映射關係(mapping),這個mapping最終是由DispatcherServlet調用找到匹配到url對應的controller方法並調用.

經過查看Spring的bean依賴關係圖(找到類WebConfig, Ctrl+Alt+U並選spring beans dependency)能夠找到RequestMappingHandlerMapping生成的線索.

簡化的關係圖以下:

能夠看到WebmvcConfigurationSupport中有個@Bean註解的方法生成RequestMappingHandlerMapping的實例, 而WebmvcConfigurationSupport繼承了DelegatingWebMvcConfiguration, 後者是由@EnableWebMvc註解導入.

/**
    * 
    * 返回排序爲0的RequestMappingHandlerMapping實例bean, 用來處理註解方式的Controller請求.
    */
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
    // 順序爲0, 順便提一句, 靜態資源的處理器Handler的順序爲Integer.Max
    mapping.setOrder(0);
    mapping.setInterceptors(getInterceptors());
    mapping.setContentNegotiationManager(mvcContentNegotiationManager());
    mapping.setCorsConfigurations(getCorsConfigurations());

    PathMatchConfigurer configurer = getPathMatchConfigurer();

    Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
    if (useSuffixPatternMatch != null) {
        mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
    }
    Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
    if (useRegisteredSuffixPatternMatch != null) {
        mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
    }
    Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
    if (useTrailingSlashMatch != null) {
        mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
    }

    UrlPathHelper pathHelper = configurer.getUrlPathHelper();
    if (pathHelper != null) {
        mapping.setUrlPathHelper(pathHelper);
    }
    PathMatcher pathMatcher = configurer.getPathMatcher();
    if (pathMatcher != null) {
        mapping.setPathMatcher(pathMatcher);
    }
    Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
    if (pathPrefixes != null) {
        mapping.setPathPrefixes(pathPrefixes);
    }

    return mapping;
}

好了, 如今有了DispatcherServlet, 而且有了能夠處理映射關係的RequestMappingHandlerMapping, 接下來再看下當請求到達時, DispatcherServlet 如何爲Url找到對應的Handler方法.

DispatcherServlet中定義了處理請求的doService()方法, 最終這個方法委託doDispatch()處理請求, 特別注意中文註釋的幾個語句, 除此以外, 這個方法還提供了生命週期的一些處理工做.

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 {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 獲取當前請求對應的handler
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // 獲取當前請求對應handler的適配器
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 最終調用Handler的方法
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

上面代碼中, 重點關注getHandler方法.

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

能夠看到請求所需的handler是取自實例變量this.handlerMappings,接下來順藤摸瓜, 看這個變量是什麼時候初始化的.經過引用, 咱們查找到了下面方法.

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    if (this.detectAllHandlerMappings) {
        // 找到上下文中的全部HandlerMapping, 包括祖先上下文
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // HandlerMapping排序
            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.  
            // 這個註釋...
        }
    }

    // 保證至少要有一個HandlerMapping.
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

整理下調用關係: DispatcherServlet initHandlerMappings <-- initStrategies <-- onRefresh <--
FrameworkServlet initWebApplicationContext <-- initServletBean <--
HttpServletBean init <--
GenericServlet init(ServletConfig config)
最後的GenericServlet是servlet Api的.

Spring Boot 中的DispatcherServlet

Spring Boot微服務中的DispatcherServlet裝配, 由於其通常使用內置的Servlet容器, 是經過DispatcherServletAutoConfiguration來完成的. 下面是生成DispatcherServlet bean的代碼, 這個bean在內部靜態類DispatcherServletConfiguration中.

@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
    DispatcherServlet dispatcherServlet = new DispatcherServlet();
    dispatcherServlet.setDispatchOptionsRequest(
            this.webMvcProperties.isDispatchOptionsRequest());
    dispatcherServlet.setDispatchTraceRequest(
            this.webMvcProperties.isDispatchTraceRequest());
    dispatcherServlet.setThrowExceptionIfNoHandlerFound(
            this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
    return dispatcherServlet;
}

上面咱們經過註解方式構建了一個MVC應用程序, 而且經過源碼分析其構建原理, 其中Spring使用的前端控制器實現類是DispatcherServlet, 其在Servlet容器啓動的時候實例化, 並初始化容器中的Handler處理器. 當請求到達DispatcherServlet時會調用其doDispatcher()方法選擇最合適的處理器. 最後咱們掃了一眼Spring Boot的自動裝配DispatcherServlet方式.

相關文章
相關標籤/搜索