servlet
在了解dispatcherServlet的流程前,首先要了解servlet這個技術,
狹義的來說,servlet只是一個介面,廣義的來說,它是一個規范,
在傳統的Javaweb專案里,通過繼承HttpServlet(實作了service介面)重寫doGet,doPost方法,再在web.xml里標識,通過tomcat作為servlet容器來管理這些servlet,
tomcat在不考慮io模型通信情況下, 只有兩個主要的功能,
- 封裝request,response物件
- 呼叫servlet的service方法
這個是理解dispatcher流程的關鍵,首先要知道tomcat到底是干什么的
我們可以看下HttpServlet的service方法的原始碼
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
復制代碼
也就是說傳統的servlet的專案中,tomcat把請求封裝后根據url呼叫對應路徑的servlet,執行service方法,再判斷請求型別去呼叫doGet(),doPost()等,
dispatcherServlet和servlet的關系
dispatcherServlet依賴關系很復雜,簡單來說就是他間接的實作了servlet介面,先看下servlet介面的方法
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
復制代碼
我們一會重點說下init和service方法,
對于springmvc來說,需要在web.xml里配置dispatcherServlet,讓tomcat去管理,對于spring的專案,tomcat只需要管理spring自帶的servlet就可以了,我們不需要去寫servlet,我們是通過bean讓spring容器去管理,這里的spring容器和tomcat容器不是一個概念,
springboot基于自動配置不需要去手動配置,
根據上面所說,任何路徑下的請求,tomcat都會呼叫spring的dispatcherServlet的service方法去處理,由于依賴關系復雜,service方法是在dispatcher的父類,總結來說, 最后會呼叫dispatch的doDispatch方法 ,中間會有些程序,這里就忽略不計了,
流程
丑陋的面經只會甩給你一張圖

硬背這種八股文,完全不知道他的理念和實作細節,對我們的思想是沒有一點好處的,所以要剖析下他的原始碼,
初始化
首先要對spring容器初始化重繪容器,才能進行互動,對于springmvc來說,tomcat會先執行dispatcher的init方法(其實是HttpServletBean的init方法)來重繪容器,底層就是呼叫的refresh方法,對于springboot來說,會執行main方法的run方法,里面執行一個refresh方法來重繪容器,雖然仍然會執行init方法,但里面的refresh方法應該是不會再執行的,(雖然不知道springboot為什么可以不讓執行,但根據打斷點來說,他確實沒有執行),總之殊途同歸,
請求流程
我再把之前的捋一遍,請求過來后,先被tomcat封裝request,response物件,再呼叫service方法,即dispatchServlet的doDispatch方法,直接看原始碼
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.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
復制代碼
這么看復雜的一比,實際上對比丑陋面經的圖片,只有幾個方法比較重要,
//從Handler映射器獲取handlerchain
mappedHandler = getHandler(processedRequest);
//獲取配接器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//執行handler,獲取ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//判斷mv中的視圖是否為空
applyDefaultViewName(processedRequest, mv);
//視圖決議
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
復制代碼
感覺這應該是spring中最容易理解的原始碼了,我們逐條分析,
映射器
首先是getHandler()方法
protected HandlerExecutionChain getHandler(HttpServletRequest request) {
//this.handlerMappings就是一個HandlerMapping陣列,Spring容器啟動后,會將所有定義好的映射器物件存放在這個陣列里
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
復制代碼
這個理解起來很簡單,由于有多種的Handler,就有多種的mapping,需要找到正確的mapping
- RequestMappingUrlHandlerMapping 最常見的使用注解標識的handler映射
- BeanNameUrlHandlerMapping
- SimpleUrlHandlerMapping
- ControllerClassNameHandlerMapping
其中第一個最為常見,使用@requestMapping注解標識的handlermapping,其他的有繼承controller,httpservlet的,就不多說了,
通過回圈,直到找到正確的處理器映射器,獲取到handle,實際上是一個HandlerExecutionChain,他的構成其實就是攔截器陣列+handler,
配接器
上一步獲取到了處理器鏈后,獲取配接器,通過getHandlerAdapter方法,引數為handler(目前攔截器陣列還沒有用)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
復制代碼
這里就用上了大名鼎鼎的 配接器模式 了
如果不使用配接器,以后再增加handle種類時,由于不同handle之間的邏輯完全不一樣,需要通過在dispatch中使用if else的方式去判斷handle的種類,再執行操作,
protected HandlerAdapter getHandlerAdapter(Object handler) {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
};
}
復制代碼
這里的this.handlerAdapters是獲取配接器陣列,跟上面有點像,獲取到合適的配接器回傳,
配接器執行handle方法
配接器使用handle方法執行
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
復制代碼
最后得到了一個modelandview,在古老的jsp時代,是通過modelandview傳遞資料并決議jsp視圖的,但由于現在前后端分離,使用@ResponseBody注釋只傳送json串,所以這里的modelandview為空,
所以接下來的視圖決議部分我們就不關注了
攔截器
所以handerchain里的攔截器作用是什么呢,
其實在配接器執行handle方法 前后 會進行一個攔截器的處理,只是我沒有寫而已,
最后
tomcat的處理,我就不說了
小結
springmvc這塊的流程如果不深挖其實很簡單,基本上有過開發經驗的都能夠大概了解這塊的流程,但其實我上面省略了一部分,就是配接器的 handle 方法和映射器的 getHandler 方法,這塊比較難理解,所以我這里只介紹前后端分離和常用注解的情況,我盡量從頂向下的方式去說明原始碼,
映射器的原始碼分析
面經簡略版
- 在ioc初始化時,映射器也會初始化,就把映射對應關系放在一個map里
- 映射器的 getHandler 方法,通過url的后綴,從map中獲取到對應的handler
詳細版
先說下HandlerMapping的繼承關系
實際上最常用的就是
RequestMappingHandlerMapping ,他對應的就是@Controller @RequestMapping這種寫法的handle映射器,我這里就只介紹這一個映射器了,
我們直接從請求流程來看
首先先是在dispatcher里回圈映射器串列,呼叫getHandler方法(上面有寫),實際就是呼叫AbstractHandlerMapping的getHandler方法
AbstractHandlerMapping
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//省略版
Object handler = getHandlerInternal(request);
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
return executionChain;
}
復制代碼
這個getHandlerInternal()回傳的就是我們需要的handle,點進去會呼叫
AbstractHandlerMethodMapping 的方法
getHandlerInternal()
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//獲取url,例如/user/login
String lookupPath = initLookupPath(request);
//獲取HandlerMethod,核心方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
復制代碼
先看回傳值,回傳了一個 HandlerMethod ,這個就是我們的處理器,之前說過,springmvc有多種handler,每個handle的處理邏輯都不一樣,通過不同映射器獲得的handler都不同,之后需要通過配接器來統一規范執行,這個后面再說,總之 HandlerMethod 這個物件很重要,
HandlerMethod類
看下HandlerMethod類的構造
//省略了很多
public class HandlerMethod {
private final Object bean;
private final Method method;
}
復制代碼
就介紹下這兩個成員變數吧,bean就是controller的bean,Method是映射到controller的方法,Method不懂的話可以去看下反射的原理,總之在后面配接器那里會執行method的invoke方法,也就是執行controller上映射的方法,
我們在回傳剛才的方法,看下核心方法 lookupHandlerMethod
lookupHandlerMethod()
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//重點
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
Match bestMatch = matches.get(0);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
復制代碼
這個代碼我進行了瘋狂的省略,把一些多映射或者空的情況刪掉了,我們先去理解正常代碼的一個流程,
我們來分析下第二行重點代碼,this. mappingRegistry .getMappingsByDirectPath(lookupPath),我們首先要看這個 mappingRegistry 是什么,
mappingRegistry類
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
}
復制代碼
mappingRegistry是一個內部類,里面都是一堆map,這個getMappingsByDirectPath方法只是從 pathLookup 這個map里,以lookupPath(url,例如/user/login)為key獲取值而已,這個值就是我們的handler,
這時候問題就來了,這些map是什么時候獲取的內容呢

在springioc容器初始化時,映射器也會初始化,就把映射對應關系放在pathLookup這個map里,具體流程就跟容器初始化有關了,我就不詳細敘述了,
小結
可以發現,對于這種請求來說,根本就沒用到
RequestMappingHandlerMapping的方法,都是他的父類AbstractHandlerMapping的方法,之后的流程就很簡單了,只是把handlerMethod封裝成handerchain,回傳了,
配接器的原始碼分析
面經簡略版
- 呼叫 RequestMappingHandlerAdapter 的handle方法,實際是通過反射呼叫controller的方法,并回傳值
- 根據回傳值選擇特定的回傳值決議器(例如使用@Response注解回傳的是物件,則使用RequestResponseBodyMethodProcessor,將物件序列化為json)
- 最后回傳的mv為null,跳過了視圖決議器,
詳細版
HandlerAdapter
上面流程的模塊介紹了配接器有很多種,他們都實作了HandleAdapter介面,看下介面的原始碼
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
}
復制代碼
同樣省略,重要的只有這兩個方法,
第一個方法在上文回圈判斷 是否合適 時呼叫過,第二個方法就是dispatcher里的執行方法,回傳一個modelAndView,這么一看很簡單嗎,所以我們來看下他的實作類,
由于handle種類很多,就會對應了很多的配接器

我們平常@Controller出來的 HandlerMethod 使用的是
RequestMappingHandlerAdapter ,是這里最復雜的,為什么復雜呢,因為它涉及了很多的url映射,引數和回傳值的處理,這里我先舉一個簡單的例子,
SimpleServletHandlerAdapter,servlet型別的handler配接器,
SimpleServletHandlerAdapter
public class SimpleServletHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
((Servlet) handler).service(request, response);
return null;
}
}
復制代碼
這里先說明一點,spring也是可以使用servlet規范的寫法的,例如繼承httpservlet,只是還會走mvc的流程,因為這種servlet不歸tomcat管理,而是作為一個bean被spring容器管理,
可以看到這個實作類和他的名字一樣簡單,supports只是判斷了handler是否是servlet型別,handle只是呼叫了service,
AbstractHandlerMethodAdapter
//簡化版
public abstract class AbstractHandlerMethodAdapter implements HandlerAdapter {
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
return handleInternal(request, response, (HandlerMethod) handler);
}
protected abstract ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
}
復制代碼
先看
RequestMappingHandlerAdapter的抽象類,
- supports判斷型別是否是 HandlerMethod ,除此之外還有個新方法supportsInternal,但是對于RequestMappingHandlerAdapter來說,這個方法一直回傳true,所以我們可以認為, 當一個Handler是HandlerMethod 型別的時候, 就會被這個配接器處理,這里的重點就是 HandlerMethod ,他代表著我們平常使用的handler型別
- handle呼叫新方法handleInternal,作為引數的handler被強轉為 HandlerMethod 型別
handle方法流程
剛才簡單介紹下父類配接器的介面,接下來看流程
上面說到,dispatcher會先回圈獲取對應的handleAdapeter,通過的就是supports方法,我就忽略掉了,
然后會呼叫handle方法,其實就是
RequestMappingHandlerAdapter的 handleInternal 方法
RequestMappingHandlerAdapter
handleInternal()
protected ModelAndView handleInternal(){
ModelAndView mav;
mav = invokeHandlerMethod(request, response, handlerMethod);
return mav;
}
復制代碼
在抽象的省略后,只剩下invokeHandlerMethod方法
invokeHandlerMethod()
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//ServletInvocableHandlerMethod是HandlerMethod的子類
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
//核心
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest);
}
復制代碼
這里有大量異步和mavcontainer的部分我都刪掉了,沒意義的同時也是因為我看不懂,我就不分析了,
這塊代碼核心就是
invocableMethod.invokeAndHandle(webRequest, mavContainer),分析下引數,
- webRequest request的封裝
- mavcontainer在前后端分離情況都是為空的,
HandlerMethod子類
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//獲得回傳值,providedArgs為引數
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//回傳值決議
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
復制代碼
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//引數決議器,例如@RequestBody,物件,變數之類的,對引數決議之后放入args陣列,
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
return doInvoke(args);
}
復制代碼
protected Object doInvoke(Object... args) throws Exception {
//獲取method,這個method就是controller對應url的方法
Method method = getBridgedMethod();
//反射呼叫controller的方法
return method.invoke(getBean(), args);
}
復制代碼
這是請求到controller之前的最后一步,這是InvocableHandlerMethod的 doInvoke 方法,這里只進行了一個簡單的反射而已,獲取到Method物件后呼叫invoke方法,也就是執行controller下映射url的方法,我這里舉一個例子
@GetMapping
public ApiMsg get(@RequestParam(value = "current", required = false, defaultValue = "1") int current,
@RequestParam(value = "size", required = false, defaultValue = "10") int size){
return adminReportService.selectAllReport(current,size);
}
復制代碼
一個極其標準的controller,回傳的是一個Object物件
對于回傳值和引數值的決議我就不研究了,
小結
對于前后端分離傳遞json這種寫法,modelandview都為空,都是直接對回傳值處理的,我就沒有關注這個modelandview的處理,
總結
作為最常使用的mvc,應該是對于我們這種web開發來說最容易理解的spring原始碼了,里面還有很多的學問,包括對不同handler的處理或者不同類之間的區別,jsp的決議等,我才疏學淺就不深究了,如果上面有什么寫得不對,請評論告訴我,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/317821.html
標籤:其他
