SpringMVC 详细教程及源码讲解

news2024/11/25 22:28:42

目录

  • 一、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模块

在这里插入图片描述

2.2 引入依赖

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  <!--springMVC-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.19</version>
  </dependency>
  <dependency>
    <!--日志-->
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
  </dependency>
  <!--ServletAPI-->
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
  </dependency>
  <!--Spring5和Thymeleaf整合包-->
  <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.15.RELEASE</version>
  </dependency>
</dependencies>

3. 配置web.xml

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

3.1 默认配置方式

此配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为 -servlet.xml,
例如,以下配置所对应SpringMVC的配置文件位于WEB-NF下,文件名为springMVC-servlet.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 Application</display-name>

  <!--
      配置SpringMVC的前端控制器DispatcherServlet

      springMVC的配置文件默认的位置和名称
      位置: WEB-INF下
      名称: <servlet-name>-servlet.xml,当前配置下的配置文件名为springMVC-servlet.xml

      url-pattern中/和/*的区别:
        /:匹配浏览器向服务器发送的所有请求(不包括jsp)
        /*:匹配浏览器向服务器发送的所有请求(包括jsp)
  -->
  <servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

3.2 扩展配置方式

web.xml默认配置方式,spring配置文件位于WEB-INF目录下
在这里插入图片描述
如果要将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 Application</display-name>

  <!--
      配置SpringMVC的前端控制器DispatcherServlet

      springMVC的配置文件默认的位置和名称
      位置: WEB-INF下
      名称: <servlet-name>-servlet.xml,当前配置下的配置文件名为springMVC-servlet.xml

      url-pattern中/和/*的区别:
        /:匹配浏览器向服务器发送的所有请求(不包括jsp)
        /*:匹配浏览器向服务器发送的所有请求(包括jsp)
  -->
  <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>
    <!--将DispatcherServlet的初始化时间提前到服务器启动时,避免初次访问加载时间过长-->
    <load-on-startup>1</load-on-startup>
  </servlet>

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

编译后的文件目录:
在这里插入图片描述

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的配置文件

<?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"
       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>

    <!--配置Thymeleaf视图解析器-->
    <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 项目结构

在这里插入图片描述

6.2 视图层实现

index.html代码

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

<!--
    themeleaf命名空间,自动添加上下文
    th:href="@{/hello} 对应路径:localhost:8080/springMvC/hello
-->
    <a th:href="@{/hello}">测试SpringMVC</a>
    <a href="/hello">测试绝对路径</a>

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

6.3 在tomcat服务器启动服务

在这里插入图片描述

在这里插入图片描述

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>Title</title>
</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";
}

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

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中进行注册

<!--配置Spring的编码过滤器-->
<filter>
  <filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-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>Title</title>
</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<String, Object>
    在这里插入图片描述

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>

<!--配置Thymeleaf视图解析器-->
<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的注解驱动-->
<mvc:annotation-driven/>

<!--
    视图控制器,为当前的请求直接设置视图名称实现页面跳转
    若设置视图控制器,则只有视图控制器所设置的请求会被处理,其他的请求将全部404,此时必须在配置一个标签:<mvc:annotation-driven />
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>

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

六、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 地址的一部分,以保证整体风格的一致性。
在这里插入图片描述
案例演示:

控制层:

@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>

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

3. 过滤器HiddenHttpMethodFilter

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

<!--配置Spring的编码过滤器-->
<filter>
  <filter-name>CharacterEncodingFilter</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>
  <filter-name>httpMethodFilter</filter-name>
  <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>CharacterEncodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>httpMethodFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<!--设置SpringMVC的前端控制器-->
<servlet>
  <servlet-name>springMVC</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-mvc.xml</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>springMVC</servlet-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格式的请求体参数

在这里插入图片描述
使用@RequestBody注解将json格式的请求参数转换为java对象
a>导入jackson的依赖
b>在SpringMVC的配置文件中设置<mvc:annotation-driven />
c>在处理请求的控制器方法的形参位置,直接设置json格式的请求参数要转换的java类型的形参,使用@RequestBody注解标记

2.1 导入jackson依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.1</version>
</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.html</h1>

    <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的配置文件中设置<mvc:annotation-driven />
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)
    })
}

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

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>
    
        <!--bean和ref标签所配置的拦截器默认对DispatcherServlet处理的所有的请求进行拦截-->
        <!--<bean class="com.fd.springmvc.interceptor.FirstInterceptor"/>-->
        <!--<ref bean="firstInterceptor"></ref>-->
        
        <!--第三种:自定义请求路径拦截-->
        <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>

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

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

<!--配置自定义异常处理,使用SpringMVC自带的SimpleMappingExceptionResolver-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!--异常映射到指定逻辑试图-->
        <property name="exceptionMappings">
            <props>
                <!--key设置要处理的异常,value设置出现该异常时要跳转的页面所对应的逻辑视图-->
                <prop key="ArithmeticException">error</prop>
            </props>
        </property>
        <!--设置异常信息共享在请求域中,value为请求域的异常信息的属性名-->
        <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.html</h1>

<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 生命周期来进行调度。
在这里插入图片描述

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执行流程
上图是在网上看到的觉得很形象就借用过来了,原图作者:云川之下

十二、SSM整合练习

项目结构
在这里插入图片描述

1. 引入依赖

<!--统一管理依赖版本-->
    <properties>
        <spring-version>5.3.19</spring-version>
    </properties>

    <dependencies>
        <!--Spring上下文依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <!--SpringMVC相关依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <!--事务管理器datasource相关包依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <!--spring整合junit的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <!--mybatis核心依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!--mybatis和spring的整合包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
        <!--数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.11</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--mysql的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <!--log4j日志依赖-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--分页插件依赖-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.3.0</version>
        </dependency>
        <!--日志相关依赖,slf4j门面日志的实现-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>
        <!--servletAPI,DispatcherServlet继承的HttpServlet依赖于Servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <!--json转java对象相关依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.1</version>
        </dependency>
        <!--文件上传的依赖-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
        <!--thymeleaf整合spring5的依赖-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.15.RELEASE</version>
        </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 Application</display-name>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
  </context-param>

  <!--
    url-pattern中/和/*的区别:
        /:匹配浏览器向服务器发送的所有请求(不包括jsp)
        /*:匹配浏览器向服务器发送的所有请求(包括jsp)

  -->

  <!--配置编码过滤器-->
  <filter>
    <filter-name>characterEncodingFilter</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>1</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <listener>
    <!--在服务器启动时加载spring的配置文件-->
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--配置前端控制器DispatcherServlet,处理前端请求映射到控制器-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

spring-mvc.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.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"/>

    <!--配置Thymeleaf视图解析器-->
    <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"/>

    <!--配置文件上传解析器, id必须为multipartResolver-->
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>
</beans>

spring.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: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">

    <!--引入jdbc.properties-->
    <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>

    <!--开启事务管理的注解驱动,将使用注解@Transactional标识的方法或类中所有的方法进行事务管理-->
    <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>

    <!--
        spring整合mybatis配置方式
        mybatis使用方式:
            InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(stream);
            sqlSession = sqlSessionFactory.openSession(true);
            SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);

    -->
    <!--配置sqlSessionFactoryBean,可以直接在Spring的IOC中获取sqlSessionFactory-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
        <!--
            设置myBatis的核心配置文件的路径,既可以通过mybatis的配置文件的方式配置,也可以直接通过spring配置
        -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--配置数据源-->
        <property name="dataSource" ref="druidDataSource"/>
        <!--设置类型别名所对应的包-->
        <property name="typeAliasesPackage" value="com.fd.ssm.pojo"/>
        <!--设置映射文件的路径,只有映射文件的包和mapper接口的包不一致时需要设置-->
        <!--<property name="mapperLocations" value="classpath:mappers/*.xml"/>-->
    </bean>

    <!--
        配置mapper接口的扫描,可以将指定包下所有的mapper接口,通过sqlSession创建代理实现类对象,并将这些对象交给IOC容器管理
        使用时可以通过直接装配mapper接口,来实现mybatis的使用
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.fd.ssm.mapper"/>
    </bean>
</beans>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--
        Mybatis核心配置文件中的标签必须按照指定的顺序配置
        (properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)
    -->
    <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映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!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.html</h1>

<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. 测试结果

在这里插入图片描述
这里主要提供一个整合及实现的思路,实现的功能也比较简单就是一个员工数据的查询,只要项目整合成功其他业务逻辑其实都差不多,所以不多做缀诉。

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

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

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

相关文章

基于ChatGLM的Deepin文档问答Bot

一、背景介绍 题目来源&#xff1a;2023全国大学生计算机系统能力大赛操作系统设计赛-功能挑战赛题目地址&#xff1a;proj225-document-question-answering-bot题目描述&#xff1a;https://wiki.deepin.org 上有900多条deepin系统相关的中文教程和词条&#xff0c;请编写能根…

【第358场周赛】翻倍以链表形式表示的数字,Java解密。

LeetCode 第358场周赛 恒生专场。 文章目录 剑指Offer:翻倍以链表形式表示的数字示例:限制:解题思路:剑指Offer:翻倍以链表形式表示的数字 【题目描述】 给你一个 非空 链表的头节点 head ,表示一个不含前导零的非负数整数。 将链表 翻倍 后,返回头节点 head 。 示例…

django——创建 Django 项目和 APP

2.创建 Django 项目和 APP 命令&#xff1a; 创建Django项目 django-admin startproject name 创建子应用 python manager.py startapp name 2.1 创建工程 在使用Flask框架时&#xff0c;项目工程目录的组织与创建是需要我们自己手动创建完成的。 在django中&#xff0c;…

第四课 学习动词短语

文章目录 前言动词短语动副词组及物动副词组实义动词副词介词动宾词组固定特殊可分开动词短语时态变化规则 一、动副词组1、go ahead 先走&#xff0c;进行 不及物go along 前进&#xff0c;向前走&#xff0c;与....一道去go away 走开&#xff0c;离去&#xff0c;逃走&#…

全网最全的接口自动化测试教程

为什么要做接口自动化 相对于UI自动化而言&#xff0c;接口自动化具有更大的价值。 为了优化转化路径或者提升用户体验&#xff0c;APP/web界面的按钮控件和布局几乎每个版本都会发生一次变化&#xff0c;导致自动化的代码频繁变更&#xff0c;没有起到减少工作量的效果。 而…

用友 U8 CRM 任意文件上传+读取漏洞复现(HW0day)

0x01 产品简介 用友U8 CRM客户关系管理系统是一款专业的企业级CRM软件&#xff0c;旨在帮助企业高效管理客户关系、提升销售业绩和提供优质的客户服务。 0x02 漏洞概述 用友 U8 CRM客户关系管理系统 getemaildata.php 文件存在任意文件上传和任意文件读取漏洞&#xff0c;攻击…

RK3399平台开发系列讲解(入门篇)Linux内核常见的规则

🚀返回专栏总目录 文章目录 一、编码风格二、内核结构分配和初始化三、面向对象的思想沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 本篇将介绍在内核代码的演化过程中应该遵守标准规则 一、编码风格 参考一下内核编码风格手册,它位于内核源代码树的 Documentat…

Flink 火焰图

方式一 使用 Flink Web UI 的 Flame Graph Flink 自己也支持了 Task 粒度的 Flame Graphs 功能,并且可以细化到 subtask 粒度。 第一步:配置启用功能 Flink 作业动态参数里增加配置:“rest.flamegraph.enabled”: “true” 并重启作业。当前该功能没有默认开启,因为这个功…

ROS2 学习(一)介绍,环境搭建,以及个人安装的一些建议

ROS2 学习 学习自b站课程&#xff1a;https://www.bilibili.com/video/BV16B4y1Q7jQ?p1 &#xff08;up主&#xff1a;古月居GYH&#xff09; ROS 介绍 Robot OS&#xff0c;为机器人开发提供了相对完善的 middleware&#xff0c;工具&#xff0c;软件等。 ROS1 对嵌入式设…

ProsperEx 的野望:借势 RWA 浪潮,构建全新的链上衍生品体系

真实资产代币化&#xff08;RWA&#xff09;并不是一个新概念了&#xff0c;以 USDT、USDC、DAI 等一系列美元稳定币是行业内最早的 RWA 概念资产&#xff0c;这些资产以美元为价值基础通过不同信用的机制&#xff0c;将其价值映射至链上&#xff0c;并以加密货币的形式体现&am…

关于memset的小实验

关于memset的小实验 memset是包含在<string.h>的函数&#xff0c;用来给字符数组赋值。然而人们常常把它拿来给整型变量赋值。 void *MEMSET (void *dstpp, int c, size_t len)memset是一个返回通用指针的函数&#xff0c;返回的地址便是输入的地址 int c表示对这块内…

Linux学习之awk函数

awk里边的函数分为内置函数和自定义函数。 内置函数有下边的几种&#xff1a; 算术函数&#xff08;arithmetic&#xff09; 字符串函数&#xff08;string&#xff09; 输入/输出函数和通用函数&#xff08;input/output, and general&#xff09; 自定义函数格式如下&#xf…

企业计算机服务器中了360后缀勒索病毒怎么办,勒索病毒解密数据恢复

随着计算机技术的不断发展&#xff0c;企业的办公系统得到了很大提升&#xff0c;但是随之而来的网络安全威胁也不断增加&#xff0c;勒索病毒的攻击事件时有发生。近期&#xff0c;我们收到某地连锁超市的求助&#xff0c;企业的计算机服务器遭到了360后缀勒索病毒攻击&#x…

【算法题】螺旋矩阵III (求解n阶蛇形矩阵)

一、问题的提出 n阶蛇形矩阵的特点是按照图1所示的方式排列元素。n阶蛇形矩阵是指矩阵的大小为nn&#xff0c;其中n为正整数。 题目背景 一个 n 行 n 列的螺旋矩阵可由如图1所示的方法生成&#xff0c;观察图片&#xff0c;找出填数规律。填数规则为从 1 开始填到 nn。 图1 …

SPI协议简介

什么是SPI&#xff1f; SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xff0c;是美国摩托罗拉公司&#xff08;Motorola&#xff09;最先推出的一种同步串行传输规范&#xff0c;也是一种单片机外设芯片串行扩展接口&#xff0c;是一种高速…

[管理与领导-13]:IT基层管理者 - 激励 - 除了薪资奖金,还有哪些激励手段?

目录 前言 第1章 问题现象&#xff1a;对激励的误解 第一个误解&#xff1a;就是激励就是给钱 第二个误解&#xff1a;就是激励当成了升职 第三个误解&#xff1a;把激励当成忽悠 第2章 背后原因 1 没有找到和满足员工真正的需求&#xff1a;不同人&#xff0c;需求不同…

【数据结构与算法——TypeScript】树结构Tree

【数据结构与算法——TypeScript】 树结构(Tree) 认识树结构以及特性 什么是树? &#x1f332; 真实的树&#xff1a;相信每个人对现实生活中的树都会非常熟悉 &#x1f332; 我们来看一下树有什么特点&#xff1f; ▫️ 树通常有一个根。连接着根的是树干。 ▫️ 树干到…

代码随想录算法训练营之JAVA|第二十六天| 332. 重新安排行程

今天是第26天刷leetcode&#xff0c;立个flag&#xff0c;打卡60天。 算法挑战链接 332. 重新安排行程https://leetcode.cn/problems/reconstruct-itinerary/ 第一想法 题目理解&#xff1a;将多个行程的ticket 用第一个行程的结束地点作为下一个行程的开始地点&#xff0c;…

人工智能轨道交通行业周刊-第55期(2023.8.7-8.13)

本期关键词&#xff1a;北京智慧交通规划、成都数智化规划、关门车、集装箱标志、大模型隐私、视觉大模型 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交…

定量分析计算51单片机复位电路工作原理

下面画出等效电路图 可以知道单片机内必然有一个电阻RX&#xff0c;为了简化分析&#xff0c;我们假设他是线性电阻&#xff08;不带电容&#xff0c;电感的支路&#xff09; 还有一个基础知识&#xff1a; 电容器的充电放电曲线&#xff1a; 还需要知道电容电压的变化是连续…