Spring MVC組件之HandlerMapping

Spring MVC組件之HandlerMapping

HandlerMapping概述

HandlerMapping組件的作用解析一個個Request請求,并找到相應處理這個Request的Handler。Handler一般可以理解為Controller控制器里的一個方法。

HandlerMapping組件主要做了兩件事件。

  1. 在組件初始化時,會把Request請求和對應的Handler進行注冊,其實就是把Request和對應的Handler以鍵值對的形式存在一個map中。

  2. 解析一個個Request請求,在注冊的map中找相應的handler。

在SpringMvc的源碼中,HandlerMapping定義為一個接口。接口除了定義幾個屬性字段,只定義了一個getHandler方法。

1.png

HandlerMapping類圖

2.png

從以上類圖中可以看出,HandlerMapping組件主要是分了兩個系列。一個系列主要繼承至AbstractHandlerMethodMapping。另一個系列主要繼承至AbstractUrlHandlerMapping。而AbstractHandlerMethodMapping和AbstractUrlHandlerMapping這兩個抽象類又都是繼承至AbstractHandlerMapping。

AbstractHandlerMapping

AbstractHandlerMapping是一個抽象類,它實現(xiàn)了HandlerMapping接口。AbstractHandlerMapping是一個非?;A的類,HandlerMapping的所有子類系列都是繼承自它。AbstractHandlerMapping采用了模板模式進行了整體的設計,各個子類通過覆寫模板方法來實現(xiàn)相應功能。

AbstractHandlerMappin抽象類既然繼承了HandlerMapping接口,它肯定事要實現(xiàn)getHandler方法。在AbstractHandlerMappin類中,具體代碼如下:

  @Override

  @Nullable

  public  final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {

 Object handler = getHandlerInternal(request);

  if (handler == null) {

  handler = getDefaultHandler();

 }

  if (handler == null) {

  return  null;

 }

  // Bean name or resolved handler?

  if (handler  instanceof String) {

 String handlerName = (String) handler;

  handler = obtainApplicationContext().getBean(handlerName);

 }

  // Ensure presence of cached lookupPath for interceptors and others

  if (!ServletRequestPathUtils.*hasCachedPath*(request)) {

 initLookupPath(request);

 }

 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

  if (logger.isTraceEnabled()) {

  logger.trace("Mapped to " + handler);

 }

  else  if (logger.isDebugEnabled() && !DispatcherType.*ASYNC*.equals(request.getDispatcherType())) {

  logger.debug("Mapped to " + executionChain.getHandler());

 }

  if (hasCorsConfigurationSource(handler) || CorsUtils.*isPreFlightRequest*(request)) {

 CorsConfiguration config = getCorsConfiguration(handler, request);

  if (getCorsConfigurationSource() != null) {

 CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);

  config = (globalConfig != null ? globalConfig.combine(config) : config);

 }

  if (config != null) {

  config.validateAllowCredentials();

 }

  executionChain = getCorsHandlerExecutionChain(request, executionChain, config);

 }

  return  executionChain;

}

getHandler方法中要特別注意getHandlerInternal(request)方法,該方法是一個模板方法。在AbstractHandlerMapping類中,getHandlerInternal(request)方法只是一個抽象的方法,沒有做任何的事情。該方法專門預留給AbstractHandlerMapping的子類來覆寫,從而實現(xiàn)自己的業(yè)務邏輯。

AbstractHandlerMapping還繼承自WebApplicationObjectSupport類,并重寫了該父類的initApplicationContext方法。

@Override

 protected  void initApplicationContext()  throws BeansException {

 extendInterceptors(this.interceptors);

 detectMappedInterceptors(this.adaptedInterceptors);

 initInterceptors();

 }

在initApplicationContext方法,定義了三個方法。

  1. extendInterceptors(this.interceptors)

extendInterceptors是一個模板方法,給子類提供了一個修改this.interceptors攔截器的入口。

  1. detectMappedInterceptors(this.adaptedInterceptors)

detectMappedInterceptors方法是將Spring MVC中所有MappedInterceptor類型的bean,添加到this.adaptedInterceptors的集合中。

3.initInterceptors()

initInterceptors是初始化攔截器,將所有的this.interceptors集合中的攔截器包裝后,添加到this.adaptedInterceptors的集合中。

AbstractHandlerMethodMapping系列

AbstractHandlerMethodMapping

AbstractHandlerMethodMapping是一個非常重要的類,AbstractHandlerMethodMapping除了繼承AbstractHandlerMapping抽象類,它還實現(xiàn)了InitializingBean接口。

Handler的注冊

在Spring中如果一個類實現(xiàn)了InitializingBean接口,Spring容器就會在實例化該Bean時,會調用Bean的afterPropertiesSet方法。

AbstractHandlerMethodMapping類中,在覆寫InitializingBean接口的afterPropertiesSet方法時,完成了初始化注冊的工作。這也是HandlerMapping組件的第一步工作,把Request請求和對應的Handler先進行注冊。

AbstractHandlerMethodMapping類的afterPropertiesSet方法具體代碼如下圖所示。

  @Override

  public  void afterPropertiesSet() {

 initHandlerMethods();

 }

可以看出在AbstractHandlerMethodMapping類的afterPropertiesSet方法中,調用了initHandlerMethods()方法??磇nitHandlerMethods()方法的名稱,就知道它其實是完成了初始化的工作。

  protected  void initHandlerMethods() {

  for (String beanName : getCandidateBeanNames()) {

 if (!beanName.startsWith(***SCOPED_TARGET_NAME_PREFIX***)) {

 processCandidateBean(beanName);

 }

 }

 handlerMethodsInitialized(getHandlerMethods());

 }

在initHandlerMethods()方法中,通過getCandidateBeanNames方法,先獲取Spring容器中所有的Bean的名稱。過濾掉名稱以” scopedTarget.”打頭的Bean。通過循環(huán)再把Bean的名稱傳入processCandidateBean(beanName)方法。

processCandidateBean(beanName)方法主要做了三件事情。

  1. 通過Bean的名稱找到Bean對應的類型。

  2. 通過isHandler方法,過濾掉不符合條件的Bean。isHandler方法是一個模板方法,具體邏輯是在子類RequestMappingHandlerMapping中實現(xiàn)的。isHandler方法只會選擇含有@Controller和@RequestMapping注解的bean。

  3. 通過detectHandlerMethods方法,建立request請求和handler之間的對應映射關系。

detectHandlerMethods方法

detectHandlerMethods方法主要做了兩件事情。

1. 使用getMappingForMethod方法,通過handler找到有@RequestMapping注解的方法。在AbstractHandlerMethodMapping類中,getMappingForMethod方法在只是一個抽象方法,具體實現(xiàn)是在子類RequestMappingHandlerMapping類中,實現(xiàn)具體的業(yè)務邏輯。

2. 使用registerHandlerMethod方法,將找到的方法進行注冊。所謂注冊其實就是將找到的HandlerMothed放到一個Map中。如果同一個HandlerMothed進行第二次注冊,就會拋出異常。

Handler的查找

在AbstractHandlerMethodMapping類中,覆寫了父類AbstractHandlerMapping的模板方法getHandlerInternal方法。

  protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {

 String lookupPath = initLookupPath(request);

  this.mappingRegistry.acquireReadLock();

  try {

 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

  return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);

 }

  finally {

  this.mappingRegistry.releaseReadLock();

 }

 }

在getHandlerInternal方法中,主要做了三件事件。

  1. 在initLookupPath方法中,把一個request轉換成一個url.

  2. 再通過request和lookupPath 這兩個參數(shù),使用lookupHandlerMethod方法,找到相應的HandlerMethod。

  3. 在找到HandlerMethod對象的情況下,會調用HandlerMethod的createWithResolvedBean方法。該方法會判斷這個HandlerMethod對象。是不是String 類型的。如果是String 類型則說明他只是一個Bean的名稱,會根據這個Bean的名稱在Spring容器中找到該Bean。根據這個Bean,會創(chuàng)建一個新的HandlerMethod對像返回。

RequestMappingInfoHandlerMapping

RequestMappingInfoHandlerMapping類主要是重寫了父類的getMatchingMapping方法。getMatchingMapping方法會根據當前的請求,返回一個匹配了各種RequestCondition的RequestMappingInfo對象。

SpringMVC會根據這個RequestMappingInfo對象來獲取對應的Handler。

RequestMappingHandlerMapping

Spring MVC容器在初始化HandlerMapping類型的組件時,最終初始化的AbstractHandlerMethodMapping系列組件,就是RequestMappingHandlerMapping。

RequestMappingHandlerMapping主要是重寫了父類的三個方法。

  1. afterPropertiesSet方法

重寫了父類的初始化方法,在afterPropertiesSet方法中,創(chuàng)建了一個BuilderConfiguration類型的對象。然后對BuilderConfiguration對象,進行了屬性的設置。

  1. isHandler方法

主要是用于判斷獲取何種類型的Handler。對Handler起到一個過濾的作用,只取@Controller和@RequestMapping兩種注解類型的Handler。

  1. getMappingForMethod方法

getMappingForMethod方法主要是通過method來創(chuàng)建相應的RequestMappingInfo對象。程序先從method對象上獲取RequestMapping注解。再從RequestMapping注解的信息,來創(chuàng)建“路徑匹配”, ”頭部匹配”, ”請求參數(shù)匹配”, ”可產生MIME匹配”, ”可消費MIME匹配”, ”請求方法匹配”,等RequestCondition接口的實例。最后組合這些RequestCondition接口實例,來創(chuàng)建一個RequestMappingInfo對象。SpringMVC會根據RequestMappingInfo對象來注冊請求和Handler的映射關系。

RequestMappingInfo對象實現(xiàn)了RequestCondition接口。接口RequestCondition是Spring MVC對一個請求匹配條件的概念建模。

AbstractUrlHandlerMapping系列

AbstractUrlHandlerMapping系列從名字就可以看出,主要是處理url和handler的關系。AbstractUrlHandlerMapping類先將url和handler的映射關系存在一個Map。再通過url來獲取相應的handler。

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping類跟AbstractHandlerMethodMapping類一樣,也繼承了AbstractHandlerMapping這個抽象類。所有url跟HandlerMapping相關系列的子類,都是繼承至AbstractUrlHandlerMapping這個父類。

AbstractUrlHandlerMapping同時也實現(xiàn)了MatchableHandlerMapping的接口。MatchableHandlerMapping接口定義了一個用于匹配的match方法。

HandlerMap的注冊

在AbstractUrlHandlerMapping類中,registerHandler這個方法是專門負責對handler進行注冊的。Handler的注冊,其實就是把url和對應的handler存儲到handlerMap這個哈希map中。

  protected  void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {

 Assert.notNull(urlPath, "URL path must not be null");

 Assert.notNull(handler, "Handler object must not be null");

 Object resolvedHandler = handler;

  // Eagerly resolve handler if referencing singleton via name.

  if (!this.lazyInitHandlers && handler  instanceof String) {

 String handlerName = (String) handler;

 ApplicationContext applicationContext = obtainApplicationContext();

  if (applicationContext.isSingleton(handlerName)) {

  resolvedHandler = applicationContext.getBean(handlerName);

 }

 }

 Object mappedHandler = this.handlerMap.get(urlPath);

  if (mappedHandler != null) {

  if (mappedHandler != resolvedHandler) {

  throw  new IllegalStateException(

  "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +

  "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");

 }

 }

  else {

  if (urlPath.equals("/")) {

  if (logger.isTraceEnabled()) {

  logger.trace("Root mapping to " + getHandlerDescription(handler));

 }

 setRootHandler(resolvedHandler);

 }

  else  if (urlPath.equals("/*")) {

  if (logger.isTraceEnabled()) {

  logger.trace("Default mapping to " + getHandlerDescription(handler));

 }

 setDefaultHandler(resolvedHandler);

 }

  else {

  this.handlerMap.put(urlPath, resolvedHandler);

  if (getPatternParser() != null) {

  this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);

 }

  if (logger.isTraceEnabled()) {

  logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));

 }

 }

 }

 }

在registerHandler方法中,主要做了4件事情。

  1. 如果handler不是懶加載的,且handler是字符串類型的。此時就把handler作為一個BeanName,在Spring 容器中獲取這個handler的bean對象。

  2. 對urlPath進行了校驗,如果一個urlPath對應了多個不同的handler,代碼就會拋出異常。

  3. 對特殊的urlPath進行了單獨的處理,對”/”,”/*”分別調用了setRootHandler方法和setDefaultHandler方法進行了特殊的處理。

  4. 在handlerMap中記錄url和handler的對應關系。通過this.handlerMap.put(urlPath, resolvedHandler)這句代碼,把url和handler通過鍵值對的方式存儲到hash散列中。

Handler的查找

在AbstractUrlHandlerMapping類中,具體實現(xiàn)了如何從一個url來獲取相應的的handler。AbstractUrlHandlerMapping類中,重寫了getHandlerInternal方法。通過url來獲取handler的邏輯,就寫在getHandlerInternal方法中。

  protected Object getHandlerInternal(HttpServletRequest request) throws Exception {

 String lookupPath = initLookupPath(request);

 Object handler;

  if (usesPathPatterns()) {

 RequestPath path = ServletRequestPathUtils.*getParsedRequestPath*(request);

  handler = lookupHandler(path, lookupPath, request);

 }

  else {

  handler = lookupHandler(lookupPath, request);

 }

  if (handler == null) {

  // We need to care for the default handler directly, since we need to

  // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.

 Object rawHandler = null;

  if (StringUtils.*matchesCharacter*(lookupPath, '/')) {

  rawHandler = getRootHandler();

 }

  if (rawHandler == null) {

  rawHandler = getDefaultHandler();

 }

  if (rawHandler != null) {

  // Bean name or resolved handler?

  if (rawHandler  instanceof String) {

 String handlerName = (String) rawHandler;

  rawHandler = obtainApplicationContext().getBean(handlerName);

 }

 validateHandler(rawHandler, request);

  handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);

 }

 }

  return  handler;

}

在getHandlerInternal方法中,程序會先根據request獲取到一個url。再通過lookupHandler方法來獲取相應的Handler。

lookupHandler方法

lookupHandler從方法名稱就可以知道,就是通過url來查找對應的Handler。lookupHandler首先會從handlerMap中直接獲取。若找到了Handler,則會直接返回。

若不能直接從handlerMap中獲取,則會使用PathPattern進行模式匹配。如果一個url匹配了多個PathPattern,會對多個PathPattern進行排序,取最優(yōu)的一個。

獲取到了PathPattern后,通過PathPattern從pathPatternHandlerMap獲取Handler。如果Handler為String類型,那么這個Handler是Handle Bean的名稱。再根據這個BeanName在Spring容器中找到相應的Bean。

獲取到Handler后,會使用validateHandler對這個Handler進行校驗。validateHandler是一個模板方法,主要留給子類進行擴展實現(xiàn)。

最后會使用buildPathExposingHandler方法,為這個Handler增加一些攔截器。

buildPathExposingHandler

buildPathExposingHandler方法主要是給Handler注冊了兩個內部的攔截器。他們分別是PathExposingHandlerInterceptor和UriTemplateVariablesHandlerInterceptor攔截器。

AbstractDetectingUrlHandlerMapping

在AbstractDetectingUrlHandlerMapping類中,主要是重寫了父類的initApplicationContext()方法。在initApplicationContext()方法中,調用了detectHandlers()方法。

protected  void detectHandlers() throws BeansException {

 ApplicationContext applicationContext = obtainApplicationContext();

  if (logger.isDebugEnabled()) {

  logger.debug("Looking for URL mappings in application context: " + applicationContext);

 }

 String[] beanNames = (this.detectHandlersInAncestorContexts ?

 BeanFactoryUtils.*beanNamesForTypeIncludingAncestors*(applicationContext, Object.class) :

  applicationContext.getBeanNamesForType(Object.class));

  // Take any bean name that we can determine URLs for.

  for (String beanName : beanNames) {

 String[] urls = determineUrlsForHandler(beanName);

  if (!ObjectUtils.*isEmpty*(urls)) {

  // URL paths found: Let's consider it a handler.

 registerHandler(urls, beanName);

 }

  else {

  if (logger.isDebugEnabled()) {

  logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");

 }

 }

 }

}

在調用了detectHandlers()方法中,主要做了以下幾個步驟。

  1. 獲取Spring 容器中所有bean的名稱。

  2. 循環(huán)遍歷所有bean的名稱,對每一個beanName調用determineUrlsForHandler方法,獲取這個beanName對應的url。

  3. 再調用父類的registerHandler(urls, beanName)方法,對url和handler進行注冊。

determineUrlsForHandler(beanName)方法在AbstractDetectingUrlHandlerMapping類中,只是一個虛方法,專門留給子類來具體實現(xiàn)。

BeanNameUrlHandlerMapping

Spring MVC容器在初始化HandlerMapping類型的組件時,默認初始化AbstractUrlHandlerMapping系列的組件時,初始化的就是BeanNameUrlHandlerMapping組件。

BeanNameUrlHandlerMapping類繼承至AbstractDetectingUrlHandlerMapping這個父類,子類中主要是重寫了determineUrlsForHandler方法。determineUrlsForHandler方法中,主要是篩選了Bean的名稱或者Bean的別名以“/”斜杠開頭的Bean。最后會把Bean的名稱和對應的Bean對象注冊到handlerMap這個HashMap對象中。

SimpleUrlHandlerMapping

SimpleUrlHandlerMapping類繼承至AbstractUrlHandlerMapping這個父類。SimpleUrlHandlerMapping類中有一個Map<String, Object>類型的urlMap屬性。我們可以把url和對應的HandlerMapping的放到這個屬性中。SimpleUrlHandlerMapping類會在registerHandlers(Map<String, Object> urlMap)方法中,遍歷這個urlMap,再調用AbstractUrlHandlerMapping父類的registerHandler(String urlPath, Object handler)方法,對url和HandlerMapping進行注冊。

除了Map<String, Object>類型的urlMap屬性。SimpleUrlHandlerMapping類中也提供了使用Properties來進行url的注冊。通過setMappings(Properties mappings)方法,SimpleUrlHandlerMapping會把mappings合并到urlMap屬性中。再走urlMap屬性的注冊邏輯。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容