SpringMVC 详细教程及源码讲解

目录

  • 一、SpringMVC简介
    • 1. 什么是MVC?
    • 2.什么是SpringMVC?
    • 3.SpringMVC的特点?
  • 二、SpringMVC入门案列
    • 1. 开发环境
    • 2. 创建Maven工程
      • 2.1 添加web模块
      • 2.2 引入依赖
    • 3. 配置web.xml
      • 3.1 默认配置方式
      • 3.2 扩展配置方式
    • 4.创建请求控制器
    • 5. 创建SpringMVC的配置文件
    • 6. 测试方法
      • 6.1 项目结构
      • 6.2 视图层实现
      • 6.3 在tomcat服务器启动服务
    • 7. 总结
  • 三、@RequestMapping注解
    • 1. @RequestMapping注解的功能
    • 2. @RequestMapping注解的位置
    • 3. @RequestMapping注解的属性
      • 3.1 value属性
      • 3.2 method属性
      • 3.3 params属性
      • 3.4 headers属性
      • 3.5 SpringMVC支持ant风格的路径
      • 3.6 SpringMVC支持路径中的占位符 (==重点==)
    • 4. SpringMVC获取请求参数
      • 4.1 通过servletAPI获取
      • 4.2 通过控制器方法的形参获取
      • 4.3 @RequestParam:将请求参数和控制器方法的形参绑定
      • 4.4 @RequestHeader: 将请求头信息和控制器方法的形参绑定
      • 4.5 @CookieValue:将cookie数据和控制器方法的形参绑定
      • 4.6 通过控制器方法的实体类类型的形参获取
      • 4.7 过滤器解决获取请求参数的乱码问题
  • 四、域对象共享数据
    • 1. 使用ServletAPI向request域对象共享数据
    • 2. 使用ModelAndView向request域对象共享数据
    • 3. 使用model向请求域共享数据
    • 4. 使用modelMap向请求域共享数据
    • 5. 使用map向请求域共享数据
    • 6. Model和ModelMap和map的关系
    • 7. 向会话域共享数据
    • 8. 向应用域共享数据
  • 五、SpringMVC的视图
    • 1. ThymeleafView
    • 2. 转发视图
    • 3. 重定向视图
    • 4. 视图控制器view-Controller
  • 六、RESTful架构
    • 1. RESTful简介
    • 2. RESTful的实现
    • 3. 过滤器HiddenHttpMethodFilter
  • 七、SpringMVC处理ajax请求
    • 1. @RequestBody获取请求体信息
    • 2. @RequestBody获取json格式的请求体参数
      • 2.1 导入jackson依赖
      • 2.2 测试方法
    • 3. @ResponseBody响应浏览器数据
    • 4. @ResponseBody响应浏览器json格式数据
    • 5. @RestController注解
  • 八、拦截器与过滤器
    • 1. 简介
    • 2. 过滤器的使用
    • 3. 拦截器的使用
      • 3.1 拦截器的配置
        • SpringMVC中的拦截器需要实现HandlerInterceptor接口
        • SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置
      • 3.2 拦截器的三个方法
      • 3.3 多个拦截器的执行顺序
    • 4. 拦截器实现源码解析(了解)
  • 九、异常处理器
    • 1. SpringMVC自带的异常处理器SimpleMappingExceptionResolver
      • 1.1 基于配置的异常处理
      • 1.2 基于注解的异常处理
      • 1.3 源码解析
    • 2. 自定义异常处理器
    • 3. 异常处理源码解析(了解)
  • 十、注解配置SpringMVC
    • 1. 创建初始化类,代替web.xml
    • 2. 创建SpringConfig配置类,代替Spring的配置文件
    • 3. 创建WebConfig配置类,代替SpringMVC的配置文件
  • 十一、SpringMVC执行流程
    • 1. SpringMVC常用组件
    • 2. DispatcherServlet初始化过程
      • 2.1 初始化WebApplicationContext
      • 2.2 创建WebApplicationContext
      • 2.3 DispatcherServlet初始化策略
      • 2.4 源码继承调用解析
    • 3. DispatcherServlet调用组件处理请求
      • 3.1 processRequest()
      • 3.2 doService()
      • 3.3 doDispatch()
      • 3.4 processDispatchResult()
      • 3.5 源码继承调用解析
    • 4. SpringMVC的执行流程
  • 十二、SSM整合练习
    • 1. 引入依赖
    • 2. 配置文件
    • 3. 控制器方法
    • 4. 业务层方法
    • 5. 数据层方法
    • 6. mapper映射文件
    • 7. 表现层
    • 8. 测试结果

一、SpringMVC简介

1. 什么是MVC?

MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分

M:Model,模型层,指工程中的javaBean,作用是处理数据。javaBean分为两类:

  • 一类称为实体类Bean: 专门存储业务数据的,如 Student、User等
  • 一类称为业务处理Bean: 指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。

V: View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据

C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器

MVC的工作流程:

用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器。

2.什么是SpringMVC?

SpringMVC是Spring的一个后续产品,是Spring的一个子项目。
SpringMVC是Spring 为表述层开发提供的一整套完备的解决方案。在表述层框架历经 Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为Java EE 项目表述层开发的首选方案。

注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台servlet

3.SpringMVC的特点?

  • Spring 家族原生产品,与I0C容器等基础设施无缝对接
  • 基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可。
  • 性能卓著,尤其适合现代大型、超大型互联网项目要求

二、SpringMVC入门案列

1. 开发环境

IDE: idea 2021
构建工具: maven 3.6.9
服务器: tomcat 8.5.9
Spring版本: 5.3.19

2. 创建Maven工程

2.1 添加web模块

SpringMVC 详细教程及源码讲解_第1张图片

2.2 引入依赖

<dependencies>
  <dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.11version>
    <scope>testscope>
  dependency>
  
  <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webmvcartifactId>
    <version>5.3.19version>
  dependency>
  <dependency>
    
    <groupId>ch.qos.logbackgroupId>
    <artifactId>logback-classicartifactId>
    <version>1.2.3version>
  dependency>
  
  <dependency>
    <groupId>javax.servletgroupId>
    <artifactId>javax.servlet-apiartifactId>
    <version>4.0.1version>
  dependency>
  
  <dependency>
    <groupId>org.thymeleafgroupId>
    <artifactId>thymeleaf-spring5artifactId>
    <version>3.0.15.RELEASEversion>
  dependency>
dependencies>

3. 配置web.xml

作用:注册SpringMVC的前端控制器DispatcherServlet

3.1 默认配置方式

此配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为 -servlet.xml,
例如,以下配置所对应SpringMVC的配置文件位于WEB-NF下,文件名为springMVC-servlet.xml
SpringMVC 详细教程及源码讲解_第2张图片

DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Applicationdisplay-name>

  
  <servlet>
    <servlet-name>springMVCservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
  servlet>
  <servlet-mapping>
    <servlet-name>springMVCservlet-name>
    <url-pattern>/url-pattern>
  servlet-mapping>
web-app>

3.2 扩展配置方式

web.xml默认配置方式,spring配置文件位于WEB-INF目录下
SpringMVC 详细教程及源码讲解_第3张图片
如果要将spring配置文件放在java类路径下(resources目录下),可以通过标签设置springMvC配置文件的位置和名称。

DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Applicationdisplay-name>

  
  <servlet>
    <servlet-name>springMVCservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    
    <init-param>
      <param-name>contextConfigLocationparam-name>
      <param-value>classpath:springMVC.xmlparam-value>
    init-param>
    
    <load-on-startup>1load-on-startup>
  servlet>

  <servlet-mapping>
    <servlet-name>springMVCservlet-name>
    <url-pattern>/url-pattern>
  servlet-mapping>
web-app>

编译后的文件目录:
SpringMVC 详细教程及源码讲解_第4张图片

4.创建请求控制器

由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,因此需要创建处理具体请求的类,即请求控制器
请求控制器中每一个处理请求的方法成为控制器方法
因为SpringMVC的控制器由一个POJO(普通的java类)担任,因此需要通过@Controller注解将其标识为一个控制层组件,交给Spring的IOC容器管理,此时SpringMVC才能够识别控制器的存在

@Controller
public class HelloController {

    /*
        @RequestMapping注解: 处理请求和控制器方法之间的映射关系
        @RequestMapping注解的value属性可以通过请求地址匹配请求,/表示的当前工程的上下文路径
        localhost:8080/springMvC/

    */
    @RequestMapping("/")
    public String protal() {
        System.out.println("请求方法");
        // 将逻辑视图返回
        return "index";
    }

    @RequestMapping("/hello")
    public String hello() {
        return "success";
    }
}

5. 创建SpringMVC的配置文件


<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    
    <context:component-scan base-package="com.fd.spring">context:component-scan>

    
    <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        
                        <property name="prefix" value="/WEB_INF/templates"/>
                        
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="utf-8"/>
                    bean>
                property>
            bean>
        property>
    bean>
beans>

6. 测试方法

6.1 项目结构

SpringMVC 详细教程及源码讲解_第5张图片

6.2 视图层实现

index.html代码

DOCTYPE html>
<html lang="en" xmlns: th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <h1>hello SpringMVCh1>


    <a th:href="@{/hello}">测试SpringMVCa>
    <a href="/hello">测试绝对路径a>

    <div>
        这是主页界面。。。。。。。。。。
    div>
body>
html>

6.3 在tomcat服务器启动服务

SpringMVC 详细教程及源码讲解_第6张图片

SpringMVC 详细教程及源码讲解_第7张图片

7. 总结

浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理。前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面。

三、@RequestMapping注解

1. @RequestMapping注解的功能

从注解名称上我们可以看到,@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。
SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求

2. @RequestMapping注解的位置

@RequestMapping标识一个类:设置映射请求的请求路径的初始信息
@RequestMapping标识一个方法: 设置映射请求请求路径的具体信息

@Controller
@RequestMapping("/test")
public class TestRequestMappingController {

    // 此时请求映射所映射的请求的请求路径为: /test/hello
    @RequestMapping("/hello")
    public String protal() {
        return "index";
    }
}

3. @RequestMapping注解的属性

注解源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}

3.1 value属性

@RequestMapping注解value属性作用: 通过请求的请求路径匹配请求

value属性是数组类型,即当前浏览器所发送请求的请求路径匹配value属性中的任何一个值则当前请求就会被注解所标识的方法进行处理。

@Controller
@RequestMapping("/test")
public class TestRequestMappingController {
    
    @RequestMapping({"/hello", "/hai"})
    public String protal() {
        return "index";
    }
}

3.2 method属性

@RequestMapping注解的method属性作用:通过请求的请求方式匹配请求

method属性是RequestMethod类型的数组,即当前浏览器所发送请求的请求方式匹配method属性中的任何一个,则当前请求就会被注解所标识的方法进行处理。若浏览器所发送的请求的请求路径和RequestMapping注解value属性匹配,但是请求方式不匹配此时页面报错:405 - Request methodxxx’not supported

@Controller
@RequestMapping("/test")
public class TestRequestMappingController {

    // 此时请求映射所映射的请求的请求路径为: /test/hello
    @RequestMapping(value = {"/hello", "/hai"}, 
            method = RequestMethod.GET)
    public String protal() {
        return "index";
    }    
}

扩展:

在@RequestMapping的基础上,结合请求方式的一些派生注解:
@GetMapping,@postMapping,@DeleteMapping,@PutMapping

  • 处理get请求的映射–>@GetMapping
  • 处理post请求的映射–>@PostMapping
  • 处理put请求的映射–>@PutMapping
  • 处理delete请求的映射–>@DeleteMapping
@Controller
@RequestMapping("/test")
public class TestRequestMappingController {

    // 此时请求映射所映射的请求的请求路径为: /test/hello及/test/hai,请求方法为get请求
    @GetMapping(value = {"/hello", "/hai"})
    public String protal() {
        return "index";
    }
}

常用的请求方式有get,post,put,delete。但是目前浏览器只支持get和post,若在form表单提交时,为method设置了其他请求方式的字符串 (put或delete),则按照默认的请求方式get处理
若要发送put和delete请求,则需要通过Spring提供的过滤器HiddenHttpMethodFilter,在下面RESTful部分会讲到。

3.3 params属性

@RequestMapping注解的params属性通过请求的请求参数匹配请求映射
@RequestMappin注解的params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系
“param”:要求请求映射所匹配的请求必须携带param请求参数
“!param”:要求请求映射所匹配的请求必须不能携带param请求参数
"param=value”: 要求请求映射所匹配的请求必须携带param请求参数且param=value
“param!=value”: 要求请求映射所匹配的请求必须携带param请求参数但是param!=value

@Controller
@RequestMapping("/test")
public class TestRequestMappingController {

    // 请求参数必须携带username参数,不能携带password参数,必须携带age参数且值为20
    @GetMapping(value = {"/hello", "/hai"},
    params = {"username", "!password", "age=20"})
    public String protal() {
        return "index";
    }
}

3.4 headers属性

@RequestMapping注解的headers属性通过请求的请求头信息匹配请求映射
@RequestMapping注解的headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
”heaqer”:要求请求映射所匹配的请求必须携带header请求头信息
”!header”:要求请求映射所匹配的请求必须不能携带header请求头信息
"header=value”: 要求请求映射所匹配的请求必须携带header请求头信息且header=value
"header!=value”: 要求请求映射所匹配的请求必须携带header请求头信息且header!=value

若当前请求满足@RequestMapping注解的value和method属性,但是不满足headers属性,此时页面显示404错误,即资源未找到。

3.5 SpringMVC支持ant风格的路径

在@RequestMapping注解的value属性值中设置一些特殊字符
?:表示任息的单个字符
*:表示任意的0个或多个字符
:表示任意层数的任意目录
注意:在使用
时,只能使用/**/xxx的方式

@RequestMapping("/a?a")
public String testAnt() {
    return "success";
}

3.6 SpringMVC支持路径中的占位符 (重点)

原始方式:/deleteUser?id=1
rest方式::/user/delete/1
SpringMVC路径中的占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符(xxx)表示传输的数据,在通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参。

案列演示:

view视图:

DOCTYPE html>
<html lang="en" xmlns: th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>

    <a th:href="@{/user/delete/1}">测试SpringMVC路径中占位符a>

body>
html>

控制器:

@RequestMapping("/user/delete/{id}")
public String test(@PathVariable String id) {
    System.out.println(id); // id = 1
    return "success";
}

4. SpringMVC获取请求参数

4.1 通过servletAPI获取

只需要在控制器方法的形参位置设置HttpservletRequest类型的形参就可以在控制器方法中使用request对象获取请求参数

案列演示:

视图层:

<form th:action="@{/param/servletAPI}" method="post">
    用户名:<input type="text" name="username"><br>
    密  码:<input type="text" name="password"> <br>
    <input type="submit" value="登陆">
form>

控制层:

@RequestMapping("/param/servletAPI")
public String getParamByServletAPI(HttpServletRequest request) {
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    System.out.println("username: " + username + ", password: " + password);
    return "success";
}

测试结果:
SpringMVC 详细教程及源码讲解_第8张图片
在这里插入图片描述

4.2 通过控制器方法的形参获取

只需要在控制器方法的形参位置,设置一个形参,形参的名字和请求参数的名字必须一致

案列演示:

视图层:

<a th:href="@{/param?username=tom&password=123456}">测试控制器方法的形参获取请求参数a><br>

控制层:

@RequestMapping("/param")
public String getParam(String username, String password) {
    System.out.println("username: " + username + ", password: " + password);
    return "success";
}

测试结果:
在这里插入图片描述

4.3 @RequestParam:将请求参数和控制器方法的形参绑定

@RequestParam注解的三个属性: value,required,defaultValue

  • value: 设置和形参绑定的请求参数的名称
  • required:设置是否必须传输value所对应的请求参数。默认值为true,表示value所对应的请求参数必须传输,否则页面报错400 - Required string parameter xxx’ is not present。若设置为false,则表示value所对应的请求参数不是必须传输,若未传输,则形参值为null
  • defaultValue:设置当没有传输value所对应的请求参数时,为形参设置的默认值,此时和required属性值无关

源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

案列演示:

视图层:

<form th:action="@{/param}" method="post">
    用户名:<input type="text" name="userName"><br>
    密  码:<input type="text" name="password"> <br>
    <input type="submit" value="登陆">
form>

控制层:

// 设置userName参数和username绑定
@RequestMapping("/param")
public String getParamByAnnotation(@RequestParam("userName", defaultValue = "hello") String username,
                                   @RequestParam("password") String password) {
    System.out.println("username: " + username + ", password: " + password);
    return "success";
}

测试结果:
在这里插入图片描述
在这里插入图片描述

4.4 @RequestHeader: 将请求头信息和控制器方法的形参绑定

用法同@RequestParam

案列演示:

@RequestMapping("/param")
public String getParamByAnnotation(@RequestParam(value = "userName", defaultValue = "hello") String username,
                                   @RequestParam("password") String password,
                                   @RequestHeader("referer") String referer,

                                   ) {

    System.out.println("referer: " + referer);// 提供请求的访问来源,从哪一个请求链接过来的
    System.out.println("username: " + username + ", password: " + password);
    return "success";
}

测试结果:
在这里插入图片描述

4.5 @CookieValue:将cookie数据和控制器方法的形参绑定

用法同@RequestParam

案列演示:

@RequestMapping("/param")
public String getParamByAnnotation(@RequestParam(value = "userName", defaultValue = "hello") String username,
                                   @RequestParam("password") String password,
                                   @RequestHeader("referer") String referer,
                                   @CookieValue("JSESSIONID") String jsessionId) {

    System.out.println("referer: " + referer);
    System.out.println("jsessionId: " + jsessionId);
    System.out.println("username: " + username + ", password: " + password);
    return "success";
}

测试结果:
在这里插入图片描述

4.6 通过控制器方法的实体类类型的形参获取

请求参数需要在控制器方法的形参位置设置实体类类型的形参,要保证实体类中的属性的属性名和请求参数的名字一致。可以通过实体类类型的形参获取请求参数

案列演示:

控制层:

@RequestMapping("/param/pojo")
public String getParamByPojo(User user) {
    System.out.println(user);
    return "success";
}

pojo:

public class User {
    private String username;

    private String password;
    
    .....
}

视图层:

<form th:action="@{/param/pojo}" method="post">
    用户名:<input type="text" name="username"><br>
    密  码:<input type="text" name="password"> <br>
    <input type="submit" value="登陆">
form>

测试结果:
在这里插入图片描述

4.7 过滤器解决获取请求参数的乱码问题

解决获取请求参数的乱码问题,可以使用SpringMVC提供的编码过滤器CharacterEncodingFilter,但是必须在web.xml中进行注册


<filter>
  <filter-name>CharacterEncodingFilterfilter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
  
  <init-param>
    <param-name>encodingparam-name>
    <param-value>UTF-8param-value>
  init-param>
  
  <init-param>
    <param-name>forceEncodingparam-name>
    <param-value>trueparam-value>
  init-param>
filter>
<filter-mapping>
  <filter-name>CharacterEncodingFilterfilter-name>
  <url-pattern>/*url-pattern>
filter-mapping>

注意:SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效

编码过滤源码解析:

// 通过web.xml中配置进行初始化赋值
public CharacterEncodingFilter(String encoding, boolean forceEncoding) {
    this(encoding, forceEncoding, forceEncoding);
}
// 判断设置请求及响应的编码格式
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    String encoding = this.getEncoding();
    if (encoding != null) {
        if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
            request.setCharacterEncoding(encoding);
        }

        if (this.isForceResponseEncoding()) {
            response.setCharacterEncoding(encoding);
        }
    }

    filterChain.doFilter(request, response);
}

四、域对象共享数据

请求域(request scope):请求域的生命周期是指一次请求的过程。页面通过forword方式跳转,目标页面仍然可以获得request的属性值。如果通过redirect方式进行页面跳转,会去重新访问新的指定的URL地址,request的属性值会丢失。
会话域(session scope):会话域的生命周期是指某个客户端与服务器所连接的时间,会话过期或用户自动退出后,会话失效。存储在会话域中的对象在整个会话期间都可以被访问。
应用域(application scope):应用域的生命周期是指从服务器开始执行到服务器关闭为止,是4个作用域时间最长的。 存储在应用域中的对象在整个应用程序运行期间可以被所有JSP和Servlet共享访问。

1. 使用ServletAPI向request域对象共享数据

视图层:
success.html界面代码

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <h2>欢迎登陆!h2>
    
    <p th:text="${scope}">p>
    
    <p th:text="${session.scope}">p>
    
    <p th:text="${application.scope}">p>
body>
html>

控制层:

@RequestMapping("/test/servletAPI")
public String servletAPI(HttpServletRequest request) {
    request.setAttribute("scope","hello servletAPI");
    return "success";
}

2. 使用ModelAndView向request域对象共享数据

使用ModeLAndView时,可以使用其Model功能向请求域共享数据
使用view功能设置逻辑视图,但是控制器方法一定要将ModelAndView作为方法的返回值

@RequestMapping("/test/mav")
public ModelAndView testMAV() {
    /*
    * ModelAndView包含Model和view的功能
    * Model:向请求域中共享数据
    * view:设置逻辑视图实现页面跳转
    *
    * */
    ModelAndView modelAndView = new ModelAndView();
    // 向共享域中共享数据
    modelAndView.addObject("scope", "hello ModelAndView");
    // 设置逻辑视图
    modelAndView.setViewName("success");
    return modelAndView;
}

3. 使用model向请求域共享数据

@RequestMapping("/test/modelMap")
public String testModelMap(ModelMap modelMap) {
    modelMap.addAttribute("scope", "hello, ModelMap");
    return "success";
}

4. 使用modelMap向请求域共享数据

@RequestMapping("/test/modelMap")
public String testModelMap(ModelMap modelMap) {
    modelMap.addAttribute("scope", "hello, ModelMap");
    return "success";
}

5. 使用map向请求域共享数据

@RequestMapping("/test/map")
public String testMap(Map<String, Object> map) {
    map.put("scope", "hello, map");
    return "success";
}

6. Model和ModelMap和map的关系

其实在底层中,这些类型的形参最终都是通过BindingAwareModelMap创建

  • public class BindingAwareModelMap extends ExtendedModelMap
  • public class ExtendedModelMap extends ModelMap implements Model
  • public class ModelMap extends LinkedHashMap
    SpringMVC 详细教程及源码讲解_第9张图片

7. 向会话域共享数据

/** 会话域:浏览器打开到关闭*/
@RequestMapping("/test/session")
public String testSession(HttpSession session) {
    session.setAttribute("scope", "hello, session");
    return "success";
}

8. 向应用域共享数据

/** 应用域:服务器打开到关闭*/
@RequestMapping("/test/application")
public String testApplication(HttpSession session) {
    ServletContext servletContext = session.getServletContext();
    servletContext.setAttribute("scope", "hello, application");
    return "success";
}

五、SpringMVC的视图

SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户。
SpringMVC视图的种类很多,默认有转发视图和重定向视图。当工程引入jstl的依赖,转发视图会自动转换为jstlView。
若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView

1. ThymeleafView

当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转

@RequestMapping("/test/view/thymeleaf")
public String testThymeleafView() {
    return "success";
}

2. 转发视图

SpringMVC中默认的转发视图是InternalResourceView

SpringMV中创建转发视图的情况:

当控制器方法中所设置的视图名称以"forward:"为前缀时,创建lnternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转。例:“forward:/”,“forward:/employee”

/** 转发视图*/
@RequestMapping("/test/view/forward")
public String testInternalResourceView() {
    // 直接转发跳转到相应视图,但是视图中的数据没有办法解析。
    return "forward:/test/model";
}

3. 重定向视图

SpringMVC中默认的重定向视图是RedirectView

当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"rediret”去掉,剩余部分作为最终路径通过重定向的方式实现跳转。例:“redirect:/”,“redirect:/employee”

/** 重定向视图*/
@RequestMapping("/test/view/redirect")
public String testRedirectView() {
    return "redirect:/test/model";
}

注:
重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剥余部分是否以/开头,若是则会自动拼接上下文路径

4. 视图控制器view-Controller

当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法在SpringMVC.xml配置文件使用view-controller标签进行标识。不用单独封装一个控制器方法。


<context:component-scan base-package="com.fd.spring">context:component-scan>


<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine">
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
            <property name="templateResolver">
                <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                    
                    <property name="prefix" value="/WEB-INF/templates/"/>
                    
                    <property name="suffix" value=".html"/>
                    <property name="templateMode" value="HTML5"/>
                    <property name="characterEncoding" value="utf-8"/>
                bean>
            property>
        bean>
    property>
bean>


<mvc:annotation-driven/>


<mvc:view-controller path="/" view-name="index">mvc:view-controller>

注:
当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:

六、RESTful架构

1. RESTful简介

REST: Representational State Transfer,表现层资源状态转移

  • 资源
    资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。
  • 资源的表述
    资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常便用不同的格式。
  • 状态转移
    状态转移说的是:在客户端和服务器端之间转移( transfer)代表资源状态的表述。通过转移和操作资源的表述来间接实现操作资源的目的。

2. RESTful的实现

具体说,就是 HTTP 协议里面,四个表示操作方式的动词: GET、POST、PUT、DELETE.
它们分别对应四种基本操作: GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。
REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
SpringMVC 详细教程及源码讲解_第10张图片
案例演示:

控制层:

@Controller
public class TestRestController {

    @RequestMapping("/")
    public String protal() {
        return "index";
    }

    @GetMapping("/user")
    public String getAllUser() {
        System.out.println("查询所有用户信息 ---> /user ---> get");
        return "success";
    }

    @GetMapping("/user/{id}")
    public String getUserById(@PathVariable String id) {
        System.out.println("查询用户Id为1的用户信息 ---> /user/1 ---> get");
        return "success";
    }

    @PostMapping("/user")
    public String addUser() {
        System.out.println("添加用户信息 ---> /user ---> post");
        return "success";
    }

    /**
     * 注意:浏览器目前只能发送get和post请求
     * 若要发送put和delete请求,需要在web.xml中配置一个过滤器HiddenHttpMethodFilter
     * 配置了过滤器之后,发送的请求要满足两个条件,才能将请求方式转换为put或delete
     * 1、当前请求必须为post
     * 2、当前请求必须传输请求参数_method, method的值才是最终的请求方式
     *
     * */

    @PutMapping("/user")
    public String updateUser() {
        System.out.println("修改用户信息 ---> /user ---> put");
        return "success";
    }

    @DeleteMapping("/user/{id}")
    public String deleteUser(@PathVariable String id) {
        System.out.println("删除用户信息 ---> /user ---> delete");
        System.out.println("用户id: " + id);
        return "success";
    }
}

视图层:

<a th:href="@{/user}">查询所有用户的信息a><br>

<a th:href="@{/user/1}">查询用户ID为1的信息a><br>

<form th:action="@{/user}" method="post">
    <input type="submit" value="添加用户信息">
form>

<form th:action="@{/user}" method="post">
    <input type="hidden" name="_method" value="put">
    <input type="submit" value="编辑用户信息">
form>

<form th:action="@{/user/1}" method="post">
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="删除用户信息">
form>

测试结果:
SpringMVC 详细教程及源码讲解_第11张图片
SpringMVC 详细教程及源码讲解_第12张图片

3. 过滤器HiddenHttpMethodFilter

注意:浏览器目前只能发送get和post请求
* 若要发送put和delete请求,需要在web.xml中配置一个过滤器HiddenHttpMethodFilter
* 配置了过滤器之后,发送的请求要满足两个条件,才能将请求方式转换为put或delete
1、当前请求必须为post
2、当前请求必须传输请求参数_method, method的值才是最终的请求方式
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数_method的值,因此请求参数_method的值才是最终的请求方式


<filter>
  <filter-name>CharacterEncodingFilterfilter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
  
  <init-param>
    <param-name>encodingparam-name>
    <param-value>UTF-8param-value>
  init-param>
  
  <init-param>
    <param-name>forceEncodingparam-name>
    <param-value>trueparam-value>
  init-param>
filter>

<filter>
  <filter-name>httpMethodFilterfilter-name>
  <filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>

<filter-mapping>
  <filter-name>CharacterEncodingFilterfilter-name>
  <url-pattern>/*url-pattern>
filter-mapping>
<filter-mapping>
  <filter-name>httpMethodFilterfilter-name>
  <url-pattern>/*url-pattern>
filter-mapping>


<servlet>
  <servlet-name>springMVCservlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
  <init-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:spring-mvc.xmlparam-value>
  init-param>
servlet>
<servlet-mapping>
  <servlet-name>springMVCservlet-name>
  <url-pattern>/url-pattern>
servlet-mapping>

目前为止,SpringMVC中提供了两个过滤器: CharacterEncodingFilter和HiddenHttpMethodFilter
在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter
原因:
在CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding)方法设置字符集的。
request.setCharacterEncoding(encoding)方法要求前面不能有任何获取请求参数的操作。
而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作

源码解析:

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List<String> ALLOWED_METHODS;
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";

    public HiddenHttpMethodFilter() {
    }

    public void setMethodParam(String methodParam) {
        Assert.hasText(methodParam, "'methodParam' must not be empty");
        this.methodParam = methodParam;
    }
    // 执行过滤操作
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        // 判断请求方式为post请求
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            // 获取请求参数为_method的参数,设置请求方式
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HttpMethodRequestWrapper(request, method);
                }
            }
        }

        filterChain.doFilter((ServletRequest)requestToUse, response);
    }

    static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }

    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }

        public String getMethod() {
            return this.method;
        }
    }
}

七、SpringMVC处理ajax请求

1. @RequestBody获取请求体信息

@RequestBody可以获取请求体信息,使用@RequestBody注解标识控制器方法的形参,当前请求的请求体就会为当前注解所标识的形参赋值。

演示案例:

控制层:

@RequestMapping("/test/ajax")
public void testAjax(Integer id, @RequestBody String requestBody, HttpServletResponse response) throws IOException {
    System.out.println("id: " + id);
    System.out.println("requestBody: " + requestBody);
    // 响应到浏览器数据
    response.getWriter().write("hello axios");
}

视图层:

<body>
<div id="app">
    <h1>index.html</h1>

    <input type="button" value="测试SpringMVC处理Ajax" @click="testAjax()"><br>

</div>

<script type="text/javascript" th:src="@{/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/js/axios.min.js}"></script>
<script type="text/javascript">
    /*
    axios({
            url: '', //请求路径
            method: '', // 请求方式
            // 以name=value&name=value的方式发送的请求参数,不管使用的请求方式是get或post,请求参数都会被拼接到请求地址后,
            // 此种方式的请求参数可以通过request.getParameter()获取
            params: {},
            // 以json格式发送的请求参数,请求参数会被保存到请求报文的请求体传输到服务器,此种方式的请求参数不可以通过request.getParameter()获取
            data: {}
        }).then(resp => {
            console.log(resp.data)
        })
    * */
    var vue = new Vue({
        el: "#app",
        methods: {
            testAjax() {
                axios.post("/spring_mvc/test/ajax?id=1001", {
                    username: "admin",
                    password: "12346"
                }).then(resp => {
                    console.log(resp.data)
                });
            },
        }
    })
</script>
</body>

测试结果:
在这里插入图片描述

2. @RequestBody获取json格式的请求体参数

SpringMVC 详细教程及源码讲解_第13张图片
使用@RequestBody注解将json格式的请求参数转换为java对象
a>导入jackson的依赖
b>在SpringMVC的配置文件中设置
c>在处理请求的控制器方法的形参位置,直接设置json格式的请求参数要转换的java类型的形参,使用@RequestBody注解标记

2.1 导入jackson依赖

<dependency>
    <groupId>com.fasterxml.jackson.coregroupId>
    <artifactId>jackson-databindartifactId>
    <version>2.15.1version>
dependency>

2.2 测试方法

控制层:

@RequestMapping("/test/ajax/requestBody/json")
public void testAjax(@RequestBody User user, HttpServletResponse response) throws IOException {
    System.out.println(user);
    response.getWriter().write("hello requestBody");
}

视图层:index.html

<body>
<div id="app">
    <h1>index.htmlh1>

    <input type="button" value="测试SpringMVC处理Ajax" @click="testAjax()"><br>

    <input type="button" value="使用@RequestBody注解处理json格式的请求参数" @click="testRequestBody()"><br>
div>

<script type="text/javascript" th:src="@{/js/vue.js}">script>
<script type="text/javascript" th:src="@{/js/axios.min.js}">script>
<script type="text/javascript">

    /*
    axios({
            url: '', //请求路径
            method: '', // 请求方式
            // 以name=value&name=value的方式发送的请求参数,不管使用的请求方式是get或post,请求参数都会被拼接到请求地址后,
            // 此种方式的请求参数可以通过request.getParameter()获取
            params: {},
            // 以json格式发送的请求参数,请求参数会被保存到请求报文的请求体传输到服务器,此种方式的请求参数不可以通过request.getParameter()获取
            data: {}
        }).then(resp => {
            console.log(resp.data)
        })

    * */
    var vue = new Vue({
        el: "#app",
        methods: {
            testAjax() {
                axios.post("/spring_mvc/test/ajax?id=1001", {
                    username: "admin",
                    password: "12346"
                }).then(resp => {
                    console.log(resp.data)
                });
            },
            testRequestBody() {
                axios.post(
                    "/spring_mvc/test/ajax/requestBody/json",
                    {
                        username: "admin",
                        password: "12346"
                    }
                ).then(resp => {
                    console.log(resp.data)
                })
            },
    })
script>
body>

测试结果:
在这里插入图片描述

3. @ResponseBody响应浏览器数据

@ResponseBody用于标识一个控制方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器

@ResponseBody
@RequestMapping("/test/responseBody")
public String testResponseBody() {
    return "success";
}

4. @ResponseBody响应浏览器json格式数据

使用@ResponseBody注解响应浏览器json格式的数据
a>导入jackson的依赖
b>在SpringMVC的配置文件中设置
c>将需要转换为json字符串的java对象直接作为控制器方法的返回值,使用@ResponseBody注解标识控制器方法,就可以将java对象直接转换为json字符串,并响应到浏览器。

演示案例:

控制器方法:

@ResponseBody
@RequestMapping("/test/responseBody/json")
public User testResponseBodyJson() {
    User user = new User("张三", "123465", 23);
    return user;
}

请求方法:

testResponseBody() {
    axios.get("/spring_mvc/test/responseBody/json").then(resp => {
        console.log(resp.data)
    })
}

测试结果:
SpringMVC 详细教程及源码讲解_第14张图片

5. @RestController注解

@RestController注解是springMVC提供的一个复合注解,标识在控制的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解

八、拦截器与过滤器

1. 简介

过滤器和拦截器。这两者在功能方面很类似,但是在具体技术实现方面,差距还是比较大的。
Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理的方式来执行。
Filter的生命周期由Servlet容器管理,而拦截器则可以通过IOC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

2. 过滤器的使用

目前为止,SpringMVC中提供了两个过滤器: CharacterEncodingFilter和HiddenHttpMethodFilter
在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter。

原因:

在CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding)方法设置字符集的。
request.setCharacterEncoding(encoding)方法要求前面不能有任何获取请求参数的操作。而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作。

具体使用见上文

3. 拦截器的使用

SpringMVC中的拦截器用于拦截控制器方法的的执行

3.1 拦截器的配置

SpringMVC中的拦截器需要实现HandlerInterceptor接口

HandlerInterceptor接口

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置

bean和ref标签所配置的拦截器默认对DispatcherServlet处理的所有的请求进行拦截


    <mvc:interceptors>
    
        
        
        
        
        
        <mvc:interceptor>
            
            <mvc:mapping path="/*"/>
            
            <mvc:exclude-mapping path="/abc"/>
            
            <ref bean="firstInterceptor"/>
        mvc:interceptor>
    mvc:interceptors>

3.2 拦截器的三个方法

preHandle(): 在控制器方法执行之前执行,其返回值表示对控制器方法的拦截(false)或放行(true)
postHandle(): 在控制器方法执行之后执行
afterCompletion(): 在控制器方法执行之后,且染视图完毕之后执行

演示案例:

创建拦截器

// 实现HandlerInterceptor接口
@Component
public class FirstInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("FirstInterceptor ---> preHandle");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("FirstInterceptor ---> postHandle");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("FirstInterceptor ---> afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

测试结果:
在这里插入图片描述

3.3 多个拦截器的执行顺序

多个拦截器的执行顺序和在SpringMVC的配置文件中配置的顺序有关

preHandle()按照配置的顺序执行,而postHandle()和afterCompletion()按照配置的反序执行

若拦截器中有某个拦截器的preHandle()返回了false

  • 拦载器的preHandle()返回false和它之前的拦截器的preHandle()都会执行
  • 所有的拦截器的postHandle()都不执行
  • 拦截器的preHandle()返回false之前的拦截器的afterCompletion()会执行

演示案例:

创建第二个拦截器

@Component
public class SecondInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("SecondInterceptor ---> preHandle");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("SecondInterceptor ---> postHandle");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("SecondInterceptor ---> afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

配置spring-mvc.xml


    <mvc:interceptors>
        <mvc:interceptor>
            
            <mvc:mapping path="/*"/>
            
            <mvc:exclude-mapping path="/abc"/>
            
            <ref bean="firstInterceptor"/>
        mvc:interceptor>

        <bean class="com.fd.springmvc.interceptor.SecondInterceptor"/>
    mvc:interceptors>

测试结果:
SpringMVC 详细教程及源码讲解_第15张图片

4. 拦截器实现源码解析(了解)

spring-webmvc:5.3.19版本

...
// 前端控制器DispatchServlet的调度操作方法
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 {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                // 获取拦截器的执行链
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                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;
                    }
                }
                // 执行拦截器的preHandle方法,如果方法返回false,直接结束方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 执行控制器方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                // 执行拦截器的postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }
            // 处理前端控制器调度结果
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                // 执行拦截器的afterCompletion方法
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}

// 处理前端控制器调度结果的方法
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) {
            // 执行拦截器的afterCompletion方法
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }

    }
}

HandlerExecutionChain类

// 拦截器的处理链
public class HandlerExecutionChain {
    // 控制器执行前的拦截器方法preHandle
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // preHandle()按照配置的顺序执行
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            // 如果preHandle返回false
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 执行拦截器的AfterCompletion方法
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }
    
        return true;
    }
    
    // postHandle方法
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        // postHandle()和afterCompletion()按照配置的反序执行
        for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }

    // afterCompletion方法
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
        // postHandle()和afterCompletion()按照配置的反序执行
        for(int i = this.interceptorIndex; i >= 0; --i) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
    
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            } catch (Throwable var7) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
            }
        }
    }
}

九、异常处理器

SpringMVC在处理请求过程中出现异常信息交由异常处理器进行处理,通过实现HandlerExceptionResolver接口可以实现一个系统的异常处理逻辑。

1. SpringMVC自带的异常处理器SimpleMappingExceptionResolver

1.1 基于配置的异常处理

在spring-mvc.xml中配置异常处理bean


    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        
        <property name="exceptionMappings">
            <props>
                
                <prop key="ArithmeticException">errorprop>
            props>
        property>
        
        <property name="exceptionAttribute" value="ex">property>
    bean>

案例演示:

控制器方法:

@Controller
public class TestController {

    @RequestMapping("/test")
    public String testInterceptor() {
        System.out.println(1/0);
        return "success";
    }
}

error视图

DOCTYPE html>
<html lang="en" xmlns: th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>错误title>
head>
<body>

<h1>error.htmlh1>

<p th:text="${ex}">p>
body>
html>

控制器报ArithmeticException异常触发异常处理器,跳转到error逻辑视图,并向浏览器返回异常信息

1.2 基于注解的异常处理

创建异常处理组件类,通过@ControllerAdvice注解进行标记,触发异常时将会执行组件内相关异常处理方法

package com.fd.springmvc.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

//将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {

    //设置要处理的异常信息
    @ExceptionHandler(ArithmeticException.class)
    public String error(Throwable ex, Model model) {
        //ex表示控制器方法所出现的异常
        model.addAttribute("ex", ex);
        return "error";
    }
}

1.3 源码解析

SimpleMappingExceptionResolver类继承AbstractHandlerExceptionResolver

public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {

	...
	// 异常处理方法
	@Nullable
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    	// 获取异常逻辑视图
        String viewName = this.determineViewName(ex, request);
        if (viewName != null) {
            Integer statusCode = this.determineStatusCode(request, viewName);
            if (statusCode != null) {
                this.applyStatusCodeIfPossible(request, response, statusCode);
            }

            return this.getModelAndView(viewName, ex, request);
        } else {
            return null;
        }
    }
	
}

AbstractHandlerExceptionResolver类实现HandlerExceptionResolver接口

public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
	
	...
	
	@Nullable
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
        if (!this.shouldApplyTo(request, handler)) {
            return null;
        } else {
            this.prepareResponse(ex, response);
            // 执行异常处理的方法,抽象方法由子类实现
            ModelAndView result = this.doResolveException(request, response, handler, ex);
            if (result != null) {
                if (this.logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
                    this.logger.debug(this.buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));
                }

                this.logException(ex, request);
            }

            return result;
        }
    }
	
	@Nullable
    protected abstract ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

2. 自定义异常处理器

实现HandlerExceptionResolver接口

public class CustomHandleException implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
                                         Exception exception) {
        // 定义异常信息
        String msg;

        // 判断异常类型
        if (exception instanceof MyException) {
            // 如果是自定义异常,读取异常信息
            msg = exception.getMessage();
        } else {
            // 如果是运行时异常,则取错误堆栈,从堆栈中获取异常信息
            Writer out = new StringWriter();
            PrintWriter s = new PrintWriter(out);
            exception.printStackTrace(s);
            msg = out.toString();
        }

        // 返回错误页面,页面显示错误信息
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("ex", msg);
        modelAndView.setViewName("error");

        return modelAndView;
    }
}

在spring-mvc.xml中添加:


<bean class="com.fd.springmvc.handler.CustomHandleException" id="customHandleException"/>

3. 异常处理源码解析(了解)

spring-webmvc:5.3.19版本

public class DispatcherServlet extends FrameworkServlet {
    @Nullable
    private List<HandlerExceptionResolver> handlerExceptionResolvers; //异常处理器

    ... 
    
    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 {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
    
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    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;
                    }
                    // 执行控制器方法
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
    
                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                // 处理控制器方法结果,如果上面方法报错,则dispatchException不为null
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }
    
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }
    
        }
    }
}

// 处理前端控制器调度结果的方法
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) {
            // 执行拦截器的afterCompletion方法
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }

    }
    
    // 异常处理方法
    @Nullable
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
        request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        ModelAndView exMv = null;
        if (this.handlerExceptionResolvers != null) {
            Iterator var6 = this.handlerExceptionResolvers.iterator();
    
            while(var6.hasNext()) {
                HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
                // 执行具体实现的异常处理方法
                exMv = resolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }
        // 异常逻辑视图
        if (exMv != null) {
            if (exMv.isEmpty()) {
                request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
                return null;
            } else {
                if (!exMv.hasView()) {
                    String defaultViewName = this.getDefaultViewName(request);
                    if (defaultViewName != null) {
                        exMv.setViewName(defaultViewName);
                    }
                }
    
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Using resolved error view: " + exMv, ex);
                } else if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Using resolved error view: " + exMv);
                }
    
                WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
                return exMv;
            }
        } else {
            throw ex;
        }
    }
}

十、注解配置SpringMVC

使用配置类和注解代替web.xml和SpringMVC配置文件的功能

1. 创建初始化类,代替web.xml

在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerlnitializer接口的类,如果找到的话就用它来配置Servlet容器。

Spring提供了这个接口的实现,名为SpringServletContainerlnitializer,这个类反过来又会查找实现WebApplicationlnitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationlnitializer基础实现,名为AbstractAnnotationConfigDispatcherServletlnitializer,当我们的类扩展了AbstractAnnotationConfigDispatcherServletlnitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文

// 代替web.xml
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {

    //设置一个配置类代替spring的配置文件
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    //设置一个配置类代替springMVC的配置文件
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    //设置springMVC的前端控制器DispatcherServlet的urL-pattern
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //设置过滤器
    @Override
    protected Filter[] getServletFilters() {
        // 设置编码过滤器
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        // 设置处理请求方式的过滤器
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }
}

2. 创建SpringConfig配置类,代替Spring的配置文件

/*代替Spring.xml配置文件*/

@Configuration //将类标识为配置类
@ComponentScan(value = "com.fd.springMVC")
public class SpringConfig {

}

3. 创建WebConfig配置类,代替SpringMVC的配置文件

/*
    代替Spring-mvc.xml配置文件

    扫描组件、视图解析器、默认的servlet、mvc的注解驱动、视图控制器、文件上传解析器、拦截器、异常解析器
* */

@Configuration //将类标识为配置类
@ComponentScan("com.fd.springMVC.controller") // 开启扫描组件
@EnableWebMvc // 开启mvc注解驱动
public class WebConfig implements WebMvcConfigurer {

    @Override
    // 设置默认的Servlet,处理静态资源
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    // 配置视图控制器
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }

    // @Bean注解修饰的方法返回对象可以被SpringIOC管理,bean的id为方法的方法名
    @Bean
    // 配置文件上传解析器
    public CommonsMultipartResolver multipartResolver() {
        // 默认编码格式为UTF-8
        return new CommonsMultipartResolver();
    }

    @Override
    // 配置拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        FirstInterceptor firstInterceptor = new FirstInterceptor();
        // 添加拦截器,设置拦截的路径和排除的路径
        registry.addInterceptor(firstInterceptor).addPathPatterns("/**").excludePathPatterns("/abc");
    }

    // 配置异常处理
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        // 添加异常错误类型,以及出现该异常时要跳转的页面所对应的逻辑视图
        properties.setProperty("java.lang.ArithmeticException", "error");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        // 设置异常信息共享在请求域中的异常信息属性名“ex”
        simpleMappingExceptionResolver.setExceptionAttribute("ex");
        // 添加异常处理解析器
        resolvers.add(simpleMappingExceptionResolver);
    }

    // 配置生成模板解析器
    @Bean
    public ITemplateResolver templateResolver() {
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        // ServletContextTemplateResolver需要一个servletContext作为构造参数,可以通过WebApplicationContext获取
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }

    // 生成模板引擎并为模板引擎注入模板解析器
    @Bean // @Bean注解修饰的方法返回对象可以被SpringIOC管理,bean的id为方法的方法名
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) { // 注入Bean的同时为bean的属性赋值可以设置在bean方法的参数里,相当于引用赋值
        SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
        springTemplateEngine.setTemplateResolver(templateResolver);
        return springTemplateEngine;
    }

    //生成视图解析器并为解析器注入模板引擎
    @Bean // @Bean注解修饰的方法返回对象可以被SpringIOC管理,bean的id为方法的方法名
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) { // 注入Bean的同时为bean的属性赋值可以设置在bean方法的参数里,相当于引用赋值,要求:参数名需要和引用的bean的id相同
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setOrder(1);
        // 设置模板引擎
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }
}

十一、SpringMVC执行流程

1. SpringMVC常用组件

  • DispatcherServlet: 前端控制器,不需要工程师开发,由框架提供
    作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求。
  • HandlerMapping: 处理器映射器,不需要工程师开发,由框架提供
    作用: 根据请求的url、method等信息查找Handler,即控制器方法。
  • Handler:处理器(开发人员创建的控制器方法),需要工程师开发
    作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
  • HandlerAdapter: 处理器适配器,不需要工程师开发,由框架提供
    作用:通过HandlerAdapter对处理器(控制器方法)进行执行
  • ViewResolver: 视图解析器,不需要工程师开发,由框架提供
    作用: 进行视图解析,得到相应的视图,例如: ThymeleafView、InternalResourceView、RedirectView
  • View: 视图页面
    作用:将模型数据通过页面展示给用户

2. DispatcherServlet初始化过程

DispatcherServlet 本质上是一个Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。
SpringMVC 详细教程及源码讲解_第16张图片

2.1 初始化WebApplicationContext

所在类: org.springframework.web.servlet.FrameworkServlet

2.2 创建WebApplicationContext

所在类:org.springframework.web.servletFrameworkServlet

2.3 DispatcherServlet初始化策略

FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件

所在类: org.springframework.web.servlet.DispatcherServlet

2.4 源码继承调用解析

public interface Servlet {
    // 初始化方法
    void init(ServletConfig var1);
    // 执行方法
    void service(ServletRequest var1, ServletResponse var2);
}

public abstract class GenericServlet implements Servlet {
    
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    
    public void init() throws ServletException {
    }    
}

public abstract class HttpServlet extends GenericServlet {
    
}

public abstract class HttpServletBean extends HttpServlet {
    public final void init() throws ServletException {
        PropertyValues pvs = new ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        ...
        this.initServletBean();
    }
    
    protected void initServletBean() throws ServletException {
    }
}

public abstract class FrameworkServlet extends HttpServletBean {
    
    protected final void initServletBean() throws ServletException {
        ...
        try {
            this.webApplicationContext = this.initWebApplicationContext();
            this.initFrameworkServlet();
        } catch (RuntimeException | ServletException var4) {
            this.logger.error("Context initialization failed", var4);
            throw var4;
        }
        ...
    }
    
    protected WebApplicationContext initWebApplicationContext() {
        // 获取父级容器(Spring容器)
        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);
                }
            }
        }
        
        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;
    }
    
    protected void onRefresh(ApplicationContext context) {
    }
}

public class DispatcherServlet extends FrameworkServlet {
    
    protected void onRefresh(ApplicationContext context) {
        this.initStrategies(context);
    }
    // 初始化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);
    }
}

3. DispatcherServlet调用组件处理请求

3.1 processRequest()

FrameworkServlet重写HttpServlet中的service和doXxx(),这些方法中调用了processRequest(request,response)
所在类: org.springframework.web.servlet.FrameworkServlet

3.2 doService()

所在类: orgspringframework.web.servlet.DispatcherServlet

3.3 doDispatch()

所在类:orgspringframework.web.servlet.DispatcherServlet

3.4 processDispatchResult()

3.5 源码继承调用解析

public interface Servlet {
    // 初始化方法
    void init(ServletConfig var1);
    // 执行方法
    void service(ServletRequest var1, ServletResponse var2);
}

public abstract class GenericServlet implements Servlet {
    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}

public abstract class HttpServlet extends GenericServlet {
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException("non-HTTP request or response");
        }
    
        this.service(request, response);
    }    
    
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }
}

public abstract class HttpServletBean extends HttpServlet {

}

public abstract class FrameworkServlet extends HttpServletBean {
    
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
            super.service(request, response);
        } else {
            this.processRequest(request, response);
        }
    }
    
    protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }
    
    protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }
    
    protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }
    
    protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }
    
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ...
        try {
            this.doService(request, response);
        } 
        ...
    }
}

public class DispatcherServlet extends FrameworkServlet {
    
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...
        try {
            this.doDispatch(request, response);
        }
        ...
    }
    // 处理请求执行方法
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...
        try {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
    
                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    // 获取处理适配器
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    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;
                        }
                    }
                    // 执行拦截器的preHandle方法
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    // 执行控制器方法
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
    
                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                // // 处理前端控制器调度结果的方法, 如果出现异常走异常处理器
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }
    
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }
    
        }
    }
}

4. SpringMVC的执行流程

1)用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获

2)DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:

a)不存在

i.再判断是否配置了mvc:default-servlet-handler

ii.如果没配置,则控制台报映射查找不到,客户端展示404错误

iii.如果有配置,则访问目标资源(一般为静态资源,如: JS,CSS,HTML),找不到客户端也会展示404错误

b)存在则执行下面的流程

3)根据该URL,调用HandlerMapping获得该Handler配置的所有相关的对象 (包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。

4)DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。

5)如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法[正向]

6)提取Request中的模型数据,填充Handler入参,开始执行Handler (Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

a)HttpMessageConveter: 将请求消息(如json、xml等数据)转换成一个对象,将对象转换为指定的响应信息

b)数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

c)数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

d)数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

7)Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象

8)此时将开始执行拦截器的postHandle(…)方法[逆向]

9)根据返回的ModelAndView (此时会判断是否存在异常: 如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。

10)渣染视图完毕执行拦截器的afterCompletion(…)方法[逆向]。

11)将渲染结果返回给客户端

SpringMVC 详细教程及源码讲解_第17张图片
上图是在网上看到的觉得很形象就借用过来了,原图作者:云川之下

十二、SSM整合练习

项目结构
SpringMVC 详细教程及源码讲解_第18张图片

1. 引入依赖


    <properties>
        <spring-version>5.3.19spring-version>
    properties>

    <dependencies>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>${spring-version}version>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>${spring-version}version>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-jdbcartifactId>
            <version>${spring-version}version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-aopartifactId>
            <version>${spring-version}version>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-testartifactId>
            <version>${spring-version}version>
        dependency>
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.7version>
        dependency>
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatis-springartifactId>
            <version>2.0.6version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.2.11version>
        dependency>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.28version>
        dependency>
        
        <dependency>
            <groupId>log4jgroupId>
            <artifactId>log4jartifactId>
            <version>1.2.17version>
        dependency>
        
        <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelperartifactId>
            <version>5.3.0version>
        dependency>
        
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.11version>
        dependency>
        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>4.0.1version>
        dependency>
        
        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
            <version>2.15.1version>
        dependency>
        
        <dependency>
            <groupId>commons-fileuploadgroupId>
            <artifactId>commons-fileuploadartifactId>
            <version>1.4version>
        dependency>
        
        <dependency>
            <groupId>org.thymeleafgroupId>
            <artifactId>thymeleaf-spring5artifactId>
            <version>3.0.15.RELEASEversion>
        dependency>
    dependencies>

2. 配置文件

web.xml

DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Applicationdisplay-name>

  <context-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:spring.xmlparam-value>
  context-param>

  

  
  <filter>
    <filter-name>characterEncodingFilterfilter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
    <init-param>
      <param-name>encodingparam-name>
      <param-value>UTF-8param-value>
    init-param>
    <init-param>
      <param-name>forceEncodingparam-name>
      <param-value>1param-value>
    init-param>
  filter>
  <filter-mapping>
    <filter-name>characterEncodingFilterfilter-name>
    <url-pattern>/*url-pattern>
  filter-mapping>

  <listener>
    
    <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
  listener>

  
  <servlet>
    <servlet-name>dispatcherServletservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    <init-param>
      <param-name>contextConfigLocationparam-name>
      <param-value>classpath:spring-mvc.xmlparam-value>
    init-param>
    <load-on-startup>1load-on-startup>
  servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServletservlet-name>
    <url-pattern>/url-pattern>
  servlet-mapping>

web-app>

spring-mvc.xml


<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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.fd.ssm.controller"/>

    
    <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver" id="viewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="utf-8"/>
                    bean>
                property>
            bean>
        property>
    bean>

    
    <mvc:default-servlet-handler>mvc:default-servlet-handler>

    
    <mvc:annotation-driven/>

    
    <mvc:view-controller path="/" view-name="index"/>

    
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
        <property name="defaultEncoding" value="UTF-8">property>
    bean>
beans>

spring.xml


<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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    
    <context:property-placeholder location="classpath:jdbc.properties"/>

    
    <context:component-scan base-package="com.fd.ssm">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    context:component-scan>

    
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="druidDataSource"/>
    bean>

    
    <tx:annotation-driven transaction-manager="transactionManager"/>

    
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="druidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>

    
    
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
        
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        
        <property name="dataSource" ref="druidDataSource"/>
        
        <property name="typeAliasesPackage" value="com.fd.ssm.pojo"/>
        
        
    bean>

    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.fd.ssm.mapper"/>
    bean>
beans>

mybatis-config.xml


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    
    <settings>
        
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        
        <setting name="lazyLoadingEnabled" value="true"/>
        
        <setting name="aggressiveLazyLoading" value="false"/>
    settings>
    
    <plugins>
        
        <plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
    plugins>

configuration>

3. 控制器方法

@Controller
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private IEmployeeService employeeService;

    @GetMapping
    public String listEmployee(Model model) {
        List<Employee> list =  employeeService.listEmployee();
        // 将数据共享在请求域
        model.addAttribute("list", list);
        return "employee_list";
    }

    @GetMapping("/page/{pageNum}")
    public String listPageEmployee(@PathVariable int pageNum, Model model) {
        // 开启导航分页
        PageHelper.startPage(pageNum, 4);
        // 获取分页后的数据
        List<Employee> list = employeeService.listEmployee();
        /*
        Pagelnfo{
        
            pageNum:当前页的页码
            pageSize: 每页显示的条数
            size:当前页显示的真实条数
            total: 总记录数
            pages:总页数
            prePage:上一页的页码
            nextPage:下一页的页码
            isFirstPage/isLastPage: 是否为第一页/最后一页
            hasPreviousPage/hasNextPage: 是否存在上一页/下一页
            navigatePages:导航分页的页码数
            navigatepageNums:导航分页的页码,[1,2,3,4,5]
        */
        // 获取分页相关数据
        PageInfo<Employee> pageInfo = new PageInfo<Employee>(list, 5);
        // 将分页数据共享在请求域
        model.addAttribute("pageInfo", pageInfo);
        return "employee_page_list";
    }
}

4. 业务层方法

@Service
public class EmployeeServiceImpl implements IEmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    public List<Employee> listEmployee() {
        return employeeMapper.listEmployee();
    }

    public List<Employee> listPageEmployee(int pageNum) {
        // 开启导航分页
        PageHelper.startPage(pageNum, 4);
        return employeeMapper.listEmployee();
    }
}

5. 数据层方法

public interface EmployeeMapper {
    List<Employee> listEmployee();
}

6. mapper映射文件


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fd.ssm.mapper.EmployeeMapper">

    <select id="listEmployee" resultType="employee">
        select * from t_employee
    select>
mapper>

7. 表现层

index.html

DOCTYPE html>
<html lang="en" xmlns: th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页title>
head>
<body>

<h1>index.htmlh1>

<a th:href="@{/employee}">查询所有用户的信息a><br>

<a th:href="@{/employee/page/1}">查询分页用户的信息a><br>
body>
html>

employee_list.html

DOCTYPE html>
<html lang="en" xmlns: th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>员工列表title>
head>
<body>

<p th:each="item : ${list}" th:text="${item}">p>
body>
html>

employee_page_list.html

DOCTYPE html>
<html lang="en" xmlns: th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>员工列表title>
head>
<body>

<p th:each="item : ${pageInfo.list}" th:text="${item}">p>

<div style="text-align: center">
    <a th:if="${pageInfo.hasPreviousPage}" th:href="@{/employee/page/1}">首页a>
    <a th:if="${pageInfo.hasPreviousPage}" th:href="@{'/employee/page/' + ${pageInfo.prePage}}">上一页a>
    <a th:if="${pageInfo.hasNextPage}" th:href="@{'/employee/page/' + ${pageInfo.nextPage}}">下一页a>
    <a th:if="${pageInfo.hasNextPage}" th:href="@{'/employee/page/' + ${pageInfo.pages}}">尾页a>
div>
body>
html>

8. 测试结果

SpringMVC 详细教程及源码讲解_第19张图片
这里主要提供一个整合及实现的思路,实现的功能也比较简单就是一个员工数据的查询,只要项目整合成功其他业务逻辑其实都差不多,所以不多做缀诉。

总算整理完了,从开始动笔到现在花了半个多月。断断续续的有空就补充点,自己在整理笔记的过程中也是去深入理解消化的过程,所以比较慢。希望能对其他朋友也有点帮助!

你可能感兴趣的:(Spring全家桶,spring,java,mybatis)