主要參與建立工做,並無涉及到請求的處理。web
servlet的處理過程:首先是從servlet接口的service方法開始,而後在HttpServlet的service方法中根據請求的類型不一樣將請求路由到doget,doHead,doPost,doPut,doDelete,doOptions和doTrace七個方法。而且作了doHead,doOptions,doTrace的默認實現,其中doHead調用doGet,而後返回只有header沒有body的response.spring
在FrameworkServ;et中重寫了service,doGet,doPost,doPut,doDelte,doOptioms,doTrace方法(除了doHead全部處理請求的方法).在service中增長了對patch類型請求的處理,其餘類型的請求直接交給了父類處理;doOptions和doTrace方法能夠經過設置dispatchOptionsRequest和dispatchTraceRequest參數決定是本身處理仍是交給父類處理;doGet,doPost,doPut,doDelete都是本身處理。全部須要本身處理的請求都交給了processRequest方法統一處理。瀏覽器
@Override緩存
protected void service(HttpServletRequest request, HttpServletResponse response)服務器
throws ServletException, IOException {session
String method = request.getMethod();mvc
if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {app
processRequest(request, response);異步
}async
else {
super.service(request, response);
}
}
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
這裏所作的事情和HttpServlet中對請求按類型分別進行處理不一樣,這裏正好相反,將全部請求都合併到了processRequest方法,全部請求類型都執行相同的模板方法processRequest.後面將會對不一樣的請求交給不一樣的Handler進行處理。這裏的service不是直接覆蓋HttpServlet中的service方法,而是又將調用super.service(),若是直接用processRequest處理,咱們在作一些特殊的需求在Post請求處理前對request作一些處理,這時可能會新建一個繼承自DispatcherServlet的類,而後覆蓋doPost方法,在裏面先對request作處理,而後在電泳supper.doPost(),這時就會出現問題了。
processRequest是FrameworkServlet類在處理請求中最核心的方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
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(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (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();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
processRequest方法中最核心的語句是doService(request,response),這是一個模板方法,在DispatcherServlet中具體實現。在doService先後還作了一些事情(裝飾模式),先開始從LocaleContextHolder中取得LocaleContext並設置到previousLocalContext,從requestContextHolder中得到RequestAttributes並設置到perviousAttributes,而後調用buildLocaleContext和BuildRequestAttributes方法得到當前請求的LocaleContext和RequestAttributes,經過initContextHolders方法將現有屬性設置到LocaleContextHolder和RequestContextHolder中(處理完成後再恢復到原來的值),
LocaleContext和RequestAttributes是什麼,localeContext裏面存放着Locale(本地信息化 zh-cn等),RequestAttributes是spring的一個接口,經過它能夠get/set/removeAttribute,根據scope參數判斷是request仍是session.這裏具體使用ServletRequestAttribute類,裏面封裝了request,response,session,均可以直接得到。
// org.springframework.web.context.request.ServletRequestAttributes.class
public void setAttribute(String name, Object value, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
}
else {
HttpSession session = getSession(true);
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}
經過scope判斷是對request仍是session進行操做;是直接對request和session進行操做,判斷request是否已經被使用過,isRequestActive(),當requestCompleted以後requestActive就會變爲false,request未完成以前爲true. RequestAttributes用來管理request和session的屬性。
org.springframework.context.i18n.LocaleContextHolder.class
LocaleContextHolder是一個抽象類,其中全部的屬性和方法都是static的,能夠直接調用,沒有父類和子類,不能實例化。
private static final ThreadLocal<LocaleContext> localeContextHolder =
new NamedThreadLocal<LocaleContext>("Locale context");
private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
new NamedInheritableThreadLocal<LocaleContext>("Locale context");
上面兩個屬性都是ThreadLocal<LocaleContext>類型的,LocaleContextHolder中提供了get/set localeContext和Locale方法,好比在程序中要用到Locale的時候,首先是request.getLocale(),這是最直接的方法,若是service層要用到locale時,這時沒有request,能夠在controller中拿出來再傳到service中,這樣也能夠,可是還有一種方法就是用LocaleContextHolder.getLocale();
org.springframework.web.context.request.RequestContextHolder.class也是一個抽象的類,裏面的屬性和方法都是static的:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
這兩個屬性都是ThreadLocal<RequestAttributes>類型的,其中的方法都是對requestAttribute的set/get/reset/currentRequestAttribute/faces.還包含了一個內部類FacesRequestAttributesFactory,避免硬編碼。
FrameworkServlet中processRequest中最後finally中對request的屬性進行了恢復調用resetContextHolder方法將原來的localeContext和requestAttribues恢復。緣由是可能在servlet外還有別的操做,爲了避免影響操做,因此須要進行恢復。
最後是publishRequesthandledEvent(request,response,startTime,failureCanse)發佈了消息
org.springframework.web.servlet.FrameworkServlet.class
private void publishRequestHandledEvent(
HttpServletRequest request, HttpServletResponse response, long startTime, Throwable failureCause) {
if (this.publishEvents) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
int statusCode = (responseGetStatusAvailable ? response.getStatus() : -1);
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause, statusCode));
}
}
當publishEvents設置爲true時,就會發布這個消息,不管請求處理成功與否都會發布。
publishEvents能夠在web.xml中配置spring mvc的servlet的時候進行配置,默認爲true,咱們能夠監聽這個事件來作一些事情,如記錄日誌。
public class ServletRequestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> {
final static Logger logger = LoggerFactory.getLogger("RequestProcessLog");
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
logger.info(event.getDescription());
}
}
上面是一個簡單的應用,只要簡單的繼承ApplicationListener,實現對應的方法,而後註冊到spring中就可使用了。
到如今爲止FrameworkServlet就分析完了,回顧一下:首先在service方法中添加了Patch的處理,並將全部須要本身處理的請求都集中到processRequest方法中統一處理,這和HttpServlet裏面根據request的類型將請求分配到各個不一樣的方法裏處理過程正好相反。
而後就是processRequest方法,將主要的處理邏輯交給了doService,這是一個模板方法,在子類中具體實現;另外就是對當前request中得到的LocaleContext和RequestAttributes方法進行保存,以及處理完以後恢復,最後發佈了ServletRequestHandledEvent事件。
DispatcherServlet是Spring mvc中最核心的類,整個處理過程的頂層設計都在這裏。
DispatcherServlet裏面執行處理的入口方法是doService,不過doService沒有直接處理,而是交給了doDispatch進行具體處理。在doDispatcher處理前doService作了一些事情,首先判斷請求是否是include請求,若是是則對request的Attribute作快照備份,等doDispatch處理完成後進行還原,作完快照後對request設置一些屬性。
org.springframework.web.servlet.DispatcherServlet.class
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
//快照include以後還原
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
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());
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);
//交給dodispatch方法
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);
}
}
}
}
對request設置的屬性中,webApplicationContext,lcaoleResolver,ThemeResolver和themeSource後面三個都和flashMap相關,主要用於Redirect轉發時參數的傳遞。好比,爲了不重複提交表單,能夠在處理完post請求後redirect到一個get請求,這樣即便用戶刷新也不會重複提交。這裏有一個問題,提交訂單時有參數,提交完成後redirect到一個顯示訂單的頁面,顯然在顯示訂單的時候還要知道一些參數。按普通的模式若是想要提交參數,就只能將其寫入url中,可是url也有長度限制。另一些場景中咱們不想將參數暴露到url中,這時既能夠用flashMap來進行傳遞參數。只須要將須要傳遞的參數寫入OUTPUT_FLASH_MAP_ATTRIBUTE,例如
((FlashMap)((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getRequest().getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name", "張三丰");
這樣在redirect以後的handle中的spring 就會自動將其值設置到model裏,spring還給咱們提供了更加簡單的操做方法,只須要在handler方法的參數中定義redirectAttributes類型的變量,而後將須要保存的屬性設置到裏面就行,以後的事情由spring自動完成.RedirectAttributes有兩種設置參數的方法addAttribute(key,value)和addFlashAttribute(key,value),用第一個方法設置的參數會拼接到url中,第二個設置的參數會保存在flashMap中.
@RequestMapping(value="/submit",method=RequestMethod.POST)
public String submit(RedirectAttributes attr) throws IOException{
((FlashMap) ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest()
.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name", "張三丰");
attr.addAttribute("orderId","one");
attr.addFlashAttribute("local","zh-cn");
return "redirect:showorders";
}
@RequestMapping(value="/showorders",method=RequestMethod.GET)
public String showOrders(Model model) throws IOException{
//dothing....
return "orders";
}
這裏分別用三種方法傳輸redirect參數
1.RequestContextHolder得到request,並從中拿到outputflashMao,將屬性放進去;
2.RedirectAttributes中addFlashAttribute方法設置
3,經過RedirectAttributes中的addAttribute方法設置,這樣設置參數不會保存到flashMao中,而是拼接到url中;
還能夠用requestContextUtils來操做:RequestContextUtils.getOutoutFlashNap(request),這樣能夠獲得outputFlashMap,其內部仍是從Request的屬性中得到的。
這就是flashMap的用法,inputFlashMap用於保存上次請求中轉發過來的屬性,outputFlashMap用來保存本次請求須要轉發的屬性,FlashMapManager用於管理他們;
doService對request設置了一些屬性,若是是include屬性,會對request當前的屬性進行快照備份,並在處理結束後恢復,最後將請求轉發給doDispatch方法。
doDispatch方法很是簡潔,從頂層設計了整個請求處理的過程,doDispatch只有四句核心的代碼,他們的任務分別是:1.根據request找到Handler;2根據handler找到對應的HandlerAdaper;3.用HandlerAdaper處理Handler;4.調用processDispatchResult方法處理上面的結果(包含找到view並渲染輸出給用戶),
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
上面有三個須要瞭解的地方:HandlerMapping,Handler,HandlerAdapter,下面具體說一下:
HandlerMapping:查找請求對應的Handler,有許多請求,每一個請求都須要一個handler來處理,具體接收到請求後用哪一個handler來處理呢,這就是handlerMapping要作的事情。
HandlerAdapter:從名字上看它是一個適配器,spring mvc中handler能夠是任意類型,只要能處理請求就夠了,可是servlet中的處理方法的結構倒是固定的,都是以request和response做爲參數的(例如doService方法),如何讓固定格式的servlet處理方法調用靈活的handler來處理呢?這就是HandlerAdapter要作的事。
Handler是用來幹活的工具,HandlerMapping是用於根據須要乾的活找到對應的工具,HandlerAdapter是用工具幹活的人。
view 和viewResolver的原理與handler和HandlerMapping 的原理相似。view是用來展示model的數據的,而viewResolver是用來查找view的。
上面四句代碼就是handlerMapping找到幹活的handler,找到使用handler的HandlerAdapter,讓HandlerAdapter使用handler幹活,幹完活後將結果寫個報告(經過view展示)
org.springframework.web.servlet.DispatcherServlet.class
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 || mappedHandler.getHandler() == 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 (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
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(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, 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);
}
}
}
}
doDispatch大致分爲兩部分:處理請求和渲染頁面:
HttpServletRequest processedRequest:實際處理時所用到的request,若是不是上傳請求則直接使用接收到的request,不然封裝爲上傳類型的request;
HandlerExceptionChain mappedHandler:處理請求時的處理器鏈(包含處理器和對應的Interceptor)
Boolean multipartRequestParsed:是否是上傳請求的標識
ModelAndView mv:封裝model和view的容器。此變量在整個springmvc中承擔着很是重要的角色。
Exception dispatchException:處理請求過程當中拋出的異常,它不包含渲染過程拋出的異常。
doDispatch中首先檢查是否是上傳請求,若是是上傳請求,就將request轉換爲MultipartHttpServletrequest,並將mulitipartRequestParsed標識設置爲true,其中也用到了multipartResolver.
而後經過gethandler方法得到處理器鏈,其中使用到了handlerMapping,返回值類型爲handlerExecutionChain類型,其中包含着與當前request相匹配的Interceptor和Handler,
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
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;
}
接下來是處理get,head請求的last_modified,當瀏覽器第一次跟服務器請求資源(get,head請求)時,服務器在返回的請求頭裏會包含一個Last_modified的屬性,表明本資源最後是何時修改的,在瀏覽器之後發送請求的時候會同時發送以前接收到的last_modifed,服務器接收到帶last_modified的請求後會用其值和本身的實際資源的最後修改時間作對比,若是資源過時了則返回新的資源(同時返回新的last-modified),不然直接返回304狀態碼錶示資源未過時,瀏覽器也直接使用以前緩存的結果。
接下來調用相應Interceptor的preHandle.
處理完Interceptor的preHandle後就到了處理請求的關鍵地方-讓handleAdapter使用handler處理請求,Controller就是在這個地方執行的,這裏主要使用了handlerAdapter.
handler處理完成後,若是須要異步請求,則直接返回,若是不須要,當view爲空時,設置爲默認view,而後執行相應的Interceptor的postHandle,設置默認view的過程當中使用到了ViewNameTranslator..
到這裏請求的內容完成了,接下來使用processDispatchResult方法處理前面返回的結果,其中包含處理異常,內部異常處理和自己拋出的異常。
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);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
以下是doDispatch的流程圖:
三個Servlet處理中的大體功能:
HttpServletBean:沒有參與實踐請求的處理;
FrameworkServlet:將不一樣類型的請求合併到了processRequest方法統一處理
processRequest作了三件事:
調用了doService模板方法具體處理請求;
將當前請求的LocaleContext和ServletRequestAttributes在處理請求前設置到LocaleContextHolder和RequestContextHolder,並在請求處理完成後恢復;
請求處理完成後發佈ServletRequestHandledEvent消息。
DispatcherServlet:doSerivce方法給request設置了一些屬性,並將請求交給了doDispatch方法具體處理。
DispatcherServlet中的doDispatch方法完成了Spring mvc中請求處理過程的頂層設計,使用了DIspatcherSerlvet中的九大組件完成了具體的請求處理。