文章目录
1.引言
在之前的文章中使用拦截器解析HttpServletRequest请求头参数构件Http上下文实现了优雅获取请求头参数,在学习记录的过程中,对Http请求的底层处理流程上不是很熟悉,在此通过对Servlet规范学习和Spring对Servlet的代码实现代码追踪加深一下自己的了解,同时也为后续过滤器和拦截器作在http调用链中的作用节点和区别的学习做下铺垫。
2.Servlet
- 为什么要了解Servelt?
Spring的Web框架就是基于Servlet实现的。所以要分析Spring MVC,首先应追根溯源,弄懂Servlet。
- Servlet是什么?
Servlet是J2EE规范的一部分,是一种运行在Web服务器端的小型java程序。Servlet是按照Servlet规范编写的一个java类,用于交互 式地浏览和修改数据,生成web动态内容。在遵守Servlet规范的前提下,我们可讲Web应用部署在Servlet容器下。
-
使用Servlet的好处?
开发者可以聚焦业务逻辑,而不用关心Http协议方面的事情。我们给予Servlet规范实现Web应用的话,HTTP协议的处理过程就不需要我们参与了。这些工作交给Servlet容器(tomcat和jetty)去做就行了,我们只需要关系业务逻辑怎么实现。
3 Servlet规范的实现
a.交互过程
在Servlet完成初始化后,针对外部对Servlet的每次请求,Servlet容器就可以使用它处理客户端请求了。
- 客户端请求由ServletRequest类型的对象请求对象表示。
- Servlet封装响应并返回给请求的客户端,该响应由ServletResponse类型的对象表示
- 这两个对象是由容器通过参数传递到Servlet接口的Service方法
在HTTP请求的场景下,容器提供的请求和响应对象具体类型分别是HttpServletRequest和HttpServletResponse。
b. 容器对Servlet规范的实现
tomcat的javax.servlet-api包中的HttpServlet,找到其中的service方法
-
完成对请求和跟返回对象的验证,确保是按照Servlet规范来的。
//javax.servlet.http.HttpServlet.java @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; //验证请求跟返回信息封装对象的类型是不是servlet规范中定义的类型的 //否则抛异常 if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } //转为HttpServletRequest跟HttpServletResponse类型 request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response); }
-
对请求中的请求类型进行判断,来选择后面的逻辑
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求的方法值 String method = req.getMethod(); //判断是不是get请求 if (method.equals(METHOD_GET)) { //判断之前这个请求对象的最后修改时间是否修改过 long lastModified = getLastModified(req); //如果值为-1,表示当前的servlet对象不支持请求头中的if-modified-since,这时候就需要调用后面的代码逻辑,代价高 if (lastModified == -1) { doGet(req, resp); } else { //如果不是-1,表示支持,然后获取请求头中的if-modified-since参数 long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); //如果请求头中的时间小于上次的请求时间,则表示页面需要进行刷新 if (ifModifiedSince < lastModified) { //设置返回体中的Last-Modified参数 maybeSetLastModified(resp, lastModified); //进行后面的逻辑 doGet(req, resp); } else { //如果两个时间相等则设置返回体的请求状态为304 resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { //获取上次的修改时间 long lastModified = getLastModified(req); //设置修改时间 maybeSetLastModified(resp, lastModified); //进行后面逻辑 doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
c. Spring对Servlet规范的实现
-
Spring中对Servlet规范的实现类是FrameworkServlet类。继承了HttpServletBean类,对get、post等方法进行实现。
//HttpServletBean继承了HttpServlet类 public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { //省略... @Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //进行请求的处理 processRequest(request, response); } @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //进行请求的处理 processRequest(request, response); } //省略... }
-
主要处理逻辑在processRequest方法中,主要处理逻辑是准备请求的上下文和参数。
//FrameworkServlet.java protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取当前系统时间,用来计算这个步骤的处理耗时 long startTime = System.currentTimeMillis(); Throwable failureCause = null; //获取当前服务运行所在地区,在RequestContextFilter中进行处理设值 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); //创建相关地区的对象的LocalContext, LocaleContext localeContext = buildLocaleContext(request); //获取请求的属性,请求相关属性会在RequestContextHolder中,它是用ThreadLocal实现的 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); //创建requestAttributes,ServletRequestAttributes是RequestAttributes子类 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); //从request获取WebAsyncManager,在filter阶段会创建WebAsyncManager,表示是不是异步相应的请求 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); //将FrameworkServlet的内部类RequestBindingInterceptor设置到asyncManager中,用于在异步中初始化跟重新设置FrameworkServlet asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //将localeContext跟requestAttributes设置到LocaleContextHolder跟RequestContextHolder中 initContextHolders(request, localeContext, requestAttributes); try { //进行业务处理 doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { //移除ThreadLocal中的请求相关信息,attribute跟context信息 resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } //打印结果 logResult(request, response, failureCause, asyncManager); //发布请求处理完成事件 publishRequestHandledEvent(request, response, startTime, failureCause); } }
-
processRequest中调用了负责业务处理的doService方法,它是一个抽象方法,必须由子类实现。FrameWorkServlet只有一个子类DispatchServlet,其中doService方法实现如下:
//DispatchServlet.java protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); //如果是一个include请求,<jsp:incluede page="xxx.jsp"/> 这种 //可能在一个请求中嵌套了另外的一个请求,因此需要备份当前请求 Map<String, Object> attributesSnapshot = null; //是否是一个include请求,通过request中的javax.servlet.include.request_uri属性判断 //JSP在运行期间是会被编译成相应的Servlet类来运行的, 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)); } } } //设置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 { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
-
doService实现中的核心是doDispath方法
//DispatchServlet.java 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); //根据request中的url从hanlderMapping中获取 //封装了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类型请求则直接返回。 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //调用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()) { //如果mappedHandler不是null,则调用对应的mappedHandler中的AsyncHandlerInterceptor if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { //对流类型的请求,做后置的处理 if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
- 检查当前请求类型是否为multipart/form-data类型,是的话转换为MultipartHttpServletRequest类型的request
- 解析url,从handleMapping对象中找到对应的封装了HandlerInterceptor的HandlerExecutionChain对象,没有找到就返回404
- 从HandlerExecutionChain中获取Handler,然后寻找合适的HandlerAdapter
- 检查是不是get请求,然后检查lastModified属性判断继续处理还是返回
- 调用HandlerAdapter的applyPreHandle方法,取到所有拦截器后,遍历执行它们的preHandle方法
- 调用前面选择的HandlerAdapter的handle方法,进行逻辑的处理,返回ModelAndView对象
- 检查当前请求是否在异步处理,如果是直接返回
- 检查当前视图是否为空,不是则返回,否则返回默认的ModelAndView对象
- 调用HandlerAdapter的applyPostHandle方法,取到所有拦截器后,遍历执行它们的postHandle方法
- 对正常返回的或是发生异常时生成的视图处理
- 对异步请求或者multipart/form-data类型请求进行后续处理
d. Controller对接Servlet
-
Controller是怎么被调用和做出响应的?
Spring容器启动时会加载这些Controller类,并解析出URL对应的处理函数,封装成Handler对象,存储到HandlerMapping中。当有请求到来时,DispatchServlet从HandlerMapping中查找URL对应的Handler,然后调用Handler对应的函数代码,最后将执行结果返回给客户端。
-
为什么要将Controller封装成Handler对象?
controller的定义方式有多种:
//方式一:通过@Controller、@RequestMapping来定义 @Controller public class DemoController { @RequestMapping("/getUserName") public ModelAndView getUserName() { ModelAndView model = new ModelAndView("Greeting"); model.addObject("message", "TOM"); return model; } } //方式二:实现Controller接口 + xml配置文件:配置DemoController与URL的对应关系 public class DemoControllerImpl implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView model = new ModelAndView("HelloWorld"); model.addObject("message", "HelloWorld"); return model; } } //方式三:继承HttpServlet抽象类 + xml配置文件:配置DemoExtendServletController类与URL的对应关系 public class DemoExtendServletController extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("Hello World!"); } }
可以看出函数的定义很随意,如果不做适配,DIspatchServlet没有办法做统一调用,而且在有接口新增时,要在DispatchServlet类代码中增加判断逻辑,不符合开闭原则。Spring利用适配器模式对代码进行统一改造,定义了统一接口的HandlerAdapter,并对每种类型的Controller定义了对应的适配类。这样在DispatchServlet类中,就不需要区分对待不同的Controller对象了,统一调用HandlerAdapter的handle对象就可以了。