spingmvc之整体处理流程
一. 概述
springmvc对请求的处理主要是围绕DispatchServlet展开的。
一个请求进来以后,首先经过tomcat的一系列处理,然后根据不同的请求类型,servlet不同的方法,例如get请求调用doGet()
。springmvc在处理该请求的过程中,先找到处理该请求的Handler
,即我们编写的业务方法;接着找到调用该Handler
的HandlerAdapter
;然后执行handler
;接着利用ViewResolver
对视图进行解析,最后封装成response返回给浏览器。
Handler
可以表现为多种形式,我们最常用的即添加了@RequestMapping
的方法
HandlerAdapter
是用来执行不同Handler
的适配器,每一种Handler
都需要一种适配器,通常用的是RequestMappingHandlerAdapter
视图层
并不是必须的,当我们使用@RequestBody
时,会在调用方法的时候就返回response,后续的视图解析就不进行了。
二. 整体流程
2.1 FrameworkServlet
在FrameworkServlet中重写了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法。最后所有需要自己处理的请求都交给了processRequest方法进行统一处理。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//设置LocaleContext信息
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
//设置RequestAttributes,可以用来获取request和session的属性
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 {
//模板方法,被DispatchServlet重写
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
//恢复了LocaleContextHolder和RequestContextHolder,在Sevlet外面可能还有别的操作,如Filter(Spring-MVC自己的HandlerInterceptor是在doService内部的)等,为了不影响那些操作,所以需要进行恢复
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
//发布了ServletRequestHandledEvent事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
这个方法,主要做了两件事情:
- 对
LocaleContext
和RequestAttributes
的设置及恢复 - 处理完后发布了
ServletRequestHandledEvent
消息
RequestAttribute属性如下,主要是包含了request和session的信息,放在RequestContextHolder
中,可以直接调用
Locale信息被放到了LocaleContextHolder
中,因此可以用LocaleContextHolder.getLocale()
直接获取到Locale信息。
小技巧:通过监听
ServletRequestHandledEvent
事件,可以在请求处理完成后,实现一些功能,比如日志的记录@Component public class ServletRequestHandlerEventListener { @EventListener(ServletRequestHandledEvent.class) public void servletListener(ServletRequestHandledEvent event) { System.out.println(event.getRequestUrl()); System.out.println(event.getClientAddress()); System.out.println(event.getServletName()); System.out.println(event.getDescription()); } }
输出如下:
/favicon.ico 0:0:0:0:0:0:0:1 dispatcherServlet url=[/favicon.ico]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[4B300AAD654DDBCA03C2B3112B2F8C20]; user=[null]; time=[14ms]; status=[OK]
2.2 DispatchServlet
2.2.1 doService
DispatchServlet重写了doService
方法
doService()
对请求做了一些额外的处理,主要有如下几点:
-
如果是
Include
请求,则对request的Attribute做个快照备份,等doDispatch处理完之后(如果不是异步调用且未完成)进行还原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)); } } }
-
设置了7个request的属性
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()); 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); }
-
WEB_APPLICATION_CONTEXT_ATTRIBUTE
=org.springframework.web.servlet.DispatcherServlet.CONTEXT
添加了springmvc容器
AnnotationConfigServletWebServerApplicationContext
,方便使用 -
LOCALE_RESOLVER_ATTRIBUTE
=org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER
添加了
localeResolver
-
THEME相关的两个,添加了主题相关的解析器和来源
-
后面3个属性与FlashMap相关,用来重定向时传递参数,之后详细分析
-
-
调用
doDispath
进一步处理请求 -
对
include
请求中request进行还原
2.2.2 doDispatch
doDispath中即使用到了前面提到的9大组件对请求进行处理,整体的处理逻辑还是比较清晰的,只是使用了九大组件对请求进行拆分。
先整体对该方法的逻辑有一个了解,对于一个请求的处理,主要分为以下几步
- 先判断是不是上传文件请求,如果是request转为专门的处理上传请求的
MultipartHttpServletRequest
- 根据request找到对应处理请求的
handler
- 根据handler的类型,找到对应的适配器
HandlerAdapter
- 如果有拦截器,执行拦截器的
preHandle()
- 使用适配器调用handler的逻辑,处理业务,得到
ModelAndView
对象 - 如果有拦截器,执行拦截器的
postHandle()
- 对处理请求过程中的异常进行处理
- 如果
ModelAndView
需要渲染,则进行视图的渲染 - 如果有拦截器,执行拦截器的
afterCompletion()
- 最后,如果是上传请求,做一些清理工作
小提示
:
- 处理请求的异常(即Handler中的异常)和渲染视图的异常是分开的
- 中间如果有任何一环发生异常,都会执行完拦截器的
afterCompletion
后再抛出
三. 使用技巧
3.1 关于拦截器的使用
拦截器是springmvc提供给我们的不同于Filter
的介入请求生命周期的方式
由上述的分析可知拦截器三个方法生效的具体时机
- preHandler(): 发生在Handler处理业务之前,并且如果返回false,会直接进入afterCompletion
- postHandler():发生在Handler处理业务之后,视图渲染之前
- afterCompletion(): 发生在视图渲染之后,如果中间有异常发生,也会调用
根据触发的时机,我们只要实现HandlerInterceptor
接口,即可注入自定义的拦截器
如果是异步请求,可以实现
AsyncHandlerInterceptor
接口,此时就不会执行postHandler
和afterConcurrentHandlingStarted
了,而是会在异步执行handler的同时,执行afterConcurrentHandlingStarted
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } }
下面定义一个基于注解的拦截器的抽象类,方便业务使用
@Slf4j
public abstract class AbstractAnnotationInterceptor<T extends Annotation> extends HandlerInterceptorAdapter {
private Class<T> clazz;
public AbstractAnnotationInterceptor(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
if (!(handler instanceof HandlerMethod)) {
return;
}
T anno = getAnnotationObj(handler, clazz);
if (null != anno) {
doPostHandle(request, response, (HandlerMethod) handler, anno, modelAndView);
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
if (!(handler instanceof HandlerMethod)) {
return;
}
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
T anno = getAnnotationObj(handler, clazz);
if (null != anno) {
doAfterCompletion(request, response, (HandlerMethod) handler, anno, exception);
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
T annotationObj = getAnnotationObj(handler, clazz);
if (null == annotationObj) {
return true;
}
return doPreHandle(request, response, (HandlerMethod) handler, annotationObj);
}
protected <A extends Annotation> A getAnnotationObj(Object handler, Class<A> clazz) {
A annotationObj = null;
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
annotationObj = method.getBean().getClass().getAnnotation(clazz);
if (annotationObj == null) {
annotationObj = method.getMethodAnnotation(clazz);
}
} else {
annotationObj = handler.getClass().getAnnotation(clazz);
}
return annotationObj;
}
protected void doPostHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, T anno, ModelAndView modelAndView) {
}
protected void doAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, T anno, Exception exception) {
}
protected boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, T anno) throws Exception {
return true;
}
}
3.2 关于过滤器的使用
-
过滤器Filter是Servlet提供给我们的组件,用于对请求和响应进行处理。
-
Filter的触发时机是在servlet处理之前,和servlet处理完毕之后
-
Servlet API提供了一个Filter接口,编写的过滤器必须实现该接口。
-
实现URL级别的权限访问控制,过滤敏感词汇,压缩响应信息等。
拦截过程:
- 当客户端发生请求后,在HttpServletRequest 到达Servlet 之前,过滤器拦截客户的HttpServletRequest 。
- 根据需要检查HttpServletRequest ,也可以修改HttpServletRequest 头和数据。
- 在过滤器中调用doFilter方法,对请求放行。请求到达Servlet后,对请求进行处理并产生HttpServletResponse发送给客户端。
- 在HttpServletResponse 到达客户端之前,过滤器拦截HttpServletResponse 。
- 根据需要检查HttpServletResponse ,可以修改HttpServletResponse 头和数据。
- 最后,HttpServletResponse到达客户端。
Filter接口中有三个重要的方法。
- init()方法:初始化参数,在创建Filter时自动调用。当我们需要设置初始化参数的时候,可以写到该方法中。
- doFilter()方法:拦截到要执行的请求时,doFilter就会执行。这里面写我们对请求和响应的预处理。
- destroy()方法:在销毁Filter时自动调用。