spring-webmvc請求處理流程
請求處理流程
我們先來看哈DispathcerServlet的繼承關係。
springmvc是基於servlet來的,web.xml中只有一個DispatcherServlet,
我們知道這其實只是所有請求處理都要經過的一個servlet,這個servlet只是做請求的分發處理,
找到合適的處理器,處理,並返回結果,這個是springmvc主要的實現方式,
現在我們來看這個核心DispatcherServlet的處理流程。
這裡先簡單說一下入口,servlet處理是從service(request,response)方法開始的, 這個方法的實現在Httpservlet中。 這個方法最終會根據請求的method(post,get等)轉到子類FrameServlet中的doPost(),doGet()方法進行處理, 在這些方法中,又會委託給processRequest(request, response)方法進行處理, 這裡就直接從這個方法入手了,前面的啥service(),doPost()等的就自己去瞧了。
processRequest(request, response)
//FrameworkServlet.java protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //為了保證當前線層的LocalContext以及RequestAttributes可以再當前請求後還能恢復,提取當前執行緒的兩個屬性(需要點進去看,buildLocaleContext()方法中) //根據當前request建立對應的LocaleContext和requestAttibutes,並繫結到當前執行緒 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); ... try { //委託給doSerivce()做進一步處理 doService(request, response); } ... finally { //請求結束後恢復執行緒原始狀態 resetContextHolders(request, previousLocaleContext, previousAttributes); //請求處理結束後無論成功與否,釋出事件通知 ... publishRequestHandledEvent(request, startTime, failureCause); } }
從FrameworkServlet的processRequest()方法中,我們可以看到以下流程(都在註釋中):
1. 為了保證當前線層的LocalContext以及RequestAttributes可以再當前請求後還能恢復,提取當前執行緒的兩個屬性;
2. 根據當前request建立對應的LocaleContext和requestAttibutes,並繫結到當前執行緒;
3. 委託給doSerivce()做進一步處理;
4. 請求結束後恢復執行緒原始狀態;
5. 請求處理結束後無論成功與否,釋出事件通知。
doService(request, response)
這個時候,我們來到了DispacherServlet的doService()方法。
//DispacherServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
//請求分發處理
doDispatch(request, response);
}
...
}
根據spring一貫的風格,這個時候的doservice依然是在做一些準備工作,準備工作中spring會把我們初始化的各種resolver和WebapplicationContext放到request的Attribute中,以便之後使用。重點來到doDispatch(request, response);
//DispacherServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
HandlerExecutionChain mappedHandler = null;
//如果是檔案上傳使用的MultipartContent型別的request,則將request轉換成MultipartHtptServletRequest型別的request
processedRequest = checkMultipart(request);
...
// 根據request資訊尋找對應的handler,
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
//如果沒有找到對應的handler,則通過response返回錯誤資訊
noHandlerFound(processedRequest, response);
return;
}
// 通過找到的handler,尋找它的介面卡handlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
// 呼叫攔截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// 真正的啟用handler呼叫,並返回檢視
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
...
//檢視名稱的轉換,用於需要新增前後綴的情況
applyDefaultViewName(request, mv);
// 呼叫攔截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
...
//處理結果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
...
}
getHandler
gethandler最終會呼叫到如下方法,可以看到其實就是遍歷我們初始化的所有handlerMappings來尋找一個合適的handler並返回。
// DispacherServlet.java
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
...
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
以我們最熟悉的@Controller註解方式來分析,系統會註冊預設的requestMapping,其中包含DefaultAnnotationHandlerMapping,一看就知道這個是處理註解對映的,不過這個類在3.2.x版本之後就棄用了,該用RequestMappingHandlerMapping,我們進入hm.getHandler(request)檢視,這個方法來自於超類AbstractHandlerMapping。
//AbstractHandlerMapping.java
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根據request獲取對應的handler,由子類AbstractUrlHandlerMapping實現
Object handler = getHandlerInternal(request);
if (handler == null) {
//如果沒有handler,則使用預設的handler
handler = getDefaultHandler();
}
//如果也沒有預設的handler,則不繼續處理,返回null
if (handler == null) {
return null;
}
//如果handler是一個string型別的,那說明這是個bean的name,根據它去獲取對應的bean
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
//加入攔截器,並返回添加了攔截器的handler處理鏈
return getHandlerExecutionChain(handler, request);
}
//AbstractUrlHandlerMapping.java
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//查詢對應的handler
Object handler = lookupHandler(lookupPath, request);
...
return handler;
}
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
//根據urlPath找到對應的handler,其實就是我們的Controller
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
...
//新增Inteceptor並封裝成HandlerExecutionChain並返回
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
...
}
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, Map<String, String> uriTemplateVariables) {
//將handler封裝到HandlerExecutionChain中
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
//新增Interceptor
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
如下圖,我們可以看到在lookupHandler()方法中的handlerMap其實就是url->controller的對映。
看下圖,我們可以看到,lookupHandler返回了封裝好的HandlerExcutionChain,該例項的屬性handler是之前找到的controller。
getHandlerAdapter
getHandlerAdapter也是遍歷之前註冊的所有adapter來獲取對應的handler的adapter,注意入參,是handler,就是我們的controller。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
...
if (ha.supports(handler)) {
return ha;
}
}
...
}
直接上圖,可以看到handlerAdapters有三個,我們使用的是@Controller註解的方式,我們直接來看AnnotationMethodHandlerAdapter。
//AnnotationMethodHandlerAdapter.java
public boolean supports(Object handler) {
//其實就是找一個resolver,如果沒有初始化一個,然後判斷一下handler裡面的method是否為空
//因為我們是註解的方式,能夠想到方法呼叫是採用反射的方式,所以這裡會判斷method是否為空
return getMethodResolver(handler).hasHandlerMethods();
}
ha.handle()
這個ha是上一步獲取到的handlerAdapter,不是第一步獲取的的handler哦,這裡才真正開始執行呼叫我們的controller邏輯。
//AnnotationMethodHandlerAdapter.java
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
//一貫風格又來了
return invokeHandlerMethod(request, response, handler);
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
//呼叫我們的controller的方法,返回結果
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
//生成一個檢視,會根據返回值的型別,是否加了ResponseBody註解做不同的處理,感興趣的自己去看
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
...
return mav;
}
可以看到我們的controller最終被呼叫了,
spring在呼叫返回結果後會對結果的返回值型別,方法是否加了@ResponseBody註解等做進一步處理,這部份就自己去看了哦,
這裡說明一下,如果方法加了@ResponseBody註解,那麼返回的mav=null。
processDispatchResult()
在這個的上一步會執行Interceptor的postHandle()方法mappedHandler.applyPostHandle(processedRequest, response, mv);,這裡提一下。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//呼叫HandlerExceptionResolver處理異常,
//這個東西就是在servlet初始化的時候最後一步initStrategies()方法當中,跟requestMapping等一起註冊的
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
if (mv != null && !mv.wasCleared()) {
//如果返回的mv不為空,處理檢視,並跳轉,這裡會用到viewResolver
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
...
if (mappedHandler != null) {
//呼叫Intecepter的afterCompletion()方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
在最後的處理結果當中,如果請求有異常發生,就會導向異常處理器去處理,
如果沒有,就會根據是否存在檢視進行跳轉,最後呼叫Interceptor的afterCompletion()方法。
到這裡整個請求算完了,剩下的就是一些後續清理工作,釋出時間通知等工作了。