之前分析了,SpringMVC启动-源码跟踪,这里分析下SpringMVC是如何处理请求的。我们知道Servlet的处理请求的入口方法是:
public void service(ServletRequest req, ServletResponse res);
以一个Http get请求为例,绘制如下流程图:
- 首先请求的入口是HttpServlet
#service(ServletRequest req, ServletResponse res);
- 然后HttpServlet,将请求参数转换为
HttpServletRequest ,HttpServletResponse
,然后调用service(HttpServletRequest req, HttpServletResponse resp)
,并通过请求类型转发给不同类型的处理方法。
//javax.servlet.http.HttpServlet
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
//省略....
doGet(req, resp);
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} //省略其他类型请求......
else {
//异常处理
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
- 接上面根据不同的请求类型,交由各类型对应的方法,由于FrameworkServlet重写了doGet()等方法,所以交由FrameworkServlet的
doGet()
处理,然后由交由processRequest()
继续后续处理。
//org.springframework.web.servlet.FrameworkServlet
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
- FrameworkServlet
processRequest()
处理后,交由doService()
继续处理。
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
- 上述
doService()
方法是个abstract方法,所以这里由DispatcherServlet处理,处理完毕后交由doDispatch()
进行结果返回。
请求的大致流程已经分析完毕,其中HttpServlet的处理比较简单,下面主要分析下FrameworkServlet
和DispatcherServlet
。
FrameworkServlet
FrameworkServlet 重写了doGet,doPost,doPut,doDelete,doTrace等方法。并且将这些方法全部上交给processRequest()
处理。
processRequest方法
processRequest是一个final
方法,这说明它不允许被重写。
//org.springframework.web.servlet.FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取当前请求(线程)的LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//重新设置当前请求的LocaleContext
LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
//获取当前请求的RequestAttributes
RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = null;
if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
//将RequestAttributes转换为ServletRequestAttributes
requestAttributes = new ServletRequestAttributes(request);
//重新设置当前请求的requestAttributes
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
//try-catch省略...
//实际处理入口
doService(request, response);
finally {
// 恢复原来的previousLocaleContext
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
if (requestAttributes != null) {
//恢复原来的previousRequestAttributes
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
//清空requestAttributes
requestAttributes.requestCompleted();
}
//省略....
if (this.publishEvents) {
//无论成功与否,发布ServletRequestHandledEvent消息。
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause));
}
}
}
requestAttributes.requestCompleted()
//org.springframework.web.context.request.AbstractRequestAttributes
/**
* 标志请求结束
* <p>Executes all request destruction callbacks and updates the
* session attributes that have been accessed during request processing.
*/
public void requestCompleted() {
//执行request拆除回调函数,并清空这些回调信息。
executeRequestDestructionCallbacks();
//更新session中所有受影响的属性
updateAccessedSessionAttributes();
//设置request无效
this.requestActive = false;
}
private void executeRequestDestructionCallbacks() {
synchronized (this.requestDestructionCallbacks) {
for (Runnable runnable : this.requestDestructionCallbacks.values()) {
runnable.run();
}
this.requestDestructionCallbacks.clear();
}
}
ServletRequestHandledEvent
无论成功与否,最后都会发布ServletRequestHandledEvent
事件,可以自定义ApplicationListener
进行监听。注意需要将该Listener注册到spring容器中
public class ServletRequestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> {
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
System.out.println(event.getDescription());
}
}
DispatcherServlet
DispatcherServlet是SpringMVC 最核心的类,整个处理过程的顶层设计都在这里。经过上面的分析,可以得知DispatcherServlet 方法的入口是:doService()
doservice()
//org.springframework.web.servlet.DispatcherServlet
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//如果是<jsp:include>include请求,快照记录当前的attributes,并在处理完成之后进行还原。
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
logger.debug("Taking snapshot of request attributes before include");
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));
}
}
}
//设置flashMap相关属性,主要用于Redirect转发时参数的传递。
this.flashMapManager.requestStarted(request);
// 对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());
try {
doDispatch(request, response);
}
finally {
//"output" FlashMap保存在底层存储,并开始进行倒计时。默认180s.
this.flashMapManager.requestCompleted(request);
// 当时include请求时,恢复之前备份的属性
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
FlashMap的作用:是在redirect请求时传递参数,
outputFlashMap
:用于保存本次请求需要转发的属性。
inputFlashMap
:用于保存上次请求转发过来的属性。
它的使用方式如下:
@RequestMapping("/submit")
public String submit(RedirectAttributes attr) {
//获取FlashMap方法1
((FlashMap) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest().getAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name", "特朗普传");
//获取FlashMap方法2:RedirectAttributes.addFlashAttribute::参数放到outputFlashMap
attr.addFlashAttribute("orderId", "9901");
//获取FlashMap方法3:RedirectAttributes.addAttribute ::参数会拼接到url后面。
attr.addAttribute("price", "zh-cn");
return "redirect:detail";
}
@RequestMapping("/detail")
public String detail(Model model) {
System.out.println(model.asMap());
return "success";
}
doService
主要对request设置了一些属性之后,最终将请求转发给doDispatch()
方法。
doDispatch()结构
doDispatch的方法也非常简洁,从顶层设计了整个请求的处理过程。doDispatch的最核心代码只有4句,它们的任务分别是:
- 根据request找到Handler
- 根据Handler找到对应的HandlerAdapter
- 用HandlerAdapter处理Handler
- 处理上面处理之后的结果(包含找到View并渲染给用户),最终调用afterCompletion
对应代码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//确定当前请求的Handler
mappedHandler = getHandler(processedRequest, false);
//确定当前Handler的HandlerAdapter .
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//HandlerAdapter 执行Handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//触发整个请求处理完毕回调方法afterCompletion
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
HandlerMapping :是用来寻找Handler的,可以理解为请求
Handler:也就是处理器,可以理解为Controller层。可以是类,也可是是方法。
HandlerAdapter:适配器。Handler处理请求可以是任意形式的,但是Servlet处理方法却是固定形式的,都是以request,response为参数的方法,怎么让Servlet调用灵活的Hander来进行处理呢,HandlerAdapter就是干这个事的。
doDispatch()详解
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
try {
ModelAndView mv;
boolean errorView = false;
try {
//检查是不是上传请求,如果是转换为MultipartHttpServletRequest
processedRequest = checkMultipart(request);
//根据request找到Handler
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//根据Handler找到HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理GET,HEAD请求的last-modified header
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;
}
}
//执行Handler注册的interceptors中的preHandle方法
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
// HandlerAdapter使用Handler处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//当View为空时(比如放回方法是Void时),根据request设置默认View
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
////执行Handler注册的interceptors中的postHandle方法(逆序)
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
}
catch (ModelAndViewDefiningException ex) {
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
}
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//处理异常请求。
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
//渲染视图。
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
//省略log....
}
// 触发整个请求处理完毕回调方法afterCompletion
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}//省略try-catch
finally {
//删除上传请求的资源
if (processedRequest != request) {
cleanupMultipart(processedRequest);
}
}
}
拆分上述方法,
getHandler()
getHandler()找到请求的HandlerExecutionChain ,执行过程按照顺序执行preHandler
方法,返回时逆序执行postHandler.
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
//省略log...
//寻找到HandlerMapping匹配到的第一个HandlerExecutionChain。
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
GET,HEAD请求的Last-Modified
当浏览器第一次跟服务器请求资源(GET,HEAD请求
)时,服务器返回的响应头中包含一个Last-Modified
属性,代表本资源最后是什么时候修改的。在浏览器以后发送请求时会同时发送之前接收到Last-Modified
,服务器接收到带有Last-Modified
的请求时会用其值和自己实际资源修改时间作比较,如果过期了则返回新的资源(同时返回新的Last-Modified
),否则返回304状态码表示资源未过期,浏览器直接使用之前缓存的结果。
triggerAfterCompletion()
private void triggerAfterCompletion(HandlerExecutionChain mappedHandler,
int interceptorIndex,
HttpServletRequest request,
HttpServletResponse response,
Exception ex) throws Exception {
// 触发整个请求处理完毕,回调interceptor的afterCompletion方法 .
if (mappedHandler != null) {
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}