1. 程式人生 > 實用技巧 >口述完SpringMVC執行流程,面試官就讓同事回家等訊息了

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

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

Spring MVC 執行流程

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

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 有多少實現:

大約有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介面繼承體系

這個體系比較龐大,我們著重看我標註的紅框跟藍筐內的內容,通過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 執行流程

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

如果返回的是一個json串的話,也就是前後端分離的專案,那麼只需要返回json資料即可。

Spring MVC 工作原理

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