Spring MVC源碼解析之組件解析:HandlerMapping和HandlerAdapter實現原理

在這裏插入圖片描述

介紹

3月份的時候由於疫情被裁員,換了一家公司,結果來了這個公司一看代碼就暈了,我熟悉的@RequestMapping註解哪去了?不用@RequestMapping註解怎麼做映射啊?然後看了一波文檔,原來映射規則是這樣做的,有如下一個Controller類

@View
public class UserApi {
	
	public String index() {
		return "index";
	}
	
	public String test() {
		return "test";
	}
}

用@View註解來表明這是一個Controller類
當訪問http://userApi/index.json的時候調用的是UserApi類的index方法
當訪問http://userApi/test.json的時候調用的是UserApi類的test方法

即映射規則是類名+方法名+.json,姿勢確實夠騷。後續我寫個demo給大家演示一下是怎麼做到的。先來分析一下Spring MVC原生的映射規則是怎麼做到的,搞懂了Spring MVC原生的映射規則,再騷的映射規則照樣能看懂。

先來回憶一下Spring MVC的執行過程
在這裏插入圖片描述

HandlerMapping是根據請求的url找到對應的handler(你暫且可以認爲你寫的controller類)
HandlerAdapter則是根據找到的handler執行對應的方法,然後返回ModelAndView

Spring MVC將Handler的查找和執行分開了,你覺得哪個不好用,就把它替換一下

Handler的三種實現方式

前面之所以說Handler,是因爲在Spring MVC中,Handler常見的實現方式有三種,雖然一般我們只用@RequestMapping註解

實現Controller接口

@Component("/index")
public class IndexController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		response.getWriter().write("IndexController");
		return null;
	}
}

訪問http://localhost:8080/index,頁面輸出IndexController,這裏需要說明的有2點

  1. 當Handler放回的ModelAndView爲null時,後續ViewResolver查找View,View進行渲染的過程會被省略
  2. @Component註解的value值必須以/開頭,後續會說原因

實現HttpRequestHandler接口

@Component("/address")
public class AddressController implements HttpRequestHandler {
	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.getWriter().write("AddressController");
	}
}

訪問http://localhost:8080/address,頁面輸出AddressController

使用@RequestMapping註解

這種方式大家應該很熟悉了,就不再介紹了

HandlerMapping

HandlerMapping接口定義如下

public interface HandlerMapping {

	// 根據請求獲取HandlerExecutionChain
	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

HandlerExecutionChain的定義也很簡單,是對Handler和這個Handler執行前後要執行的攔截器的封裝

public class HandlerExecutionChain {

	private final Object handler;
	
	@Nullable
	private List<HandlerInterceptor> interceptorList;

}

DispatcherServlet有一個成員變量

private List<HandlerMapping> handlerMappings;

當通過請求找Handler時,會依次調用handlerMappings的handler方法,找到第一個不爲null的handler則返回,繼續後面的流程,如果遍歷完handlerMappings,handler還爲null,則報404的錯誤。

Spring MVC有三種映射策略

映射策略 實現類
簡單url映射 SimpleUrlHandlerMapping
BeanName映射 BeanNameUrlHandlerMapping
@RequestMapping映射 RequestMappingHandlerMapping

各自的實現我就不放源碼了,說一下主要思路,思路理解了,追着看源碼就很容易理解了

BeanNameUrlHandlerMapping

上面演示Handler的三種寫法的時候已經演示了BeanNameUrlHandlerMapping的作用了@Componet註解的值和請求的url相同,這種映射關係還挺簡單的哈,當然支持統配符哈

註冊

在Spring啓動過程中,會拿到所有以/開頭的BeanName,並註冊到AbstractUrlHandlerMapping類的成員變量handlerMap 中,註冊的時候key重複會報異常

// AbstractUrlHandlerMapping
Map<String, Object> handlerMap = new LinkedHashMap<>();

其中key爲@Component的value值,value爲@Component修飾的類

查找

查找的時候分爲如下幾步,因爲要考慮統配符的存在,所以不可能是簡單的get

  1. 先直接從handlerMap中,如果不爲空則直接返回
  2. 遍歷handlerMap,調用AntPathMatcher的匹配方法,看請求的路徑和註冊的路徑是否有匹配的。如果有多個匹配,則對匹配的路徑進行排序。選出最優的,返回對應的Handler
  3. 如果還是沒有找到,則返回null

這個查找的邏輯我舉個例子

@Test
public void test1() {
	AntPathMatcher pathMatcher = new AntPathMatcher();
	Assert.assertTrue(pathMatcher.match("index/user", "index/user"));
	Assert.assertTrue(pathMatcher.match("index/**", "index/product/a"));
	Assert.assertTrue(pathMatcher.match("index/**/a", "index/product/a"));
}

如果有如下3個handler

@Component("/index/user")
public class AController implements Controller
@Component("/index/**")
public class BController implements Controller
@Component("/index/**/a")
public class CController implements Controller

則初始化完後handlerMap爲

"/index/user" -> AController
"/index/**" -> BController 
"/index/**/a" -> CController 

當訪問/index/user,能直接從map中取出AController然後返回

當訪問index/product/a,直接從map中拿不到,就開始遍歷key做路徑匹配,結果發現有2個路徑index/**和/index/**a匹配

因爲有2個路徑符合,所以排序,排序後得到的最優路徑爲index/**/a,取出CController,然後執行

SimpleUrlHandlerMapping

這個其實和BeanNameUrlHandlerMapping差不多,只是需要Handler對應的路徑,而不是把BeanName作爲路徑

還是以上面的IndexController和AddressController爲例,不用@Component指定BeanName,則默認爲類名,首字母小寫

@Component
public class IndexController implements Controller
@Component
public class AddressController implements HttpRequestHandler

增加如下配置類

@Configuration
public class HandlerMappingConfig {

	@Autowired
	private AddressController addressController;

	@Bean
	public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
		SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
		Map<String, Object> urlMap = new HashMap<>();
		urlMap.put("/indexV2", "indexController");
		urlMap.put("/addressV2", addressController);
		mapping.setUrlMap(urlMap);
		return mapping;
	}
}

訪問http://localhost:8080/indexV2,輸出IndexController
訪問http://localhost:8080/addressV2,輸出AddressController

註冊

註冊的邏輯和BeanNameUrlHandlerMapping相似,也是將映射關係存在AbstractUrlHandlerMapping類的成員變量handlerMap 中

// AbstractUrlHandlerMapping
Map<String, Object> handlerMap = new LinkedHashMap<>();

key爲SimpleUrlHandlerMapping的urlMap中指定的路徑,value爲urlMap中指定的Handler,當然如果urlMap中指定的Handler爲一個String,則會從容器中找到相應的實現類註冊進去

查找

查找的邏輯和BeanNameUrlHandlerMapping的邏輯一樣,因爲2個類的映射關係都存在
AbstractUrlHandlerMapping中,並且各自沒有重新查找的邏輯

在這裏插入圖片描述

RequestMappingHandlerMapping

@RequestMapping的對應的RequestMappingHandlerMapping和RequestMappingHandlerAdapter應該是Spring MVC中最複雜的部分了。因爲RequestMappingHandlerMapping和RequestMappingHandlerAdapter各成體系,包含了大量組件來協同工作,單開一篇來分享把。這篇就只分享映射關係的註冊,查找過程

註冊

之前的映射關係,是直接存在Map中,而RequestMappingHandlerMapping的映射關係是存在AbstractHandlerMethodMapping的內部類MappingRegistry的成員變量中的

class MappingRegistry {

	// 從RequestMappingInfoHandlerMapping類上可以看到T爲RequestMappingInfo
	// RequestMappingInfo -> HandlerMethod
	private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

	// 不包含通配符的url -> List<RequestMappingInfo>
	// 這裏爲什麼是一個List呢?因爲一個url有可能對應多個方法
	// 即這些方法的@RequestMapping註解path屬性一樣,但是其他屬性不一樣
	private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
}

我只放2個分析用到的屬性,其餘的屬性就不分析了

  1. spring容器在啓動的時候,會拿到所有的bean,判斷這個bean上是否有Controller或者RequestMapping註解,如果有則執行後面的步驟
  2. 解析類上的@RequestMapping註解,將其信息封裝爲RequestMappingInfo
  3. 將RequestMappingInfo及其對應的HandlerMethod註冊到mappingLookup中
  4. 如果@RequestMapping指定的url沒有通配符,則將url -> RequestMappingInfo註冊到urlLookup中

舉個例子,假如有如下一個Controller

@RestController
@RequestMapping("manage")
public class ProductController {

	@RequestMapping("product/*")
	public String index() {
		return "product";
	}

	@RequestMapping(value = "user", method = {RequestMethod.GET})
	public String userByGet() {
		return "userByGet";
	}

	@RequestMapping(value = "user", method = {RequestMethod.POST})
	public String userByPost() {
		return "userByPost";
	}
}

初始化完成後2個Map的值爲
在這裏插入圖片描述
在這裏插入圖片描述

查找

查找的過程還是和上面提到的2個Map有關

urlLookup
key=不包含通配符的url
value=List<RequestMappingInfo>

mappingLookup
key=RequestMappingInfo
value=HandlerMethod

  1. 先根據url從urlLookup中查找對應的RequestMappingInfo,如果找到的List不爲空,則判斷其他匹配條件是否符合
  2. 如果其他條件也有符合的(params,headers等),則不再遍歷所有的RequestMappingInfo,否則遍歷所有的RequestMappingInfo,因爲考慮到有通配符形式的url所以必須遍歷所有的RequestMappingInfo才能找出來符合條件的
  3. 如果最終找到的RequestMappingInfo有多個,則按照特定的規則找出一個最匹配的,再從mappingLookup返回其對應的HandlerMethod

可能有小夥伴有很多疑惑,爲什麼一個不含通配符的url會有多個handler。
因爲用@RequestMapping標記後,請求時不只要路徑匹配就可以,還有很多其他條件。
上面不就演示了一個因爲方法不同,導致了一個url會有多個handler嗎?

如果找到多個符合條件的Handler,返回最優Handler的過程也比較麻煩,不再像之前的SimpleUrlHandlerMapping只考慮路徑就可以了,還要考慮其他的條件,比較複雜,就不再分析了

總之註冊和查找的過程主要和這2個map打交道,總體來說也不復雜

HandlerAdapter

接口定義如下

public interface HandlerAdapter {

	// 該適配器是否能支持指定處理器
	boolean supports(Object handler);

	// 執行處理邏輯,返回 ModelAndView
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

	// 請求的目標資源最近一次的修改時間
	long getLastModified(HttpServletRequest request, Object handler);

}

DispatcherServlet有一個成員變量

private List<HandlerAdapter> handlerAdapters;

當通過HandlerMapping找到Handler後,會依次調用handlerAdapters的supports方法,找到第一個返回true的HandlerAdapter,然後調用HandlerAdapter的handle方法,完成執行。

常用的HandlerAdapter如下

類名 作用
HttpRequestHandlerAdapter 執行實現了HttpRequestHandler接口的Handler
SimpleControllerHandlerAdapter 執行實現了Controller接口的Handler
RequestMappingHandlerAdapter 執行Handler類型是HandlerMethod及其子類的Handler,RequestMappingHandlerMapping返回的Handler是HandlerMethod類型

HttpRequestHandlerAdapter

public class HttpRequestHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof HttpRequestHandler);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}

}

強轉爲HttpRequestHandler然後調用handleRequest方法,最後返回null,當ModelAndView爲null的時候,ViewResolver查找View,並且View進行渲染的過程會被省略

SimpleControllerHandlerAdapter

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return ((Controller) handler).handleRequest(request, response);
	}

}

直接強轉然後調用handleRequest方法

RequestMappingHandlerAdapter

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {


	// 參數解析器,對各種參數註解如@PathVariable,@RequestParam的的支持
	@Nullable
	private List<HandlerMethodArgumentResolver> customArgumentResolvers;

	// 返回值處理器,如當方法或類上有@ResponseBody註解時,用消息轉換器轉爲json
	@Nullable
	private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

	// 消息轉換器,對@RequestBody @ResponseBody註解的支持
	private List<HttpMessageConverter<?>> messageConverters;

}

RequestMappingHandlerAdapter自成體系,包含了大量組件對請求進行處理,如參數解析器,返回值處理器等。

RequestMappingHandlerAdapter的handler函數在父類AbstractHandlerMethodAdapter中,定義如下

public final boolean supports(Object handler) {
	return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

其中RequestMappingHandlerAdapter重寫了supportsInternal方法,永遠返回true,即RequestMappingHandlerAdapter支持Handler類型是HandlerMethod的Handler

而RequestMappingHandlerMapping返回的Handler類型就是HandlerMethod,因此可以知道@RequestMapping對應的HandlerMapping是RequestMappingHandlerMapping,對應的HandlerAdapter是RequestMappingHandlerAdapter

HandlerMethod的定義也很簡單,封裝了要執行方法所對應的類,方法,參數。這樣直接就能通過反射來執行。

public class HandlerMethod {

	// 封裝的handler的類
	private final Object bean;

	// 封裝的handler的方法
	private final Method bridgedMethod;

	// 封裝方法的參數
	private final MethodParameter[] parameters;
	
}
method.invoke(obj, args);

所以你看真正執行的過程也不復雜,但在執行前後有個很多組件參與,如參數解析器,返回值處理器等,就導致這個類有點複雜,再開文章分享把

總結

Spring MVC爲什麼要搞這麼多HandlerMapping和HandlerAdapter呢?

主要還是爲了適應不同的場景,靜態資源的請求用SimpleUrlHandlerMapping是不是特別方便,邏輯清晰還容易調試。而RequestMappingHandlerMapping則比較適合寫業務,因爲能適應複雜多變的場景

最開始提到的映射規則如何實現?

其實很簡單,寫一個HandlerMapping的實現類,返回HandlerMethod,這樣只改變了查找過程,後續執行的過程沒有改變,因此各種參數註解@RequestParam,@RequestBody,@PathVariable等都可以使用

歡迎關注

在這裏插入圖片描述

參考博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章