前面的文章已经讲了从一个请求在spring中的处理过程(从Servlet规范到FrameworkServlet)代码及流程图说明接下来就是,从FrameworkServlet
到DispatcherServlet
的部分进行分析了。
1.从FrameworkServlet
到DispatcherServlet
在FrameworkServlet
的processRequest
方法中,有调用doService
方法的这个步骤。这个方法是一个抽象方法,必须由子类来实现。这个方法是处理GET, POST, PUT , DELETE
请求具体逻辑的方法。
1.1 从FrameworkServlet
进入到DispatcherServlet
的processRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
......
//进行业务处理
doService(request, response);
......
}
所以我们可以直接到DispatcherServlet
中进一步的分析。直接看对应的doService
方法
1.2 进行真正请求处理前的准备doService
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.
//如果是一个include请求,<jsp:incluede page="xxx.jsp"/> 这种中,可能在一个请求(A)中嵌套了另外的一个请求(B),因此需要备份当前请求(A)
Map<String, Object> attributesSnapshot = null;
//是否是一个include请求,通过request中的javax.servlet.include.request_uri属性判断,JSP在运行期间是会被编译成相应的Servlet类来运行的,
// 所以在Servlet中也会有类似的功能和调用语法,这就是RequestDispatch.include()方法,在一个被别的servlet使用RequestDispatcher的include方法调用过的servlet中,
// 如果它想知道那个调用它的servlet的上下文信息该怎么办呢,那就可以通过request中的attribute中的如下属性获取:javax.servlet.include.request_uri
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
//获取包含的请求中的获取A请求的内部B请求设定的spring的策略
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
//设置web应用上下文到请求中,
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
//设置本地解析器
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
//设置主题解析器
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
//设置主题,如果没有设置则为null,默认的为WebApplicationContext
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
//将request跟response保存到一个FlashMap中,FlashMap用来将一个请求跟另外一个请求关联起来,通常在redirect的时候有用
if (this.flashMapManager != null) {
//如果当前请求的FlashMap在之前的请求中保存过了,则取出来,并去除对应的缓存
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
//保存FlashMap到属性中
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 {
//在FrameworkServlet中会生成WebAsyncManager
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
这里对上面的流程总结一下:
- 检查当前的请求中是否包含另外的一个请求(也就是请求包含),如果是这种情况需要把内部请求的属性作为快照保存起来。(个人对于这一块还是有点没有理解清楚的,欢迎指点)
- 设置对应的web请求上下,本地解析器,主题解析器,主题到request中
- 检查
flashMapManager
是不是null(这个不会为空,在initFlashMapManager
方法初始化的时候回创建),如果不是空则吧对应的request
,respons
设置进一个FlashMap
对象中,同时设置对应的输入,输出跟flashMap
管理对象到request
中 - 进行请求分发处理
doDispatch
方法 - 最后处理异步请求处理相关的逻辑,主要检查这个请求的异步处理逻辑是不是正在处理,是的就将上面保存的内部请求设置到
request
中。
这里涉及到请求包含(include)跟请求转发(forward)。关于这两个东西可以百度一下或者参考下面这个文章请求包含(Include)和请求转发(Forward)这个位置理解有点费力。个人理解可能也有点缺陷,欢迎指正。
在准备好了处理请求需要的相关环境跟参数的之后,就是进行请求的分发处理了,之所以叫分发是因为每个请求都有对应的处理类,现在进入到分发的逻辑方法doDispatch
1.3 进行请求分发的doDispatch
因为doDispatch
这个方法的内部逻辑比较复杂(方法内部调用了其他的复杂的方法逻辑),一篇博客肯定写不完,所以这里主要写整个方法内的逻辑。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//从request中获取WebAsyncManager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//检查是不是multipart请求并将请求转化为MultipartHttpServletRequest类型的
processedRequest = checkMultipart(request);
//如果请求不是原来的request请求,则表示是multipart请求并且解析过的
multipartRequestParsed = (processedRequest != request);
//获取封装了HandlerInterceptor的HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
//如果不存在对应的处理链则返回
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//从HandlerExecutionChain中获取Handler然后寻找合适的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//获取请求方式
String method = request.getMethod();
boolean isGet = "GET".equals(method);
//检查是不是GET请求
if (isGet || "HEAD".equals(method)) {
//检查当前的get类型的请求的最后修改时间是不是存在的,这个参数用来减少数据传输用
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
//如果浏览器请求中的最后请求时间跟服务器的最后修改时间一致,并且是get类型请求则直接返回。关于lastModified这个会说明
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//调用mappedHandler中拦截器的applyPreHandle方法,如果对应的请求不符合规则则直接返回
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//执行对应的HandlerAdapter的Handler方法,拿到对应的视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//检查当前的请求是否正在异步处理,如果是的则直接放弃并返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//如果处理的结果返回的视图是空的则使用默认的视图,不为空则用处理的结果
applyDefaultViewName(processedRequest, mv);
//调用applyPostHandle方法对视图进行返回后的处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
//进行错误视图的处理
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
//如果mappedHandler不是null,则调用对应的mappedHandler中的AsyncHandlerInterceptor
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
//对流类型的请求,做后置的处理
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
对于doDispatch
方法的处理流程就跟网上的mvc处理流程图一样的,把整个流程包含在了一个方法里面。这里就用网上的一张图具体说明
- 首先会检查当前的请求是不是
multipart/form-data
类型的请求,是的则将请求进行转换为MultipartHttpServletRequest
类型的request。(MultipartHttpServletRequest
是HttpServletRequest
的子类) - 获取合适的获取封装了
HandlerInterceptor
集合的HandlerExecutionChain
,如果没有找到合适的,就直接返回404的错误页面 - 从
HandlerExecutionChain
中获取Handler然后寻找合适的HandlerAdapter
- 检查是不是
get
请求,是的然后在检查lastModified
这个属性选择是直接返回还是进行后续的请求处理。(lastModified
这个属性后面说一下,其实前面文章已经说过了) - 调用前面选择的
HandlerAdapter
的applyPreHandle
方法检查请求是否符合定义的要求,不符合就返回。 - 调用前面选择的
HandlerAdapter
的handle
方法,进行逻辑的处理,然后返回ModelAndView
对象 - 检查当前的请求是否正在异步处理,如果是的则直接放弃并返回
- 检查返回的
ModelAndView
的视图是不是null
,是的则返回默认的,不是的则不处理 - 调用前面选择的
HandlerAdapter
的applyPostHandle
方法对视图进行最后的处理 - 对正常返回的或者发生异常时生成的视图进行处理,
- 对异步请求或
multipart/form-data
类型请求进行后续的处理
对于lastModified
这个属性可以参考这篇文章HttpServlet---getLastModified与缓存
后续的
虽然整个的request
请求的逻辑只有这么多,但是里面的每个步骤都是比较复杂的。这一篇讲不完,因此挑选里面比较重要的几个步骤进行分篇讲解。主要以下几个步骤
网友评论