了解 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.mzph.cn/diannao/64432.shtml

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

相关文章

Springboot3.x配置类(Configuration)和单元测试

配置类在Spring Boot框架中扮演着关键角色&#xff0c;它使开发者能够利用Java代码定义Bean、设定属性及调整其他Spring相关设置&#xff0c;取代了早期版本中依赖的XML配置文件。 集中化管理&#xff1a;借助Configuration注解&#xff0c;Spring Boot让用户能在一个或几个配…

鸿道Intewell-C纯实时构型,适合有功能安全认证需求的工业操作系统

鸿道Intewell-C纯实时构型&#xff0c;适合有功能安全认证需求的工业操作系统 鸿道Intewell-C是一款工业实时微内核操作系统&#xff0c;由科东软件自主研发&#xff0c;具有超低延迟和最小抖动&#xff0c;保障工业设备可以高效处理时间敏感的现场业务&#xff0c;支持多种工…

Stream– ESP8266物联网应用,(客户端向服务器发送数据信息 客户端向服务器请求数据信息)

Stream– ESP8266物联网应用 Stream对于ESP8266-Arduino语言来说指的是数据序列。请留意&#xff1a;在C编程中Stream常被翻译作“流”。我们认为将Stream称为数据序列更加直观。因为数据序列这一概念有两个很关键特点。 第一个特点是“序”&#xff0c;即数据序列不能是杂乱…

芯品荟|SWM221系列芯片之TFTLCD彩屏显示及控制

“革新未来&#xff0c;智驭控制新纪元”&#xff0c;由广东华芯微特集成电路有限公司市场总监张琢&#xff0c;对SWM221系列的强大功能表现进行了整体介绍。 确实&#xff0c;华芯微特在TFTLCD显示及控制有十多年应用基础和积累的团队&#xff0c;仍勇于挑战&#xff0c;自我…

MIT S6081 2024 Lab 1 | Operating System | Notes

目录 安装与下载 实验1 开始我们的实验 sleep&#xff08;简单&#xff09; pingpong&#xff08;简单&#xff09; primes (中等)/(困难) find&#xff08;中等&#xff09; xargs&#xff08;中等&#xff09; finally Reference I. Tools Debian 或 Ubuntu Arch…

华为认证HCIA——数据传输形式,数据封装的基本概念

前言&#xff1a; 整理下学习笔记&#xff0c;打好基础&#xff0c;daydayup!!! 对网络概念有基本理解后&#xff08;华为认证HCIA——网络基本概念&#xff09;&#xff0c;开始进一步学习数据传输。 数据传输的形式 数据传输主要有三种形式&#xff1a; 1&#xff0c;电路传…

opencv小练习(未完成版)

读取一张彩色图像并将其转换为灰度图。 import cv2# 读取图片 img cv2.imread("./duck.png") img cv2.resize(img, dsizeNone, fx0.4, fy0.4, interpolationcv2.INTER_LINEAR) # 读取一张灰度图 img_gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 展示图片 cv2.im…

WSL (Windows Subsystem for Linux)

文章目录 Windows下使用Linux的三种方式&#xff1a;1.WSL(1)安装WSL(2)初始化Linux系统(3)安装、创建、激活 Python虚拟环境 2.虚拟机3.Docker Windows下使用Linux的三种方式&#xff1a; 1.WSL 是最简单的在 Windows 上运行 Linux 环境的方式&#xff0c;适用于日常开发和命…

金融分析-Transformer模型(基础理论)

Transformer模型 1.基本原理 transformer的core是注意力机制&#xff0c;其本质就是编码器-解码器。他可以通过多个编码器进行编码&#xff0c;再把编码完的结果输出给解码器进行解码&#xff0c;然后得到最终的output。 1.1编码器 数据在编码器中会经过一个self-attention的…

【一本通】两个数的最小公倍数

【一本通】两个数的最小公倍数 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入两个正整数&#xff0c;编程计算两个数的最小公倍数。 输入 两个整数 输出 最小公倍数 样例输入 12 18样例输出 …

D 咖智能饮品机器人:开启商业新篇

在科技迅猛发展的当下&#xff0c;智能机器人正逐步渗透到各个商业领域&#xff0c;D 咖智能饮品机器人便是其中的佼佼者&#xff0c;它的出现为饮品行业带来全新的发展契机&#xff0c;有望开启商业新篇。 从大环境来看&#xff0c;消费者对于饮品的需求日益多元化和个性化。他…

35. Three.js案例-创建带阴影的球体与平面

35. Three.js案例-创建带阴影的球体与平面 实现效果 知识点 WebGLRenderer WebGLRenderer 是Three.js中用于渲染场景的主要类之一&#xff0c;它负责将场景中的对象渲染到画布上。 构造器 new THREE.WebGLRenderer(parameters : Object) 参数类型描述parametersObject可选…

leetcode212. 单词搜索 II

给定一个 m x n 二维字符网格 board 和一个单词&#xff08;字符串&#xff09;列表 words&#xff0c; 返回所有二维网格上的单词 。 单词必须按照字母顺序&#xff0c;通过 相邻的单元格 内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一…

容器化技术

文章目录 虚拟化容器容器隔离实现隔离文件 chroot隔离访问 namespace隔离资源 cgroups Dockerkubernetes 虚拟化容器 容器的首要目标是让软件分发部署过程从传统的发布安装包、靠人工部署转变为直接发布已经部署好的、包含整套运行环境的虚拟计划镜像。 一个计算机软件能够给正…

【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析

目录 一、前言 二、Spring Boot 简介 三、Spring Boot 核心模块 四、Spring Boot 项目实战&#xff1a;构建一个简单的 RESTful API 1. 创建 Spring Boot 项目 2. 配置数据库 3. 创建实体类 4. 创建 JPA 仓库接口 5. 创建服务层 6. 创建控制器层 7. 测试 API 8. 运…

土地档案管理关系[源码+文档]

目录 摘 要 Abstract 1 绪论 1.1 可行性研究编写目的 1.2 项目背景 1.3 土地管理现状 1.4 土地档案管理研究方向 1.5 项目目标 1.6 项目设计原则 1.6.1 实用性原则 1.6.2 经济性原则 1.6.3 合法性原则 2 相关技术介绍 2.1 三层架构的选择 2.2 编程…

使用C语言编写UDP循环接收并打印消息的程序

使用C语言编写UDP循环接收并打印消息的程序 前提条件程序概述伪代码C语言实现编译和运行C改进之自由设定端口注意事项在本文中,我们将展示如何使用C语言编写一个简单的UDP服务器程序,该程序将循环接收来自指定端口的UDP消息,并将接收到的消息打印到控制台。我们将使用POSIX套…

Audiocraft智能音频和音乐生成工具部署及使用

1、概述 Facebook开源了一款名为AudioCraft的AI音频和音乐生成工具。 该工具可以直接从文本描述和参考音乐生成高质量的音频和音乐。AudioCraft包含MusicGen、AudioGen和EnCodec三个模型&#xff0c;分别实现音乐生成、音频生成和自定义音频模型构建。 2、项目地址 https://…

vulnhub靶场【DriftingBlues】之7

前言 靶机&#xff1a;DriftingBlues-6&#xff0c;IP地址192.168.1.65 攻击&#xff1a;kali&#xff0c;IP地址192.168.1.16 都采用虚拟机&#xff0c;网卡为桥接模式 主机发现 使用arp-scan -l或netdiscover -r 192.168.1.1/24 信息收集 使用nmap扫描端口 SSH服务&…

SCAU期末笔记 - Linux系统应用与开发教程样卷解析(2024版)

我真的不理解奥&#xff0c;为什么会有给样卷不自带解析的&#xff0c;对答案都没得对&#xff0c;故整理一篇 样卷1 一、选择题 1、为了遍历shell脚本调用时传入的参数&#xff0c;需要在shell脚本中使用_____。 A.$#表示参数的个数B.S表示所有参数C.$0表示脚本名D.$1表示…