当前位置: 首页 > news >正文

Spring进阶 - SpringMVC实现原理(二)DispatcherServlet处理请求的过程

前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第二篇:DispatcherServlet处理请求的过程的源码解析。

这篇文章能帮你理清楚 JVM、Tomcat、Spring 宏观上的关系。 另外 Servlet ,HttpServlet , ServletRequest, HttpServletRequest 等类,能看到 SpringMVC 对他们的实现与增强,以及SpringMVC处理请求的流程。

回顾整理处理流程

首先让我们整体看一下Spring Web MVC 处理请求的流程:

img

  1. 首先用户发送请求——>DispatcherServlet前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行 处理,作为统一访问点,进行全局的流程控制;
  2. DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求(如 user/list)映射为 HandlerExecutionChain 对象(包含一 个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
  3. DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器(适配器设计模型,便于调用),从而支持多种类型的处理器, 即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  4. HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法(Controller 方法),完成功能处理;并返回一个ModelAndView 对象, 前后端分离项目返回的是一个 业务对象(如:List );
  5. ModelAndView 的逻辑视图名——> ViewResolver,ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术,**如果是前后端分离项目,此步骤跳过,**没有 ModelAndView 对象,那就没东西解析;
  6. View——>渲染,View 会根据传进来的Model 模型数据进行渲染,此处的Model 实际是一个Map 数据结构,因此很容易支持其他视图技术;
  7. 返回控制权给DispatcherServlet,由DispatcherServlet 返回响应给用户,到此一个流程结束。

JVM 、 Tomcat 、 Spring宏观关系

JVM 是一个进程,Spring boot是一个库(框架),运行在 JVM 之上,tomcat 是内嵌在 Spring Boot 中的,所以本质上就一个 JVM 进程,Spring Boot 和 tomcat 是两个库。

假如没有Spring Boot , 传统的 tomcat 是需要一个 JVM 进程单独启动,不细说。

tomcat 如何提供网络能力呢?这个库为啥能监听网络请求?

本质上是调用了OS操作系统的 API, 一个请求发送到我们的服务器,最先接收到的是网卡,然后操作系统会把请求的数据放到 socket 接收缓冲区,tomcat 具有能够调用操作系统 API 的方法,所以就可以监听操作系统socket缓冲区是否有数据。

1. 用户浏览器发送 HTTP 请求↓
2. 操作系统网络层接收(TCP/IP)↓
3. Tomcat 通过 JVM 调用操作系统的网络 API(通常是通过 Java NIO 或传统的 Socket API)4. Tomcat 解析 HTTP 请求↓
5. Tomcat 找到对应的 Servlet6. 调用 Servlet 的方法(如 doGet)↓
7. 返回响应

聊完了这个我们再看看 Servlet , HttpServlet 和 SpringMVC 的关系。

Servlet , HttpServlet 和 SpringMVC

Servlet 是 JavaEE(Jakarta EE)规范里定义的一个接口, 定义了所有 Web 组件(Servlet)的最基本行为 。 比如最基本的生命周期方法:init(), service(), destroy()

HttpServletServlet 接口的一个抽象实现类,理解成对 Servlet 的能力扩展, 专门为 HTTP 协议做了适配 。

Servlet 有一个方法是 Servlet.service(ServletRequest, ServletResponse) ,Servlet 和 ServletRequest 之间有点像调用关系。没有继承关系或者实现关系。

ServletRequest 它是 java 的一个接口, 封装一次请求的数据(不限于 HTTP,可以是任意协议,比如最早还有 FTP、SMTP 的扩展)。

HttpServletRequest 也是 java 的接口, 继承了 ServletRequest 接口,作为它的能力扩展。新增了 HTTP 特有的方法,比如 getMethod() ——> 获取请求方法(GET/POST/PUT/DELETE …) , getCookies(), getSession() 。

以上都是Java规范,接口或者抽象类,Tomcat 对它们都有实现。

tomcat 有 StandardWrapper ,定位和 Servlet 一样,管理容器的生命周期。

tomcat 有 DefaultServlet , 一个具体的 HttpServlet 子类,负责静态资源处理。

tomcat 有 Request 实现了 HttpServletRequest 接口。

关键

已经清晰了 Java 和 Tomcat 的关系,已经完全足够处理请求了,Spring MVC 有什么工作要做呢?

Spring MVC 并没有取代 Servlet,而是构建在 Servlet 之上,提供了一个更强大、更灵活、更易于开发的应用层框架。

我们知道 Spring MVC 核心是 DispatcherServlet这个组件,它本质上是 HttpServlet。也就是说 Spring 提供的一个实现了 HttpServlet 接口的类,跟 tomcat 抢活干?对,前面流程说了 tomcat 会把请求交给 DispatcherServlet 去跑流程,那DispatcherServlet 就要具有处理请求的能力呀,所以必须要实现 HttpServlet 接口。

Spring MVC 只是实现了 HttpServlet 而已?不需要实现 HttpServletRequest 接口吗?

HttpServletRequest 已经被 tomcat 实现了,Spring MVC 没有改动任何 HttpServletRequest ,拿过来直接用,**但是,**Spring MVC 使用了包装器模式对 HttpServletRequest 进行了增强。你使用的是增强版的HttpServletReqeust ,这个增强对开发者来说是透明的,表明上 HttpServletRequest 没有改变。内部有部分简化和增强。

另外Spring MVC 有一个MultipartHttpServletRequest 接口,实现了 HttpServletRequest 接口,让SpringMVC 处理请求时能接收文件。

最后,SpringMVC提供了一个 RequestContextHolder 工具类,鱼总用了多次,将包装后的 Request 对象存放在 **ThreadLocal**(线程上下文) 中,意味着即使在一个没有直接传入 **HttpServletRequest** 对象的深层 Service 方法里,只要你需要,你也可以通过 **RequestContextHolder.getRequestAttributes()** 来获取到当前请求的上下文信息。

下面我们从源码剖析。

doGet入口

我们启动一个 Spring Boot 项目,端口号为 8080 , 当有一个 get 请求(localhost:8080/api/health) ,Spring MVC 处理过程是怎样的?

  1. 用户发起 HTTP GET 请求
  2. Tomcat 接收请求,创建 Request/Response 对象
  3. Tomcat 调用已注册的 DispatcherServlet.doGet() 方方法。 执行权从 Tomcat 转移到 Spring。

我们就从这个 doGet() 方法开始看源码:

首先doGet()

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {processRequest(request, response);
}
  1. DispatcherServlet.doGet() 调用 processRequest(),执行 Spring 框架通用逻辑,如上下文管理
/*** Process this request, publishing an event regardless of the outcome.* <p>The actual event handling is performed by the abstract* {@link #doService} template method.*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 计算处理请求的时间long startTime = System.currentTimeMillis();Throwable failureCause = null;LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = buildLocaleContext(request);RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());// 初始化contextinitContextHolders(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 {// 重置contextresetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);}
}

最重要的就是调用doService方法,这个方法由DispatchServlet类实现。

SpringMVC 处理请求的流程

请求分发

  1. processRequest() 调用 doService(),控制权从通用框架转交给 MVC 专用,doDispatch方法是真正处理请求的核心方法,流程就到了 Spring MVC, 认真看源码,看看Spring MVC 如何处理请求。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 判断是不是文件上传类型的requestprocessedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 根据request获取匹配的handler.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 根据handler获取匹配的handlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 如果handler支持last-modified头处理String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 真正handle处理,并返回modelAndViewmv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}// 通过视图的prefix和postfix获取完整的视图名applyDefaultViewName(processedRequest, mv);// 应用后置的拦截器mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}// 处理handler处理的结果,显然就是对ModelAndView 或者 出现的Excpetion处理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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}

映射和适配器处理

对于 42 行真正的handle方法,我们看下其处理流程

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);
}

交给handleInternal方法处理,以RequestMappingHandlerAdapter这个HandlerAdapter中的处理方法为例

@Override
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);// Execute invokeHandlerMethod in synchronized block if required.if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No HttpSession available -> no mutex necessarymav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No synchronization on session demanded at all...mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;
}

上面 24 行,打个断点,我们熟悉的 Controller 控制器这这一步触发 ,原来 Spring MVC 中 Handler 处理器概念的技术实现用到了 Controller ,这知识点就接上了我们MVC概念的控制层。控制层会处理数据(Model) , MVC 两个概念都清晰了。当Controller return的时候,会返回 Json 数据给前端,这就属于渲染层了,前后端分离不会真的渲染 HTML 页面给前端了,仅仅是发送JSON数据。

img

来看看controller 具体怎么被执行

然后执行invokeHandlerMethod这个方法,用来对RequestMapping(usercontroller中的list方法)进行处理

*** Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}* if view resolution is required.* @since 4.2* @see #createInvocableHandlerMethod(HandlerMethod)*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// 重要:设置handler(controller#list)方法上的参数,返回值处理,绑定databinder等ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}// 执行controller中方法invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}
}

invokeAndHandle交给UserController中具体执行list方法执行

img

后续invoke执行的方法,直接看整个请求流程的调用链即可

img

执行后获得视图和Model

img

视图渲染

还是那句话,前后端分离没有视图渲染这个步骤,这以 JSP 项目为例讲解的完整 MVC 源码。

接下来继续执行processDispatchResult方法,对视图和model(如果有异常则对异常处理)进行处理(显然就是渲染页面了)

/*** Handle the result of handler selection and handler invocation, which is* either a ModelAndView or an Exception to be resolved to a ModelAndView.*/
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) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// 是否需要渲染视图if (mv != null && !mv.wasCleared()) {render(mv, request, response); // 渲染视图if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);}
}

接下来显然就是渲染视图了, spring在initStrategies方法中初始化的组件(LocaleResovler等)就派上用场了。

/*** Render the given ModelAndView.* <p>This is the last stage in handling a request. It may involve resolving the view by name.* @param mv the ModelAndView to render* @param request current HTTP servlet request* @param response current HTTP servlet response* @throws ServletException if view is missing or cannot be resolved* @throws Exception if there's a problem rendering the view*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.Locale locale =(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);View view;String viewName = mv.getViewName();if (viewName != null) {// We need to resolve the view name.view = resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isTraceEnabled()) {logger.trace("Rendering view [" + view + "] ");}try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "]", ex);}throw ex;}
}

后续就是通过viewResolver进行解析了,这里就不再继续看代码了,上述流程基本上够帮助你构建相关的认知了。

最后无非是返回控制权给DispatcherServlet,由DispatcherServlet 返回响应给用户。

最后的最后我们看下请求的日志:

21:45:53.390 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/011_spring_framework_demo_springmvc_war_exploded/user", parameters={}
21:45:53.400 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to tech.pdai.springframework.springmvc.controller.UserController#list(HttpServletRequest, HttpServletResponse)
22:51:14.504 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.view.JstlView - View name 'userList', model {dateTime=Fri Apr 22 21:45:53 CST 2022, userList=[tech.pdai.springframework.springmvc.entity.User@7b8c8dc]}
22:51:14.550 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.view.JstlView - Forwarding to [/WEB-INF/views/userList.jsp]
22:51:44.395 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK

参考文章

https://pdai.tech/md/spring/spring-x-framework-springmvc-source-2.html

http://www.dtcms.com/a/449813.html

相关文章:

  • SQL 面试高频:INNER JOIN vs LEFT JOIN 怎么考?
  • 【51单片机】【protues仿真】基于51单片机智能路灯控制系统
  • 3d模型免费素材网站wordpress如何修改行距
  • 再探类对象——C++入门进阶
  • 公司网站如何维护wordpress去掉title前空格及keywords最后的逗号
  • 仙居谁认识做网站的广告投放平台代理
  • 智慧餐厅管理系统qq群排名优化软件官网
  • InnoDB核心限制与应对策略
  • 贵州建设厅监理协会网站前后端分离实现网站开发
  • QNX 开发环境搭建
  • Java技术栈 —— 使用MinIO进行大文件分片上传与下载
  • `modprobe`命令 与 `KVM`模块 笔记251006
  • 山东省建设监理协会网站打不开赣州招聘网最新招聘
  • 贵阳网站建设设计个人网页设计作品集分析
  • 音乐介绍网站怎么做做暧暧小视频网站
  • 公网带宽1m能建设电商网站吗wordpress新建数据库
  • C57-断言函数assert
  • 网站的制作建站人汽车业务网站开发公司
  • 详解指针2
  • 第一章 :感知机(上)
  • 做网站都要会些什么设计网站建设合同书6
  • 网站开发工程师 能做什么响应式布局的概念
  • 反激开关电源
  • 长沙网站建设外贸0基础做电商从何下手
  • vs2015做网站做民宿需要和多家网站合作吗
  • 集团型网站建设室内设计平面图简单
  • 比利时网站后缀用php做的网站前后台模板
  • 视频网站如何做微信营销长春火车站和高铁站是一个站吗
  • steamdeck 龙神Ryujinx模拟器输入控制器无效
  • 彩票网站网站建设路由器上建网站