從原始碼的角度徹底搞懂 HandlerMapping 和 HandlerAdapter
徹底搞懂 HandlerMapping和HandlerAdapter
知識點的回顧:
當Tomcat接收到請求後會回撥Servlet的service方法,一開始入門Servlet時,我們會讓自己的Servlet去實現HttpServlet介面,重寫它的doGet()
和doPost()
方法
在SpringMvc中,SpringMvc的核心元件DispatcherSerlvet的繼承圖如上,可以看到上圖,其實這個DispatcherServlet終究還是一個Servlet
我們追蹤一下他的生命週期建立過程, 首先是說Servlet的建立時機,其實是存在兩種情況的, 這取決於.setLoadOnStartup(1);
於是Tomcat例項化Servlet,Servlet被初始化時首先被回撥的方法是init()
這大家都知道的,但是SpringMvc提供的DispatcherServlet中存在一個靜態塊,原始碼如下: 這個靜態塊幹了什麼事呢? 讀取的是class path
下面的 DispatcherServlet.properties
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { // todo 它讀取的是class path 下面的 DispatcherServlet.properties 配置檔案 // todo resource/web/servlet/DispatcherServlet.properties // todo 將這些預設的實現資訊,封裝進了Properties defaultStrategies ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } }
那問題來了,這個配置檔案到底存放的什麼? 讓DispatcherServlet
如此迫切的去載入? 我們檔案貼在下面,可以看到存放的是一些全類名,這些是DiapacherServlet針對不同策略介面提供的八個預設的實現,當在上下文中沒有匹配到程式設計師新增的這些實現時,就會使用這些預設的實現
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. # todo 這裡存在 DiapacherServlet策略介面的八個預設的實現 # todo 當在上下文中沒有匹配到程式設計師新增的這些實現時,就會使用這些預設的實現 org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
好吧, 雖然這也不算跑題,但是我們還是回到DispatcherServlet
的init()
方法,其實去這個DispatcherServlet
中是找不到這個init()
方法的, 那這個方法在哪裡了呢? 其實就在他的祖父類HttpServletBean
中,原始碼如下:
意圖很明確,前面用來初始化環境引數,後者呼叫initServletBean();
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
// todo 設定初始化引數
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// todo 初始化SerlvetBean
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
initServletBean();
見名知意,初始化ServletBean
,說白了就是想去初始化DispatcherServlet
唄,跟進去檢視,不出意料,他是個抽象方法,跟進它的實現類
他的實現類是FrameworkServlet
,進去跟進,看他去建立應用的上下文,但是如果上下文已經被初始化了,他是不會重複建立上下文的
我們繼續跟進它的onRefresh()
方法,同樣這個方法是一個抽象方法,而它是實現就是我們關注的DispatcherServlet
,原始碼如下: 在下面做了很多事,我們還是僅僅關注兩點,初始化了handlerMapping
和handlerAdapater
protected void onRefresh(ApplicationContext context) {
// todo 進行跟進,方法就在下面
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
// todo 初始化和檔案上傳相關的解析器
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
/**
* todo 初始化處理器對映器,這是我們看到重點
* todo 什麼是處理器對映器?
* todo Controller在Spring中有多種情況, 那當一個使用者的請求到來時, 如何進一步找到哪一種Controller來處理使用者的請求呢?
* todo 這一步就是通過處理器對映器完成的
* todo 說白了, 通過處理器對映器我們可以找到那個處理當前請求的特定的元件, 我們稱它為handler
* todo 但是這之間存在一個問題,就是說,這個handler到底是方法級別的,還是類級別的我們是不知道的,只能說,這個handler就肯定能處理這次請求而已
*/
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
到現在為止,本位關注的兩個元件其實就完成了初始化了, 下一個問題就來了,什麼時候使用他們呢?
那就得從Servlet的service()
方法說起了,大家都知道這個方法會在Serlvet每一次收到請求時就會被回撥一次,再回想我們原來是怎麼變成來著? 但是後來我們都直接實現HttpServlet
介面,然後重寫他們的doGet()
和doPost()
來實現我們自己的Servlet, 那SpringMvc是怎麼做的呢?
原始碼如下:
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
都是通過processRequest(request, response);
來實現的,我們往下追蹤這個方法,最終也是不出所料,我們來到了DispacherServlet
中
我擷取部分原始碼如下:
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);
// Determine handler for the current request.
//todo 確定當前請求的處理程式,跟進去
//todo 換句話說就是 推斷Controller的型別, Controller存在三種類型
// todo 跟進去看看這個handlerMapping的獲取方式
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// todo 用到了介面卡設計模式,
// todo 如果 上面的HandlerExecutionChain 是bean型別, 經過這個方法後將被設定成bean
// todo 如果 上面的HandlerExecutionChain 是 method 型別, 經過這個方法後將被設定成嗎method
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 (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
同樣的,我們只是關注上面的兩點,看看SpringMvc是如何玩轉HandlerMapping
和HandlerAdapter
的, 分兩步,先跟進mappedHandler = getHandler(processedRequest);
方法
看看他幹啥了,遍歷所有的HandlerMapping
第一個問題: 還記不記得哪裡來的RequestMappingHandlerMapping
,沒錯就是文章一開始我們去看初始化DispatcherServlet
時在靜態塊裡面完成的載入已經後續的初始化,所以按理說,下面的陣列中就存在兩個handlerMapping
分別是BeanNameUrlHandlerMapping
和RequestMappingHandlerMapping
第二個問題: 什麼是HandlerMapping
? 直接看它的中文翻譯就是處理器對映器是不好理解的,其實也沒有特別難懂, 就是一個對映器嘛,對映什麼呢? 就是對映使用者的請求與後端程式設計師編寫的Controller之間的關係, 再直白一點, 就是一個使用者的請求經過了這個HandlerMapping
就可以百分百確定出哪一個控制器是用來處理它的
第三個問題: 下面的HandlerMapping
是個陣列,意味著這個對映的規則是多種多樣的,所以來個迴圈,如果沒有任何一個對映器滿足條件怎麼辦呢? 404唄
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
於是經過了上面HandlerMapping
的處理我們獲取出來了一個 HandlerExecutionChain ,並且我們百分百確定這個 HandlerExecutionChain 就是用來處理當前的請求的,但是!!! 我們卻不能直接使用,因為我們是不清楚前面的獲取到的這個執行器鏈是個方法,還是個類,於是介面卡就用上了
原始碼如下:
這個介面卡就是HandlerAdapter
,使用設配器設計模式,不管得到的handler到底是什麼型別的,都可以找到正確的方法區執行它
HandlerAdapter
同樣是DisapcherServlet
的一大元件,它和上面的處理器對映器是一樣的,同樣是從配置檔案中被讀取出來
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
咋樣? 其實本文倒是也沒什麼難度,就是覺得確實挺好玩的