了解 SpringMVC 请求流程

文章目录

  • 1. Spring 基础 - SpringMVC 请求流程
    • 1.1 引入
    • 1.2 什么是 MVC
    • 1.3 什么是 Spring MVC
    • 1.4 请求流程
      • 核心架构的具体流程步骤
      • 补充
    • 1.5 案例
      • **Maven 包引入**
      • **业务代码的编写**
      • Dao
      • Service
      • Controller
      • webapp 下的 web.xml
      • springmvc.xml
      • JSP 视图
  • 2. Spring 进阶 - DispatcherServlet 的初始化过程
    • 2.1 DispatcherServlet 和 ApplicationContext 有何关系?
    • 2.2 DispatcherServlet 是如何初始化的?
      • init
      • initWebApplicationContext
      • refresh
      • initHandlerxxx
  • 3. Spring 进阶 - DispatcherServlet 处理请求过程
    • 3.1 doGet 入口
    • 3.2 请求分发
    • 3.3 映射和适配器处理
    • 3.4 视图渲染

本文围绕 SpringMVC 请求流程进行展开,帮助进一步理解 SpringMVC。

1. Spring 基础 - SpringMVC 请求流程

前边我们学习了 Spring 框架和 Spring 框架中最为重要的两个技术点(IOP 和 AOP),那么如何更好的构建上层的应用呢(比如 web 应用),这便是 SpringMVC;Spring MVC 是 Spring 在 Spring Container Core 和 AOP 等技术基础上,遵循上述 Web MVC 的规范推出的 web 开发框架,目的是为了简化 Java 栈的 web 开发。本节主要围绕 SpringMVC 主要的流程,并编写基础案例。

1.1 引入

前边我们学习了 Spring 框架和 Spring 框架中最为重要的两个技术点(IOC 和 AOP)。

​ 那么,如何更好的构建上层的应用呢?比如 web 应用?

在这里插入图片描述

​ 针对上层的 Web 应用,SpringMVC 诞生了,它也是 Spring 技术栈中最为重要的一个框架。

通过如下问题构建对 SpringMVC 的认识。

  • Java 技术栈的 Web 应用是如何发展的?
  • 什么是 MVC,什么是 SpringMVC?
  • SpringMVC 主要的请求流程是什么样的?
  • SpringMVC 中还有哪些组件?
  • 如何编写一个简单的 SpringMVC 程序呢?

1.2 什么是 MVC

MVC 英文是 Model View Controller,是模型(model)- 视图(view)- 控制器(controller)的缩写,一种软件设计规范。本质上也是一种解耦。

​ 用一种业务逻辑、数据、界面显示分离的方法,将业务逻辑聚集到一个部件里面,在改进和个性化定制解码及用户交互的同时,不需要重新编写业务逻辑。MVC 被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

在这里插入图片描述

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
  • View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
  • Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从读取数据,控制用户输入,并向模型发送数据。

1.3 什么是 Spring MVC

简单而言,Spring MVC 是 Spring 在 Spring Container Core 和 AOP 等技术基础上,遵循上述 Web MVC 的规范推出的 web 开发框架,目的是为了简化 Java 栈的 web 开发。

​ Spring Web MVC 是一种基于 Java 的实现了 Web MVC 设计模式的请求驱动类型的轻量级 Web 框架,即使用了 MVC 框架模式的思想,将 web 层进行职责解耦,基于请求驱动指的是使用请求 - 响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC 也是要简化我们日常 Web 开发的。

相关特征如下

  • 让我们能非常简单的设计出干净的 Web 层和薄薄的 Web 层;
  • 进行更简介的 Web 层的开发;
  • 天生与 Spring 框架继承(如 IoC 容器、AOP 等);
  • 提供强大的约定大于配置的契约式编程支持;
  • 能简单的进行 Web 层的单元测试;
  • 支持灵活的 URL 到页面控制器的映射;
  • 非常容易与其它视图技术集成,如 Velocity、FreeMarker 等等,因为模型数据不放在特定的 API 里,而是放在一个 Model 里(Map 数据结构实现,因此很容易被其它框架使用);
  • 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的 API;
  • 提供一套强大的 JSP 标签库,简化 JSP 开发;
  • 支持灵活的本地化、主体等解析;
  • 更加简单的异常处理;
  • 对静态资源的支持;
  • 支持 Restful 风格。

1.4 请求流程

Spring Web MVC 框架也是一个基于请求驱动的 Web 框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器(动作/处理器)进行处理。

核心架构的具体流程步骤

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

在这里插入图片描述

核心架构的具体流程步骤如下:

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

补充

上述流程仅为核心流程,这里补充一些其它组件:

  1. Filter(ServletFilter)

​ 进入 Servlet 前可以有 preFilter,Servlet 处理之后还可有 postFilter。

  1. LocaleResolver

​ 在视图解析/渲染时,还需要考虑国际化(Local),显然这里需要有 LocalResolver。

  1. ThemeResolver

​ 如何控制视图样式呢?SpringMVC 中还涉及了 ThemeSource 接口和 ThemeResolver,包含一些静态资源的集合(样式及图片等),用来控制应用的视觉风格。

  1. 对于文件的上传请求?

​ 对于常规请求上述流程是合理的,但是如果是文件的上传请求,那么就不太一样了;所以这里便出现了 MultipartResolver。

1.5 案例

Maven 包引入

​ 主要引入 spring-webmvc 包(spring-webmvc 包中已经包含了 Spring Core Container 相关的包),以及 servlet 和 jstl(JSP 中使用 jstl)的包。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>io.zhanbo</groupId><artifactId>Spring</artifactId><version>0.0.1-SNAPSHOT</version><packaging>war</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><java.version>8</java.version><spring.version>5.3.9</spring.version></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><port>8080</port><path>/</path></configuration></plugin></plugins></build></project>

业务代码的编写

​ User 实体

public class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

Dao

@Repository
public class UserDaoImpl {public List<User> findUserList() {return Collections.singletonList(new User("zhanbo", 18));}
}

Service

@Service
public class UserServiceImpl {@Autowiredprivate UserDaoImpl userDao;public List<User> findUserList() {return userDao.findUserList();}
}

Controller

@Controller
public class UserController {@Autowiredprivate UserServiceImpl userService;@RequestMapping("/user")public ModelAndView list(HttpServletRequest request, HttpServletResponse response) {ModelAndView modelAndView = new ModelAndView();modelAndView.addObject("dateTime", new Date());modelAndView.addObject("userList", userService.findUserList());modelAndView.setViewName("userList"); // views 目录下 userList.jspreturn modelAndView;}
}

webapp 下的 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><display-name>SpringMVC</display-name><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 通过初始化参数指定 SpringMVC 配置文件的位置和名称 --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
</web-app>

springmvc.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"><!-- 扫描注解 --><context:component-scan base-package="io.zhanbo.spring.springmvc"/><!-- 静态资源处理 --><mvc:default-servlet-handler/><!-- 开启注解 --><mvc:annotation-driven/><!-- 视图解析器 --><bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/><property name="prefix" value="/WEB-INF/views/"/><property name="suffix" value=".jsp"/></bean>
</beans>

JSP 视图

​ 创建 userList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>User List</title><!-- Bootstrap --><link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body><div class="container"><c:if test="${!empty userList}"><table class="table table-bordered table-striped"><tr><th>Name</th><th>Age</th></tr><c:forEach items="${userList}" var="user"><tr><td>${user.name}</td><td>${user.age}</td></tr></c:forEach></table></c:if></div>
</body>
</html>

2. Spring 进阶 - DispatcherServlet 的初始化过程

前面我们有了 IOC 的源码基础以及 SpringMVC 的基础,我们便可以进一步深入理解 SpringMVC 主要实现原理,包含 DispatcherServlet 的初始化过程和 DispatcherServlet 处理请求的过程的源码解析。本小节围绕 DispatcherServlet 的初始化火车的源码解析。

2.1 DispatcherServlet 和 ApplicationContext 有何关系?

DispatcherServlet 作为一个 Servlet,需要根据 Servlet 规范使用 Java 配置或 web.xml 声明和映射。反过来,DispatcherServlet 使用 Spring 配置来发现请求、视图解析、异常处理等等所需的委托组件。那它和 ApplicationContext 有和关系呢?

​ DispatcherServlet 需要 WebApplicationContext(继承自 ApplicationContext)来配置。WebApplicationContext 可以链接到 ServletContext 和 Servlet。因为绑定了 ServletContext,这样应用程序就可以在需要的时候使用 RequestContextUtils 的静态方法访问 WebApplicationContext。

​ 大多数应用程序只有一个 WebApplicationContext,除此之外也可以一个 Root WebApplicaitonContext 配多个 Servlet 实例,然后各自拥有自己的 Servlet WebApplicationContext 配置。

​ Root WebApplicationContext 包含需要共享给多个 Servlet 实例的数据源和业务服务基础 Bean。这些 Bean 可以在 Servlet 特定的范围被集成或覆盖。

在这里插入图片描述

2.2 DispatcherServlet 是如何初始化的?

DispatcherServlet 首先是 Servlet,Servlet 有自己的生命周期的方法(init,destory 等),那么我们在看 DispatcherServlet 初始化时首先需要看源码中 DispatcherServlet 的类结构设计。

​ 首先我们看 DispatcherServlet 的类结构关系,在这个类依赖结构中找到 init 的方法。

在这里插入图片描述

​ 很容易找到 init() 的方法位于 HttpServletBean 中。

init

​ init() 方法如下,主要读取 web.xml 中 servlet 参数配置,并交给子类方法 initServletBean() 继续初始化

public final void init() throws ServletException {// 读取 web.xml 中的 Servlet 配置PropertyValues pvs = new ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {// 转换成 BeanWrapper,为了方便使用 Spring 的属性注入功能BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);// 注入 Resource 类型需要依赖于 ResourceEditor 解析,所以注册 Resource 类关联到 ResourceEditor 解析器ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));// 更多的初始化可以让子类进行拓展this.initBeanWrapper(bw);// 让 spring 注入 namespace,contextConfigLocation 等属性bw.setPropertyValues(pvs, true);} catch (BeansException ex) {if (this.logger.isErrorEnabled()) {this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", ex);}throw ex;}}// 让子类进行拓展this.initServletBean();
}

​ 通过断点,我们可以知道读取配置,正是初始化了我们 web.xml 中配置。

在这里插入图片描述

​ 再看下 initServletBean() 方法,位于 FrameworkServlet 类中

protected final void initServletBean() throws ServletException {this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("Initializing Servlet '" + this.getServletName() + "'");}long startTime = System.currentTimeMillis();try {// 最重要的是这个方法this.webApplicationContext = this.initWebApplicationContext();// 可以让子类进一步拓展this.initFrameworkServlet();} catch (RuntimeException | ServletException ex) {this.logger.error("Context initialization failed", ex);throw ex;}if (this.logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);}if (this.logger.isInfoEnabled()) {this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}}

initWebApplicationContext

​ initWebApplicationContext 用来初始化和刷新 WebApplicationContext。

​ 方法如下:

protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());WebApplicationContext wac = null;// 如果在构建函数已经被初始化if (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);}this.configureAndRefreshWebApplicationContext(cwac);}}}// 没有在构建函数中初始化,则尝试通过 contextAttribute 初始化if (wac == null) {wac = this.findWebApplicationContext();}// 还没有的话,只能重新创建了if (wac == null) {wac = this.createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {synchronized(this.onRefreshMonitor) {this.onRefresh(wac);}}if (this.publishContext) {String attrName = this.getServletContextAttributeName();this.getServletContext().setAttribute(attrName, wac);}return wac;
}

​ webApplicationContext 只会初始化一次,依次尝试构建函数初始化,没有则通过 contextAttribute 初始化,仍没有则会创建新的。

​ createWebApplicationContext 方法如下:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {Class<?> contextClass = this.getContextClass();if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");} else {// 通过反射方式初始化ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);wac.setEnvironment(this.getEnvironment());wac.setParent(parent);// 这里便是 springmvc.xmlString configLocation = this.getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}// 初始化 Spring 环境this.configureAndRefreshWebApplicationContext(wac);return wac;}
}

​ configAndRefreshWebApplicationContext 方法初始化设置 Spring 环境

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {// 设置 context IDif (ObjectUtils.identityToString(wac).equals(wac.getId())) {if (this.contextId != null) {wac.setId(this.contextId);} else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());}}// 设置 servletContext,servletConfig,namespace,listenerwac.setServletContext(this.getServletContext());wac.setServletConfig(this.getServletConfig());wac.setNamespace(this.getNamespace());wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());}// 让子类去拓展this.postProcessWebApplicationContext(wac);this.applyInitializers(wac);// Spring 环境初始化完了,就可以初始化 DispatcherServlet 处理流程中需要的组件了wac.refresh();
}

refresh

​ 有了 webApplicationContext 后,就开始刷新了(onRefresh 方法),这个方法是 FrameworkServlet 提供的模板方法,由子类 DispatcherServlet 来实现的。

protected void onRefresh(ApplicationContext context) {this.initStrategies(context);
}

​ 刷新主要是调用 initStrategies(context) 方法对 DispatcherServlet 中的组件进行初始化,这些组件就是在 SpringMVC 请求流程中包的主要组件。

protected void initStrategies(ApplicationContext context) {this.initMultipartResolver(context);this.initLocaleResolver(context);this.initThemeResolver(context);// 主要看如下三个方法this.initHandlerMappings(context);this.initHandlerAdapters(context);this.initHandlerExceptionResolvers(context);this.initRequestToViewNameTranslator(context);this.initViewResolvers(context);this.initFlashMapManager(context);
}

initHandlerxxx

​ 我们主要看 initHandlerXXX 相关的方法,它们之间的关系可以看 SpringMVC 的请求流程:

在这里插入图片描述

  1. HandlerMapping 是映射处理器
  2. HandlerAdapter 是处理器适配器,它用来找到你的 Controller 中的处理方法
  3. HandlerExceptionResolver 是当遇到处理异常时的异常解析器

​ initHandlerMapping 方法如下,无非就是获取按照优先级排序后的 HandlerMappings,将来匹配时按优先级高的 HandlerMapping 进行处理。

 private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerMappings);}} else {try {HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);} catch (NoSuchBeanDefinitionException var4) {}}if (this.handlerMappings == null) {this.handlerMappings = this.<HandlerMapping>getDefaultStrategies(context, HandlerMapping.class);if (this.logger.isTraceEnabled()) {this.logger.trace("No HandlerMappings declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");}}for(HandlerMapping mapping : this.handlerMappings) {if (mapping.usesPathPatterns()) {this.parseRequestPath = true;break;}}}

​ initHandlerAdapters 方法和 initHandlerExceptionResolvers 方法也是类似的,如果没有找到,那就构建默认的。

private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters = null;if (this.detectAllHandlerAdapters) {Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerAdapters = new ArrayList(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerAdapters);}} else {try {HandlerAdapter ha = (HandlerAdapter)context.getBean("handlerAdapter", HandlerAdapter.class);this.handlerAdapters = Collections.singletonList(ha);} catch (NoSuchBeanDefinitionException var3) {}}if (this.handlerAdapters == null) {this.handlerAdapters = this.<HandlerAdapter>getDefaultStrategies(context, HandlerAdapter.class);if (this.logger.isTraceEnabled()) {this.logger.trace("No HandlerAdapters declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");}}}private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}} else {try {HandlerExceptionResolver her = (HandlerExceptionResolver)context.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);this.handlerExceptionResolvers = Collections.singletonList(her);} catch (NoSuchBeanDefinitionException var3) {}}if (this.handlerExceptionResolvers == null) {this.handlerExceptionResolvers = this.<HandlerExceptionResolver>getDefaultStrategies(context, HandlerExceptionResolver.class);if (this.logger.isTraceEnabled()) {this.logger.trace("No HandlerExceptionResolvers declared in servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");}}}

3. Spring 进阶 - DispatcherServlet 处理请求过程

本节围绕 DispatcherServlet 处理请求过程的源码进行解析。

3.1 doGet 入口

以 1.5 节案例为例,通过 Maven Plugn 运行 tomcat,当请求 URL 是 http://localhost:8080/user

​ 我们知道 Servlet 处理 get 请求是 doGet 方法,所以我们去找 DispatcherServlet 类结构中的 doGet 方法。

protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.processRequest(request, response);
}

​ processRequest 处理请求的方法如下:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 计算处理请求的时间long startTime = System.currentTimeMillis();Throwable failureCause = null;LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = this.buildLocaleContext(request);RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());// 初始化 contextthis.initContextHolders(request, localeContext, requestAttributes);try {// 这里处理this.doService(request, response);} catch (IOException | ServletException ex) {failureCause = ex;throw ex;} catch (Throwable ex) {failureCause = ex;throw new NestedServletException("Request processing failed", ex);} finally {// 重置 contextthis.resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}this.logResult(request, response, failureCause, asyncManager);this.publishRequestHandledEvent(request, response, startTime, failureCause);}}

​ 本质上就是调用 doService 方法,由 DispatcherServlet 类实现。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {this.logRequest(request);// 保存下请求之前的参数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("org.springframework.web.servlet")) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// 方便后续 handlers 和 view 要使用它们request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.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);}RequestPath previousRequestPath = null;if (this.parseRequestPath) {previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);ServletRequestPathUtils.parseAndCache(request);}try {// 请求分发this.doDispatch(request, response);} finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {this.restoreAttributesAfterInclude(request, attributesSnapshot);}if (this.parseRequestPath) {ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);}}}

3.2 请求分发

​ doDispatch 方法是真正处理请求的核心方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Exception dispatchException = null;try {// 判断是不是文件上传类型的 requestprocessedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 根据 request 获取匹配的 handlermappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}// 根据 handler 获取匹配的 handlerAdapterHandlerAdapter ha = this.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 获取完整的视图名this.applyDefaultViewName(processedRequest, mv);// 应用后置的拦截器mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception ex) {dispatchException = ex;} catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}// 处理 handler 处理的结果,显然就是对 ModelAndView 或者出现的 Exception 处理this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);} catch (Exception ex) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, ex);} catch (Throwable err) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}
}

3.3 映射和适配器处理

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

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

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

protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {this.checkRequest(request);ModelAndView mav;if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized(mutex) {mav = this.invokeHandlerMethod(request, response, handlerMethod);}} else {mav = this.invokeHandlerMethod(request, response, handlerMethod);}} else {mav = this.invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader("Cache-Control")) {if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);} else {this.prepareResponse(response);}}return mav;
}

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

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);Object result;try {WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);// 重要:设置 handler(controller#list) 方法上的参数,返回值处理,绑定 databinder 等ServletInvocableHandlerMethod invocableMethod = this.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()) {result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(this.logger, (traceOn) -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}// 执行 controller 中方法invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);if (!asyncManager.isConcurrentHandlingStarted()) {result = this.getModelAndView(mavContainer, modelFactory, webRequest);return (ModelAndView)result;}result = null;} finally {webRequest.requestCompleted();}return (ModelAndView)result;
}

​ invokeAndHandler 交给 UserController 中具体执行 list 方法执行。

后续 invoke 执行的方法,直接看整个请求流程的调用链即可。执行后获得视图和 Model。

3.4 视图渲染

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

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) {this.logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException)exception).getModelAndView();} else {Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;mv = this.processHandlerException(request, response, handler, exception);errorView = mv != null;}}// 是否需要渲染视图if (mv != null && !mv.wasCleared()) {this.render(mv, request, response); // 渲染视图if (errorView) {WebUtils.clearErrorRequestAttributes(request);}} else if (this.logger.isTraceEnabled()) {this.logger.trace("No view rendering, null ModelAndView returned.");}if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, (Exception)null);}}
}

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

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();response.setLocale(locale);String viewName = mv.getViewName();View view;if (viewName != null) {view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");}} else {view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");}}if (this.logger.isTraceEnabled()) {this.logger.trace("Rendering view [" + view + "] ");}try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}view.render(mv.getModelInternal(), request, response);} catch (Exception var8) {if (this.logger.isDebugEnabled()) {this.logger.debug("Error rendering view [" + view + "]", var8);}throw var8;}
}

​ 后续就是通过 viewResolver 进行解析,最后无非就是返回控制权给 DispatcherServlet,由 DispatcherServlet 返回响应给用户。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/492290.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Spring Boot的数码产品抢购系统

一、系统背景与目的 随着电子商务的快速发展&#xff0c;数码产品在线销售已成为主流趋势。然而&#xff0c;传统的数码产品销售方式存在抢购过程繁琐、库存管理混乱、用户体验不佳等问题。为了解决这些问题&#xff0c;基于Spring Boot的数码产品抢购系统应运而生。该系统旨在…

【MambaSR复现】【Windows系统下Mamba环境配置】triton 、causal conv1d和mamba_ssm模块配置保姆教程

注&#xff1a;在此之前确保环境中已经安装packaging模块 pip install packaging若此模块安装过程中出现图下报错&#xff1a;这是由于环境创建时候的权限问题导致的&#xff0c;具体解决方案见我另一篇博文&#xff01; 1.Triton模块安装 注意&#xff1a;必须先安装triton&…

git branch -r(--remotes )显示你本地仓库知道的所有 远程分支 的列表

好的&#xff0c;git branch -r 这个命令用于列出远程分支。让我详细解释一下&#xff1a; 命令&#xff1a; git branch -rdgqdgqdeMac-mini ProductAuthentication % git branch -rorigin/main作用&#xff1a; 这个命令会显示你本地仓库知道的所有 远程分支 的列表。它不…

spring使用rabbitmq当rabbitmq集群节点挂掉 spring rabbitmq怎么保证高可用,rabbitmq网络怎么重新连接

##spring rabbitmq代码示例 Controller代码 import com.alibaba.fastjson.JSONObject; import com.newland.mi.config.RabbitDMMQConfig; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframewo…

【人工智能学习之HDGCN18关键点修改】

【人工智能学习之HDGCN18关键点修改】 训练部分修改部分 训练部分 请参考文章&#xff1a;【人工智能学习之HDGCN训练自己的数据集】 修改部分 参考源码中25关键点的区域划分&#xff0c;我们将18关键点划分为&#xff1a; 头部&#xff1a; 鼻子左眼和左耳右眼和右耳 上肢…

面试题整理3----nc命令的常见用法

面试题整理3----nc命令的常见用法 1. NC是什么2. NC的常用参数2.1 开启指定端口TCP监听(-l小写的L)2.2 测试端口是否能访问(-v)2.3 开启指定端口UDP监听(-u)2.4 端口扫描(-z)2.5 指定超时时间(-w)2.6 指定本地端口号连接(-p)2.7 指定的命令(-e) 1. NC是什么 nc&#xff08;Net…

C语言实现八大排序算法

目录 1.插入排序 1.1 直接插入排序 1.2 希尔排序 2. 选择排序 2.1 直接选择排序 2.2 堆排序 *TopK问题&#xff1a; 3. 交换排序 3.1 冒泡排序 3.2 快速排序 1. Hoare版本 2. 挖坑法 3. 前后指针法 4. 快速排序优化 5. 非递归快速排序 4.归并排序 1.递归式归并…

【昇腾】NPU ID:物理ID、逻辑ID、芯片映射关系

起因&#xff1a; https://www.hiascend.com/document/detail/zh/Atlas%20200I%20A2/23.0.0/re/npu/npusmi_013.html npu-smi info -l查询所有NPU设备&#xff1a; [naienotebook-npu-bd130045-55bbffd786-lr6t8 DCNN]$ npu-smi info -lTotal Count : 1NPU…

使用Python脚本进行编写批量根据源IP进行查询的语句用于态势感知攻击行为的搜索

使用Python脚本进行编写批量根据源IP进行查询的语句 以下根据ip-list集里面的IP地址&#xff08;可以自行扩充&#xff09;&#xff0c;然后采用srcaddress "{ip}" or 的形式进行打印并存储在路径为&#xff1a;桌面的IOC结果.txt --------------------------代码如…

【Qt】信号、槽

目录 一、信号和槽的基本概念 二、connect函数&#xff1a;关联信号和槽 例子&#xff1a; 三、自定义信号和槽 1.自定义槽函数 2.自定义信号函数 例子&#xff1a; 四、带参的信号和槽 例子&#xff1a; 五、Q_OBJECT宏 六、断开信号和槽的连接 例子&#xff1a; …

揭开 Choerodon UI 拖拽功能的神秘面纱

01 引言 系统的交互方式主要由点击、选择等组成。为了提升 HZERO 系统的用户体验、减少部分操作步骤&#xff0c;组件库集成了卓越的拖拽功能&#xff0c;让用户可以更高效流畅的操作系统。 例如&#xff1a;表格支持多行拖拽排序、跨表数据调整、个性化调整列顺序&#xff1…

低代码企业管理的革命:Microi吾码产品深度测评

低代码平台Microi吾码&#xff1a;帮助企业快速构建自定义数据管理与自动化系统 在现代企业的数字化转型过程中&#xff0c;如何快速响应市场变化并高效管理内部数据&#xff0c;已成为各类企业面临的重要挑战。低代码平台作为一种创新的技术解决方案&#xff0c;为企业提供了…

机器学习之交叉熵

交叉熵&#xff08;Cross-Entropy&#xff09;是机器学习中用于衡量预测分布与真实分布之间差异的一种损失函数&#xff0c;特别是在分类任务中非常常见。它源于信息论&#xff0c;反映了两个概率分布之间的距离。 交叉熵的数学定义 对于分类任务&#xff0c;假设我们有&#…

C# OpenCvSharp DNN 实现百度网盘AI大赛-表格检测第2名方案第一部分-表格边界框检测

目录 说明 效果 模型 项目 代码 frmMain.cs YoloDet.cs 参考 下载 其他 说明 百度网盘AI大赛-表格检测的第2名方案。 该算法包含表格边界框检测、表格分割和表格方向识别三个部分&#xff0c;首先&#xff0c;ppyoloe-plus-x 对边界框进行预测&#xff0c;并对置信…

图形学笔记 - 5. 光线追踪 - RayTracing

Whitted-Style Ray tracing 为什么要光线追踪 光栅化不能很好地处理全局效果 软阴影尤其是当光线反射不止一次的时候 栅格化速度很快&#xff0c;但质量相对较低 光线追踪是准确的&#xff0c;但速度很慢 光栅化&#xff1a;实时&#xff0c;光线追踪&#xff1a;离线~10K …

day15 python(3)——python基础(完结!!)

【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、函数 1.1 函数传参中的拆包 1.2 匿名函数的定义 1.3 匿名函数练习 1.4 匿名函数应用——列表中的字典排序 2、面向对象 OOP 2.1 面向对象介绍 2.2 类和对象 2.3 类的构成和设计 2.4 面向对象代码…

C语言破解鸡蛋问题

破解鸡蛋问题 问题分析算法思路选择枚举法思路数据结构应用数组的应用变量的合理定义代码实现伪代码示例C 语言代码展示结果验证与分析不同输入验证复杂度分析问题分析 在这个 “鸡蛋问题” 中,已知条件表明这堆鸡蛋按两个两个地拿、三个三个地拿、四个四个地拿时,最后都剩一…

XXE-Lab靶场漏洞复现

1.尝试登录 输入账号admin/密码admin进行登录&#xff0c;并未有页面进行跳转 2.尝试抓包分析请求包数据 我们可以发现页面中存在xml请求&#xff0c;我们就可以构造我们的xml请求语句来获取想要的数据 3.构造语句 <?xml version"1.0" ?> <!DOCTYPE fo…

安卓主板_MTK联发科android主板方案

在当前智能设备的发展中&#xff0c;安卓主板的配置灵活性和性能优化显得尤为重要。安卓主板的联发科方案&#xff0c;在芯片上&#xff0c;搭载联发科MTK6761、MT8766、MT6765、MT6762、MT8768、MT8390、MTK8370以及MT8788等型号&#xff0c;均基于64位的四核或八核架构设计。…

计算机网络知识点全梳理(三.TCP知识点总结)

目录 TCP基本概念 为什么需要TCP 什么是TCP 什么是TCP链接 如何唯一确定一个 TCP 连接 TCP三次握手 握手流程 为什么是三次握手&#xff0c;而不是两次、四次 为什么客户端和服务端的初始序列号 ISN 不同 既然 IP 层会分片&#xff0c;为什么 TCP 层还需要 MSS TCP四…