文章目錄
1. 引言
在這裏簡單簡單分析一個請求在Spring MVC中的處理過程
2. HttpServletBean
HttpServletBean主要參與了創建工作,並沒有涉及請求的處理
3. FrameworkServlet
首先從Servlet接口的service方法開始,在FrameworkServlet中重寫了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法等,所有這些方法都交給了processRequest方法進行統一處理。以doGet的代碼爲例
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
這樣做的好處是開發者如果對DispatcherServlet有特殊對定製需求可以覆蓋doGet、doPost等方法,但是一般不必要修改內部。讓我們來看一下processReqeust方法,他是FrameworkServlet類在處理請求過程中的核心方法
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//通過ContextHolder獲得請求參數
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
//真正的處理交給doService模版方法實現,在FrameworkServlet中是空實現,由DispatcherServlet重寫
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
//事件發佈
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
processRequest自己主要做了兩件事:
- 對LocaleContext和RequestAttributes的設置和恢復
- 處理完後發佈ServletRequestHandledEvent
3.1 LocaleContextHolder和RequestContextHolder
LocaleContext存放着本地化信息,RequestAttributes是Spring的一個接口,通過他的get/set/removeAttribute根據scope判斷操作是request還是session。
這裏順便提一下LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext()其內部如下
/**
* Return the LocaleContext associated with the current thread, if any.
* @return the current LocaleContext, or {@code null} if none
*/
@Nullable
public static LocaleContext getLocaleContext() {
LocaleContext localeContext = localeContextHolder.get();
if (localeContext == null) {
localeContext = inheritableLocaleContextHolder.get();
}
return localeContext;
}
這裏用到了ThreadLocal,通過他每個線程可以獨立保存自己的內容,其核心原理是Thread內部有一個屬性爲ThreadLocal.ThreadLocalMap threadLocals,當ThreadLocal在get/set時首先拿到線程當threadLocals,然後以ThreadLocal實例作爲key,所要保存的值爲value,put進去,這樣就將具體當值保存在線程自身上面。
LocaleContextHolder和RequestContextHolder可以方便在service層沒有servlet時直接獲得了Locale和RequestAttributes,順便打個斷點看了一下他們是在什麼時候把這幾個屬性注入的,發現是在RequestContextFilter中的doFilterInternal方法裏的initContextHolders(request, attributes)方法裏
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
//注入Locale和RequestAttributes
initContextHolders(request, attributes);
try {
filterChain.doFilter(request, response);
}
finally {
resetContextHolders();
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
attributes.requestCompleted();
}
}
private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
}
3.2 事件發佈
publishRequestHandledEvent發佈一個ServletRequestHandledEvent消息,可以通過監聽這個事件記錄一些事情,通過實現接口ApplicationListenner
4. DispatcherServlet
4.1 doService
DispatcherServlet是Spring MVC最核心的類,整個處理過程的頂層設計都在這裏。其處理方法的入口在doService在其內部又交給了doDispatcher進行具體的處理,doService方法大概過程是首先判斷是否包含請求,如果是則對request的Attribut做個快照並設置一些屬性,等doDispatch處理完之後進行還原。代碼如下:
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
//實現redirect時的數據保存
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
其中的flashmap方便direct時候的保存一些數據而不用暴露在url中,我們只需要在redirect之前將需要傳遞的參數寫入OUTPUT_FLASH_MAP_ATTRIBUTE
((FlashMap)((ServletReqeustAttributes)(RequestContextHolder.getRequestAttributes())
.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE))).put("name","littlemotor");
之後就會被設置到INPUT_FLASH_MAP_ATTRIBUTE屬性裏,然後再放到model裏,當然Spring提供了更方便的方法,在控制器中通過注入RedirectAttributes實例,通過addAttribute(key,value),addFlashAttribute(key,value),可以達到同樣的效果,第一種是在redirect時參數拼接到url中,第二種是在redirect時參數保存到flashMap中,例如下面的方式:
@PostMapping("submit")
public String submit(RedirectAttribute attr){
((FlashMap)((ServletReqeustAttributes)(RequestContextHolder.getRequestAttributes())
.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE))).put("id","xxx");
//與上面的等效
attr.addFlashAttribute("id","xxx");
//zh-cn會拼接到uri上面
attr.add.addAttribute("local","zh-cn");
return "redirect:uri";
}
inputFlashMap用於保存上次請求中轉發過來的屬性,outputFlashMap用於保存本次請求中需要轉發的屬性,FlashMapManager用於管理他們。
4.2 doDispatch
doDispatch方法也比較簡介,其最核心的代碼就4句
- 根據request找到Handler
- 根據Handler找到對應的HandlerAdapter
- 用HandlerAdapter處理Handler
- 調用processDispatchResult方法處理上面每一步得到的結果
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
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.
//根據request找到Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//根據Handler找到對應的HandlerAdapter
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.
//用HandlerAdapter處理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:每一個被@RequestMapping標註的方法就是一個Handler,用於處理實際請求
- HandlerMapping:用來查找Handler
- HandlerAdapter:他是一個適配器,因爲Handler的形式是靈活的,可以是類也可以是方法,而servlet的處理方式是固定的,都是以reqeust和response爲參數的方法
doDispatcher大體可以分爲兩部分:處理請求和渲染頁面,開頭定義了幾個變量:
- HttpServletRequest processedRequest,實際處理時所用的request,如果不是上傳請求則直接使用接收到的request,否則封裝爲上傳類型的MultipartHttpServletRequest
- HandlerExecutionChain mappedHandler:處理請求的處理器鏈(包含處理器和對應的Interceptor)
- boolean multipartRequestParsed: 是不是上傳請求的標誌
- ModelAndView mv:封裝Model和View的容器
- Excetion dispatchException:處理請求過程中拋出的異常,並不包含渲染過程中拋出的異常
4.2.1 getHandler
getHandler方法獲取Handler處理器鏈,其中使用到了HandlerMapping返回值爲HandlerExecutionChain類型,其中有與當前request相匹配的HandlerInterceptor和Handler,執行時依次執行Interceptor的PreHandler,然後執行Handler,返回時執行Interceptor的postHandler方法。
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
4.2.2 getHandlerAdapter
通過HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());獲取HandlerAdapter,從類型名就可以知道這是一個適配器模式,mappedHandler.getHandler()返回的是object類型,因爲handler的類型是多樣的,在下面的getHandlerAdapter方法中通過遍歷handlerAdapters,找到適配的atapter並返回。
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
之後分別進行preHandle方法,HandlerAdapter使用Handler處理請求並得到modelAndView,如果需要異步直接返回,否則執行postHandle方法,這樣內層的try塊就執行完成了。
4.2.3 processDispatchResult
processDispatchResult處理前面返回的結果,包括處理異常,渲染頁面,觸發Interceptor的afterCompletion方法三部分內容。
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable 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);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 渲染頁面
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
//如果啓動了異步處理則返回
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
//發出請求處理完成的通知,觸發Interceptor的afterCompletion
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
4.2.4 異常捕獲
- 內層捕獲的是對請求進行處理的過程中拋出的異常,即被捕獲並傳遞給dispatcherException變量的異常,然後由processDispatchResult方法進行處理。
- 外層主要是處理渲染頁面時拋出的異常,主要處理processDispatchResult方法拋出的異常。
4.2.5 finally
在最後的finally中判斷是否請求啓動了異步處理,如果啓動了則調用相應的異步處理攔截器,否則如果是上傳請求則刪除上傳請求過程中產生的臨時資源。
5. 小結
整個過程大致如下:
- HttpServletBean沒有參與實際請求處理
- FrameworkServlet將不同的請求合併到processRequest方法統一處理,processReqeust方法做了三件事情:
- 調用doService模板方法處理請求
- 將當前請求的LocaleContext和ServletReqeustAttribes在處理請求前設置到LocaleContextHolder和ReqeustContextHolder,並在請求處理完成後恢復
- 請求處理完後發佈ServletRequestEvent消息
- DispatcherServlet:doService設置了一些屬性並將請求交給doDispatch方法具體處理。