天天看點

口述完SpringMVC執行流程,面試官就讓同僚回家等消息了

Srping MVC 執行流程真的是老生常談的話題了,最近同僚小剛出去面試,前面面試官相繼問了幾個 Spring 相關的問題,但當面試官問他,你知道 Srping MVC 的執行流程嗎?小剛娴熟的巴拉巴拉回答完後,面試官就讓他回去等通知了...

Spring MVC 執行流程

Spring MVC 執行流程(圖檔版):

口述完SpringMVC執行流程,面試官就讓同僚回家等消息了

Spring MVC 執行流程(文字版):

  1. 使用者發送請求到前端控制器 DispatcherServlet
  2. DispatcherServlet 控制器接收到請求,然後調用 HandlerMapping 處理器映射器。
  3. HandlerMapping 處理器映射器找到處理請求的 HandlerAdapter 處理器映射器。
  4. DispatcherServlet 調用 HandlerAdapter 處理器擴充卡找到具體的處理器 Controller。
  5. 執行 Controller 進行業務處理。
  6. Controller 執行完傳回 ModelAndView 給 HandlerAdapter。
  7. HandlerAdapter 将 Controller 執行結果 ModelAndView 傳回給 DispatcherServlet。
  8. DispatcherServlet 查詢一個或多個 ViewResoler 視圖解析器,找到 ModelAndView 指定的視圖 View。
  9. ViewReslover 解析後傳回具體 View。
  10. DispatcherServlet 根據View 進行渲染視圖。
  11. DispatcherServlet 響應結果給使用者。

整個流程這樣回答下來應該沒什麼問題,因為無論是書上還是面試題上基本都這麼标注的答案,但巧就巧在,同僚小剛做的項目是前後端分離的項目(履歷中寫的),言外之意就是從第 6 步開始,下面的回答基本就不對了,因為在前後端分離項目中,最終傳回給前端的資料是以 JSON 形式的,是以不存在什麼視圖解析器一說。

這個時候需要變一個答法,就是直接傳回 JSON 資料回去,可以使用 @ResponseBody 注解。

到這基本也明白了為啥小剛答完這個問題,面試官就讓他回去等消息了...

Spring MVC 工作原理

相信大家上方的執行流程都背的滾瓜爛熟了...

不知道大家是否有這種好奇,就是 @RequestMapping 注解的 Url 怎麼就跟 Controller 關聯起來了呢?

不管你好不好奇,接下來我們就假裝帶着這個好奇來看看怎麼就關聯上了。

從上邊的流程我們知道請求的入口是 DispatcherServlet ,那麼我們來看一下這個類:

@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
 
  public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
  public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
  public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
  public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
  public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
  public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
  public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
  public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
  public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";
  public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
  public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
  public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
  public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
  public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP";
  public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP";
  public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER";
  public static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION";
  public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
  private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
  protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
  private static final Properties defaultStrategies;
  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());
    }
  }
 
  /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
  private boolean detectAllHandlerMappings = true;
 
  /** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */
  private boolean detectAllHandlerAdapters = true;
 
  /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
  private boolean detectAllHandlerExceptionResolvers = true;
 
  /** Detect all ViewResolvers or just expect "viewResolver" bean? */
  private boolean detectAllViewResolvers = true;
 
  /** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
  private boolean throwExceptionIfNoHandlerFound = false;
 
  /** Perform cleanup of request attributes after include request? */
  private boolean cleanupAfterInclude = true;
 
  /** MultipartResolver used by this servlet */
  private MultipartResolver multipartResolver;
 
  /** LocaleResolver used by this servlet */
  private LocaleResolver localeResolver;
 
  /** ThemeResolver used by this servlet */
  private ThemeResolver themeResolver;
 
  /** List of HandlerMappings used by this servlet */
  private List<HandlerMapping> handlerMappings;
 
  /** List of HandlerAdapters used by this servlet */
  private List<HandlerAdapter> handlerAdapters;
 
  /** List of HandlerExceptionResolvers used by this servlet */
  private List<HandlerExceptionResolver> handlerExceptionResolvers;
 
  /** RequestToViewNameTranslator used by this servlet */
  private RequestToViewNameTranslator viewNameTranslator;
 
  private FlashMapManager flashMapManager;
 
  /** List of ViewResolvers used by this servlet */
  private List<ViewResolver> viewResolvers;
 
  public DispatcherServlet() {
    super();
  }
 
  public DispatcherServlet(WebApplicationContext webApplicationContext) {
    super(webApplicationContext);
  }
  @Override
  protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
  }
 
  protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
  }
}
           

這個類真的是太長了,實在是沒耐心看下來,以至于不得不精簡了一部分(上方為精簡後的),在這裡面我們可以看到一些熟悉的面孔:

  • HandlerMapping:用于handlers映射請求和一系列的對于攔截器的前處理和後處理,大部分用@Controller注解。
  • HandlerAdapter:幫助DispatcherServlet處理映射請求處理程式的擴充卡,而不用考慮實際調用的是 哪個處理程式
  • ViewResolver:根據實際配置解析實際的View類型。
  • ThemeResolver:解決Web應用程式可以使用的主題,例如提供個性化布局。
  • MultipartResolver:解析多部分請求,以支援從HTML表單上傳檔案。

既然 HandlerMapping 是用來映射請求的,然後我們就繼續朝着這個方向走,在上方 DispatcherServlet 中找到 HandlerMapping 相關的代碼,然後我們就找到了這個集合:

private List<HandlerMapping> handlerMappings;

接着看看在哪給這個 handlerMappings 集合指派的,然後就找到了如下方法:

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
            HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException var3) {
        }
    }

    if (this.handlerMappings == null) {
        this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No HandlerMappings declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
        }
    }

}
           

簡單分析一下 initHandlerMappings() 這段方法,首先我們需要先了解 HandlerMapping 其實是一個接口類,然後這個接口類就定義了一個方法 getHandler() ,這個方法也很簡單,就是根據請求的 request,擷取 HandlerExecutionChain 對象:

public interface HandlerMapping {
    String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
    String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
    String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;
}
           

Spring MVC 提供了許多 HandlerMapping 的實作,我們可以點進去看看這個 HandlerMapping 有多少實作:

口述完SpringMVC執行流程,面試官就讓同僚回家等消息了

大約有19個子類實作了 HandlerMapping 接口,預設使用的是 BeanNameUrlHandlerMapping(可以根據 Bean 的 name 屬性映射到 URL 中)。我們再回到 initHandlerMappings() 方法,我們可以看到在指派的時候有個

if (this.detectAllHandlerMappings)

屬性的判斷,這個屬性是用來标記是否隻期望 Srping MVC 隻加載指定的 HandlerMappring 的,如果修改為 fasle ,Spring MVC 就隻會查找名為 “handlerMapping” 的 bean,并作為目前系統的唯一的 HandlerMapping。

而正常情況為 true 時,就會加載所有 HandlerMapping 的實作類,加載之後還有個使用優先級的排序過程

AnnotationAwareOrderComparator.sort(this.handlerMappings);

,優先使用高優先級的 HandlerMapping。

看到這,可能就會覺得,既然 HandlerMapping 有這麼多的實作類,但是具體的某個實作類又是怎麼初始化的呢?畢竟 HandlerMapping 的作用可是用來映射請求的,還沒看到具體實作過程呢...

是以到這個時候,很顯然得進行下去嘛,是以不得不找一個實作類來看看是如何具體初始化的,但是具體找哪個分析呢?在決定分析哪個之前,我們先了解一下 HadlerMapping 接口的繼承體系。

HandlerMapping接口繼承體系

口述完SpringMVC執行流程,面試官就讓同僚回家等消息了

這個體系比較龐大,我們着重看我标注的紅框跟藍筐内的内容,通過1、2我們可以将 HadlerMapping 分為兩個體系,一是繼承自 AbstractUrlHandlerMapping,二是繼承 AbstractHandlerMethodMapping,因為随着版本的問題(本文以Spring4.3.13為例),其中 AbstractUrlHandlerMapping 在目前大部分的項目已經很少使用到了,是以接下來我們就重點分析AbstractHandlerMethodMapping,他就是我們經常使用的

@RequestMapping

注解會使用到的方式。

在分析 AbstractHandlerMethodMapping 之前,我們先分析下這個類的父類 AbstractHandlerMapping,不然有些方法就很懵逼。

1、AbstractHandlerMapping概述

已經不想貼這個類的完整代碼了,感興趣的小夥伴自己點進去看看吧,簡單說一下定義,AbstractHandlerMapping 是 HandlerMapping 的抽象實作,采用模闆模式設計了 HandlerMapping 的整體架構。其定義了getHandlerInternal() 方法,該方法就是一個模版方法,根據 request 來擷取相應的 Handler,由它的兩個子類來具體實作該方法。然後再根據 request 來擷取相應的 interceptors,整合從子類擷取的 Handler,組成 HandlerExecutionChain 對象傳回。

@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest var1) throws Exception;
           

2、AbstractHandlerMapping初始化

AbstractHandlerMapping 繼承了 WebApplicationObjectSupport(擷取Spring的ApplicationContext方式之一,可以看做java類擷取Spring容器的Bean),初始化時會自動調用模闆方法 initApplicationContext,具體如下:

@Override
protected void initApplicationContext() throws BeansException {
    // 模闆方法,暫無子類實作
    extendInterceptors(this.interceptors);
    // 從容器中擷取實作了MappedInterceptor接口的對象,添加到adaptedInterceptors清單中
    detectMappedInterceptors(this.adaptedInterceptors);
     // 将interceptors(由子類添加)中的對象封裝後添加到adaptedInterceptors清單中
    initInterceptors();
}
           

3、AbstractHandlerMapping的使用

AbstractHandlerMapping 繼承自 HandlerMapping ,實作了其 getHandler() 方法,我們上邊也提到該方法就是根據請求的 request,擷取 HandlerExecutionChain 對象,我們來看一下 AbstractHandlerMapping 中的實作:

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 模闆模式方法,具體由子類實作
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }
    // 根據request從adaptedInterceptors中選擇比對的interceptors,與handler一起封裝成HandlerExecutionChain對象
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    return executionChain;
}
           

到這就是交給子類去完成了,分别是 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping,接下來我們隻着重分析 AbstractHandlerMethodMapping 類。

AbstractHandlerMethodMapping

首先實作了父類 getHandlerInternal() 方法:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 獲得請求的路徑
    String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
    request.setAttribute(LOOKUP_PATH, lookupPath);
    // 獲得讀鎖
    this.mappingRegistry.acquireReadLock();

    HandlerMethod var4;
    try {
      // 獲得 HandlerMethod 對象
        HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
        var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
    } finally {
      // 釋放鎖
        this.mappingRegistry.releaseReadLock();
    }

    return var4;
}
           

重點在于 lookupHandlerMethod() 方法,如下為詳細代碼:

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  // Match數組,用于存儲比對上目前請求的結果
    List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
    // 優先級,基于直接 URL 的 Mapprings 進行比對
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        this.addMatchingMappings(directPathMatches, matches, request);
    }
  // 掃描系統資料庫的 Mappings 進行比對
    if (matches.isEmpty()) {
        this.addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }
    // 如果比對到,則擷取最佳比對的 Match 對象的 handlerMethod 屬性
    if (!matches.isEmpty()) {
      // 建立 MathComparator 對象
        Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));
        // 排序 matches 結果
        matches.sort(comparator);
        // 擷取首個 Match 對象
        AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
        // 處理存在多個 Match 對象的情況
        if (matches.size() > 1) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(matches.size() + " matching mappings: " + matches);
            }

            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
      // 比較 bestMatch 和 secondBestMatch,如果相等則抛出 IllegalStateException 異常
            AbstractHandlerMethodMapping<T>.Match secondBestMatch = (AbstractHandlerMethodMapping.Match)matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                String uri = request.getRequestURI();
                throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
            }
        }

        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        // 處理首個 Match 對象
        this.handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    } else {
      // 如果比對不到,則處理不比對的情況
        return this.handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}
           

在分析 lookupHandlerMethod() 方法的整體思路之前,我們還得知曉 AbstractHandlerMethodMapping 的内部類 MappingRegistry。MappingRegistry 類中定義了兩個比較重要的變量,

Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>()

MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>()

。其中 mappingLookup 變量儲存了 RequestMappingInfo 與 HandlerMethod 的一一對應關系,而 urlLookup 變量則儲存了 url 與 RequestMappingInfo 的對應關系,需要注意的是 MultiValueMap 類型的變量是可以一個 key 對應多個 value 的,也就是說 urlLookup 變量中,一個 url 可能對應多個 RequestMappingInfo。

有了這個概念後我們再來看 lookupHandlerMethod() 方法,整個過程結果就是,根據入參 lookupPath(可以看成是 url),從 request 中擷取到一個最符合的 RequestMappingInfo 對象,然後根據該對象再去擷取到 HandlerMethod 對象傳回給父類 AbstractHandlerMapping。

其實在 lookupHandlerMethod() 方法之前,還有一個 mappingLookup、urlLookup 等參數初始化的過程,AbstractHandlerMethodMapping 實作了 InitializingBean 接口,當 Spring 容器啟動時會自動調用其 afterPropertiesSet() 方法,來完成 handlerMethod 的注冊操作,但是該方法最終又交給 initHandlerMethods() 方法完成具體的初始化:

protected void initHandlerMethods() {
    if (logger.isDebugEnabled()) {
        logger.debug("Looking for request mappings in application context: " + getApplicationContext());
    }
    // 從springMVC容器中擷取所有的beanNam
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().getBeanNamesForType(Object.class));
  // 注冊從容器中擷取的beanName
    for (String beanName : beanNames) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                beanType = getApplicationContext().getType(beanName);
            }
            catch (Throwable ex) {
                // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                }
            }
            if (beanType != null && isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }
    }
    // 模闆方法,暫無子類實作
    handlerMethodsInitialized(getHandlerMethods());
}
           

簡單來說這個方法就是進行 HandlerMethod 的注冊操作,首先從 Spring MVC 的容器中擷取所有的 beanName 然後進行過濾處理,注冊 URL 和實作方法 HandlerMethod 的對應關系。

在 initHandlerMethods() 方法中我們主要關注兩個方法 isHandler(beanType) 與 detectHandlerMethods(beanName) ,其中 isHandler(beanType) 方法由子類 RequestMappingHandlerMapping 實作,用于對 bean 進行過濾,判斷是否包含 Controller 或者 RequestMapping 注解。

@Override
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
           

而 detectHandlerMethods(beanName) 方法,則根據篩選出的 bean,進行一系列的注冊,最終實作是在 registerHandlerMethod() 方法:

protected void detectHandlerMethods(final Object handler) {
    Class<?> handlerType = (handler instanceof String ?
            getApplicationContext().getType((String) handler) : handler.getClass());
    // CGLib動态代理的特殊處理
    final Class<?> userType = ClassUtils.getUserClass(handlerType);
    // methods包含了bean中所有符合條件的method與相關的RequestMappingInfo鍵值對
    Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            new MethodIntrospector.MetadataLookup<T>() {
                @Override
                public T inspect(Method method) {
                    try {
                        // 如果method有@RequestMapping注解,則傳回由注解得到的封裝好的RequestMappingInfo對象,否則傳回null
                        return getMappingForMethod(method, userType);
                    }
                    catch (Throwable ex) {
                        throw new IllegalStateException("Invalid mapping on handler class [" +
                                userType.getName() + "]: " + method, ex);
                    }
                }
            });

    if (logger.isDebugEnabled()) {
        logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
    }
    for (Map.Entry<Method, T> entry : methods.entrySet()) {
        Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
        T mapping = entry.getValue();
        // 注冊 beanName,Method及建立的RequestMappingInfo之間的關系
        registerHandlerMethod(handler, invocableMethod, mapping);
    }
}
           

detectHandlerMethods() 方法中的 getMappingForMethod() 方法是在子類 RequestMappingHandlerMapping 中實作的,具體實作就是建立一個 RequestMappingInfo:

/**
 * Uses method and type-level @{@link RequestMapping} annotations to create
 * the RequestMappingInfo.
 * @return the created RequestMappingInfo, or {@code null} if the method
 * does not have a {@code @RequestMapping} annotation.
 * @see #getCustomMethodCondition(Method)
 * @see #getCustomTypeCondition(Class)
 */
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
 RequestMappingInfo info = createRequestMappingInfo(method);
 if (info != null) {
  RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
  if (typeInfo != null) {
   info = typeInfo.combine(info);
  }
 }
 return info;
}

/**
 * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
 * supplying the appropriate custom {@link RequestCondition} depending on whether
 * the supplied {@code annotatedElement} is a class or method.
 * @see #getCustomTypeCondition(Class)
 * @see #getCustomMethodCondition(Method)
 */
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
 RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
 RequestCondition<?> condition = (element instanceof Class ?
   getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
 return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
           

detectHandlerMethods() 方法中的 registerHandlerMethod() 方法的操作是注冊 beanName,Method 及建立的 RequestMappingInfo 之間的關系:

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
 this.mappingRegistry.register(mapping, handler, method);
}
           

到這就簡單實作了将 url 和 HandlerMethod 的對應關系注冊到 mappingRegistry 中了。

private final AbstractHandlerMethodMapping<T>.MappingRegistry mappingRegistry = new AbstractHandlerMethodMapping.MappingRegistry();
           

最後總結

在描述 Spring MVC 執行流程的時候需要注意,隻有在以前使用 jsp、themlef 等模闆引擎的時候,我們會把前端界面放在後端的工程裡,然後在 controller 執行完業務邏輯之後會傳回一個界面模版名稱,也就是ModelAndView,然後 DispatherServlet 将 ModelAndView 傳遞給 ViewReslover 視圖解析器,視圖解析器解析後傳回具體的 View,然後對 html 界面做一個渲染。

如果傳回的是一個json串的話,也就是前後端分離的項目,那麼隻需要傳回json資料即可。

Spring MVC 使用 HandlerMappring 來找到并儲存 URL 請求和處理函數間的 mapping 關系。

以 AbstractHandlerMethodMapping 為例來具體看 HandlerMapping 的作用,首先拿到容器裡所有的 bean,然後根據一定的規則篩選出 Handler,然後儲存在 map 中,具體的篩選工作在子類中進行:篩選的邏輯就是檢查類前是否存在 @Controller 或者 @RequestMapping 注解 ,然後在 detectHandlerMethods() 方法中負責将 Handler 儲存在 map 中。

1、使用者發送請求時會先從 DispathcherServler 的 doService 方法開始,在該方法中會将 ApplicationContext、localeResolver、themeResolver 等對象添加到 request 中,緊接着就是調用 doDispatch 方法。

2、進入 doDispatch 方法後首先會檢查該請求是否是檔案上傳的請求,(校驗的規則是是否是post并且contenttType是否為multipart/為字首)即調用的是 checkMultipart 方法,如果是的話将 request 包裝成 MultipartHttpServletRequest。

3、然後調用 getHandler 方法來比對每個 HandlerMapping 對象,如果比對成功會傳回這個 Handle 的處理鍊 HandlerExecutionChain 對象,在擷取該對象的内部其實也擷取我們自定定義的攔截器,并執行了其中的方法。

4、執行攔截器的 preHandle 方法,如果傳回 false 執行 afterCompletion 方法并了解傳回

5、通過上述擷取到了 HandlerExecutionChain 對象,通過該對象的 getHandler() 方法獲得一個 object 通過 HandlerAdapter 進行封裝得到 HandlerAdapter 對象。

6、該對象調用 handle 方法來執行 Controller 中的方法,然後根據類型傳回不同的結果(如JSON、ModelAndView),該對象如果傳回一個 ModelAndView 給 DispatcherServlet。

7、DispatcherServlet 借助 ViewResolver 完成邏輯試圖名到真實視圖對象的解析,得到 View 後 DispatcherServlet 使用這個 View 對 ModelAndView 中的模型資料進行視圖渲染。

工作原理寫着寫着發現挺亂的,整體描述的不是特别清楚,也很枯燥,之後通過閱讀 Spring MVC 整體源碼後再補充。部落格園持續更新,歡迎關注,未來,我們一起成長。

本文首發于部落格園:https://www.cnblogs.com/niceyoo/p/13663133.html

繼續閱讀