SpringMVC请求处理核心方法主要是如下调用:
(FramworkServlet 的方法) processRequest <----
(DispatcherServlet的方法) doService <---- doDispatch <---- processDispatchResult <------- render方法
这个类主要参与了创建配置环境,没有涉及到请求的处理。
由于FrameworkServlet也是间接继承了HttpServlet,在这个类中重写了service,doGet,doPost,doPut,doDelete,doOptions,doTrace方法,并且在service中还增加了对于PATCH类型请求的处理。并且重写的所有请求又统一交给了processRequest方法统一进行处理。举例,其他类似:
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
super.service(request, response); //这里任然代用HttpServelt中的service方法进行分发处理,这里也是为了防止做一些特殊请求,不能完全的调用覆盖的service方法。
} else {
this.processRequest(request, response);
}
}
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
processRequest方法主要做的事:
1) 异步请求
2)调用了doService模板方法
3)对LocaleContext(这是针对国际化的环境设置)和RequestAttributes(涉及到request和session的设置)的设置和恢复
4)处理完消息后发布了ServeltRequestHandledEvent消息
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取LocaleContextHolder中原来保存的LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//获取当前请求的LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//获取RequestContextHolder中原来保存的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//获取当前请求的ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 异步操作管理
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder中
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 {
//恢复原来的LocaleContext和ServiceRequestAttributes到LocaleContextHolder和RequestContextHolder,避免影响Servlet以外的处理,如Filter
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted(); //这里执行了request后有一个requestActive状态值会转变为false,执行前是true
}
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");
}
}
}
//发布ServletRequestHandlerEvent消息,这个请求是否执行成功都会发布消息的
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
1) LocalContextHolders 这是一个abstract类,但里面的方法都是static的,可以直接调用,而且没有父类也没子类,因而我们不能对其进行实例化,只能调用其static方法。这种abstract的使用方式值得我们学习。在LocaleContextHolder中定义了两个属性:
private static final ThreadLocal localeContextHolder =
new NamedThreadLocal("Locale context");
private static final ThreadLocal inheritableLocaleContextHolder =
new NamedInheritableThreadLocal("Locale context");
LocaleContextHolder还提供了get/set方法,可以获取和设置LocaleContext,另外还提供了get/setLocale方法,可以直接操作Locale,保存在ThreadLocal中能够保证多个请求之间相互独立,互不影响,对于ThreadLocal的作用及其实现原理,我们将在后面的文章中进行说明。
LocaleContextHolder抽象类目的:为了方便在项目的任何地方使用Locale,而不需要将其作为参数进行传递到对应的地方,只需要调用一下LocaleContextHolder的getLocale()方法即可。例如如果我们再service层调用local的时候就可以直接通过LocalContextHolders.getLocal()调用而不需要依赖request。
2)RequestAttributesHolder也是一样的道理,里面封装了RequestAttributes,可以get/set/removeAttribute,而且因为实际封装的是ServletRequestAttributes,因此还可以通过get/set方法获取或修改request、response、session对象。
ServletRequestAttributes类中通过scope判断是request还是session源码:
public void setAttribute(String name, Object value, int scope) {
if (scope == 0) {
if (!this.isRequestActive()) {
throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
} else {
HttpSession session = this.obtainSession();
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}
3)publishRequestHandledEvent()发布请求处理完后的事件源码
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) {
//当publishEvents设置为true和 webApplicationContext 不为空就会处理这个事件的发布
if (this.publishEvents && this.webApplicationContext != null) {
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(), request.getMethod(),
this.getServletConfig().getServletName(), WebUtils.getSessionId(request),
this.getUsernameForRequest(request), processingTime, failureCause,
response.getStatus()));
}
}
那么怎么使用这个监听事件了,只需要的实现ApplicationListener这个接口就可以,可以用来做记录日志的监听器,并且只需要把自己需要做的事情写到onApplicationEvent里面就可以了,当然需要把这个注册到容器中,这里用注解Componet实现。
代码例子:
@Componet
public class ServletReqestHandledEventListener implements ApplicationListener {
final static Logger logger = LoggerFactory.getLogger("RequestProcessing");
@Override
public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) {
logger.info(servletRequestHandledEvent.getDescription());
}
}
DispatcherServlet里面执行处理的入口方法是doService,由于这个类继承于FrameworkServlet类,重写模板方法doService().源代码如下:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (this.logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
//当include请求时对request的Attribute做快照备份
Map attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap();
Enumeration attrNames = request.getAttributeNames();
label112:
while(true) {
String attrName;
do {
if (!attrNames.hasMoreElements()) {
break label112;
}
attrName = (String)attrNames.nextElement();
} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
//对request设置一些属性,便于具体处理时调用
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
//flashMap主要用于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 {
//request设置完相关的属性做真正的请求处理
this.doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
//还原request快照的备份
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
FlashMap介绍
上面后面的三个request参数设置都是和flashMap相关,主要用于Redirect转发参数的传递。比如为了避免重复提交表单,可以在处理完post请求后redirect到一个get请求,这样即使用户刷新也不会有重复提交的问题。不过问题是:前面的post请求是提交订单,提交完后redirect到一个显示订单的页面,显然在显示订单的页面需要订单的一些信息,但是redirect本身是没有传递参数的功能的,按照普通模式传递只能将其写入url中,但是url有长度限制,而且有些参数还不能暴露在url中,所以我们就需要 使用flashMap来进行数据传递了,我们需要在redirect之前强参数写入到OUTPUT_FLASH_MAP_ATTRIBUTE如下所示:
((FlashMap)((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().
getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name","vison");
这样在redirect之后的handle中spring就会自动将其设置到model中(先设置到INPUT_FLASH_MAP_ATTRIBUTE属性里,然后再放到model中)这样操作显得麻烦,RedirectAttributes有两种设置参数的方法addAttribute(key,value);addFlashAttribute(key,value)第一个方法设置的参数会拼接到url中,第二个方法设置的参数即使保存到flashMap中的。当然下面的outputFlashMap还可以通过RequestContextUtils.getOutFlashMap(request)来获取。
例子:
@RequestMapping(value = "/submit",method = RequestMethod.POST)
public String submit(RedirectAttributes attributes){
//方式一存值
((FlashMap)((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().
getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name","vison");
//方式二存值
attributes.addFlashAttribute("ordersId","XXX");
//方式三存值
attributes.addAttribute("Local","zh-cn");
return "redirect:showorders";
}
@RequestMapping(value = "/showorders",method = RequestMethod.POST)
public String showOrders(Model model){
// dosometing.....
return "orders";
}
如上所示前两种的方式都是存在flashMap中,对用户来说都是透明的,但是第三种这是拼接到url后面的,比如访问http://xxx/submit请求后浏览器地址会自动跳转到http://xxx/showorders?Local=zh-cn上连接。inputFlashMap用来保存上次请求中转发过来的属性,outputFlashMap用于保存本次请求需要转发的属性。下面看doDispatch方法。
这个方法是在doService方法中调用的,从底层设计了整个请求的处理流程:
i:根据request找到Handler;
ii:根据Handler找到对应的HandlerAdapter;
iii:用HandlerAdapter处理Handler;
IV:调用 processDispatchResult方法处理上面之后的结果(包含View渲染并输出给用户)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
//检查是不是上传请求,是上传请求解析,否则还是返回request
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//通过请求获取到 HandlerExecutionChain (包含请求和拦截器)
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//根据Handler找到HandlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
// 处理GET、HEAD请求的Last-Modified
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
//当数据没有更改时,就直接返回上次的数据,提高效率
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
//执行相应的Interceptor的preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// HandlerAdapter使用Handler处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//如果需要异步处理,直接返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//当view为空时,根据request设置默认view,如Handler返回值为void
this.applyDefaultViewName(processedRequest, mv);
//执行相应Interceptor的postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
//处理返回结果,包括处理异常、渲染页面,发出完成通知触发Interceptor的afterCompletion
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
// 删除上传资源
this.cleanupMultipart(processedRequest);
}
}
}
解析流程:
doDispatcher首先检查是不是上传请求,如果是则将request转换为MultipartHttpServletRequest,并将multipartRequestParsed标志设置为true。
然后通过getHandler获取Handler处理器链HandlerExecutionChain,具体的获取过程,后面分析HandlerMapping时再进行详细分析。
接下来处理GET、HEAD请求的Last-Modified,当浏览器第一次跟服务器请求资源时,服务器在返回的请求头里会包含一个last-Modified的属性,代表本资源最后是什么时候修改的。在浏览器以后发送请求时会同时发送之前接收到的last-modified,服务器接收到带Last-modified的请求后会用其值和自己实际资源的最后修改时间作对比,如果资源过期了则返回新的资源(同时返回新的last-modified),否则直接返回304状态吗表示资源未过期,浏览器直接使用之前缓存的结果。
接下来依次调用相应的Interceptor的preHandle。处理完preHandle后就到了此方法最关键的地方——让HandlerAdapter使用Handler处理请求,Controller就是在这执行的,具体内容在分析HandlerAdapter时在详细解释。
Handler处理完请求后,如果需要异步处理则直接返回,如果不需要异步处理,当view为空时,设置默认view,然后执行相应的Interceptor的postHandle。
至此,请求处理的内容就完成了,接下来使用processDispatchResult方法处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。
知识点集锦:
1》 Handler:处理器,他直接对应着MVC中的C,也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法,因为它的定义是Object,我们在方法中标注的@RequestMapping的所有方法都可以看成一个Handler,只要可以实际处理请求的都可以看成Handler
2》HandlerMapping:用来查找Handler的,在SpringMVC中会处理很多请求,每一个请求都需要一个Handler来处理,具体接受到请求后需要哪一个Handler来处理,就需要HandlerMapping来做了。
3》HandlerAdapter:适配器,不同的Handler需要找到不同HandlerAdapter来调用Handler。就如工厂里需要使用工具干活的人(HandlerAdapter)使用工具(Handler)干活,而HandlerMapping用于根据需要干的活找到相应的工具。
processDispatchResult方法主要用来处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。处理的异常是在处理请求doDispatch方的过程中产生的。
这里的View和ViewResovler与Handler和HandlerMapping相似,View就是模板用来展示数据的;ViewResovler就是用来查找数据的,总体来看就是HandlerMapping找到干活的Handler,找到使用的HandlerAdapter,让HandlerAdapter使用Handler干活,干完活后将这个结果写个报给交上去(通过View展示给用户)。
源码:
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) {
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException)exception).getModelAndView();
} else {
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}
//渲染页面
if (mv != null && !mv.wasCleared()) {
this.render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
}
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
//请求处理完,触发Interceptor的afterCompletion
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale(); //这里使用localResoler
response.setLocale(locale);
String viewName = mv.getViewName();
View view;
if (viewName != null) {
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request); //这里使用了ViewResoler获取到实际的View
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
}
} else {
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
} catch (Exception var8) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var8);
}
throw var8;
}
}
上面所有的分析可以通过下面的一张图来做总结,中间是doDispatcher的处理流程图,左边是Interceptor相关处理方法的调用位置,右边是doDispatcher方法处理过程中所涉及的组件。图中上半部分的处理请求对应着MVC的Controller即C层,下半部分的processRequestResult主要对应MVC中的View即V层,M层也就是Model贯穿于与整个过程中。