SpringMVC

1. SpringMVC 简介

(1) MVC 的介绍

     MVC 是一种软件架构的思想,将软件按照模型、

     视图、控制器来划分

MModel模型层,指工程中的 JavaBean

      作用是处理数据

JavaBean 分为两类:

        一类称为实体类 Bean:专门存储业务数据

        的,如 Student、User 等

        一类称为业务处理 Bean:指 Service Dao

        对象,专门用于处理业务逻辑和数据访问

VView视图层,指工程中的 html jsp 等页面,

      作用是与用户进行交互,展示数据

CController控制层,指工程中的 servlet,作用

      是接收请求响应浏览器

MVC 的工作流程:

用户通过视图层发送请求服务器,在服务器中请求

Controller 接收,Controller 调用相应的 Model 层

处理请求,处理完毕将结果返回 Controller

Controller 再根据请求处理的结果找到相应的 View

视图,渲染数据后最终响应给浏览器

(2) SpringMVC 的概念

SpringMVC 是 Spring 的一个后续产品,是 Spring 的

一个子项目,是 Spring 为表述层开发提供的一整套完

备的解决方案

注:三层架构分为表述层 (或表述层)、业务逻辑层数据

      访问层表述层表示前台页面后台 servlet

(3) SpringMVC 的特点

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

2. 入门案例

(1) 创建 maven 工程

① 添加 web 模块

② 打包方式:war

③ 引入依赖

<dependencies>
    <!-- SpringMVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.1</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>3.1.0</version>
        <scope>provided</scope>
    </dependency>

    <!-- Spring5和Thymeleaf整合包 -->
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
        <version>3.0.12.RELEASE</version>
    </dependency>
</dependencies>

注:由于 Maven 的传递性,我们不必将所有需要的包

       全部配置依赖,而是配置最顶端的依赖其他靠传

       递性导入

(3) 配置 web.xml

注册 SpringMVC 的前端控制器 DispatcherServlet

① 默认配置方式

此配置作用下, SpringMVC  的配置文件默认位于
WEB-INF   下,默认名称为  <servlet-name>
-servlet.xml
例如,以下配置所对应  SpringMVC  的配置文件位
于  WEB-INF  下,文件名为  springMVC-servlet.xml
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
    <servlet-name>springMVC</servlet-name>        
    <servletclass>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <!--
        设置springMVC的核心控制器所能处理的请求的请求路径
        /所匹配的请求可以是/login或.html或.js或.css方式的请求路径
        但是/不能匹配.jsp请求路径的请求
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

② 扩展配置方式

可通过  init-param   标签设置  SpringMVC  配置文件
位置 名称 ,通过  load-on-startup  标签设置
SpringMVC  前端控制器  DispatcherServlet   初始
化时间
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servletclass>
    <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
    <init-param>
        <!-- contextConfigLocation为固定值 -->
        <param-name>contextConfigLocation</param-name>
        <!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的
src/main/resources -->
        <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>
    <!--
        设置springMVC的核心控制器所能处理的请求的请求路径
        /所匹配的请求可以是/login或.html或.js或.css方式的请求路径
        但是/不能匹配.jsp请求路径的请求
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

注:

<url-pattern>   标签中使用  /   /*  的区别:
所匹配的请求可以是  /login    .html  或  .js   或  .css 
方式的请求路径,但是  不能 匹配  .jsp   请求路径的
请求,因此就可以避免在访问 jsp  页面时,该请求
被  DispatcherServlet  处理,从而找不到相应的页面
/*  则能够 匹配所有请求
例如在使用过滤器时,若需要对所有请求进行过滤,
就需要使用  /*  的写法

(4) 创建请求控制器

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

(5) 创建 SpringMVC 的配置文件

<!-- 自动扫描包 -->
<context:component-scan base-package="com.zh.mvc.controller"/>

<!-- 配置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>

<!--
    处理静态资源,例如html、js、css、jpg
    若只设置该标签,则只能访问静态资源,其他请求则无法访问
    此时必须设置<mvc:annotation-driven/>解决问题
-->
<mvc:default-servlet-handler/>

<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- 处理响应中文内容乱码 -->
        <bean
class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="defaultCharset" value="UTF-8" />
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html</value>
                    <value>application/json</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

(6) 测试 HelloWorld

① 实现对首页的访问

在请求控制器中创建处理请求的方法

// @RequestMapping 注解:处理请求和控制器方法之间的映射关系
// @RequestMapping 注解的 value 属性可以通过请求地址匹配请求, / 表示的当前工程的上下文路径
// localhost:8080/springMVC/
@RequestMapping ( "/" )
public String index () {
        //设置视图名称
        return "index" ;
}

② 通过超链接跳转到指定页面

在主页 index.html 中设置超链接

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1>首页</h1>
    <a th:href="@{/hello}">HelloWorld</a><br/>
</body>
</html>

在请求控制器中创建处理请求的方法

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

(7) 总结

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

3. @RequestMapping注解

(1) @RequestMapping注解的功能

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

(2)  @RequestMapping注解的位置

@RequestMapping 标识一个:设置映射请求的

请求路径的初始信息

@RequestMapping 标识一个方法:设置映射请求

请求路径的具体信息

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

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

(3)  @RequestMapping注解的 value 属性

@RequestMapping  注解的  value 属性 通过请求
请求地址 匹配 请求映射
@RequestMapping  注解的  value  属性是一个
符串类型的数组 ,表示该请求映射能够匹配 多个
请求地址所对应的请求
@RequestMapping  注解的  value  属性 必须设置
至少通过请求地址匹配请求映射
<a th:href="@{/testRequestMapping}">测试@RequestMapping的value属性--
>/testRequestMapping</a><br>
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
@RequestMapping(
        value = {"/testRequestMapping", "/test"}
)
public String testRequestMapping(){
    return "success";
}

(4) @RequestMapping注解的 method 属性

@RequestMapping  注解的  method  属性通过请求
的请求方式( get  或  post 匹配请求映射
@RequestMapping  注解的  method  属性是一个
RequestMethod  类型的 数组 ,表示该请求映射能
够匹配 多种 请求方式的请求
若当前请求的请求地址 满足 请求映射的 value 属性,
但是请求方式 不满足 method 属性,则 浏览器报错
405:Request method 'POST' not supported
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
<form th:action="@{/test}" method="post">
    <input type="submit">
</form>
@RequestMapping(
    value = {"/testRequestMapping", "/test"},
    method = {RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
    return "success";
}
注:
1 、对于处理指定请求方式的控制器方法,
SpringMVC 中提供了@RequestMapping
的派生注解
处理  get  请求的映射 --> @GetMapping
处理  post  请求的映射 --> @PostMapping
处理  put  请求的映射 --> @PutMapping
处理  delete  请求的映射 --> @DeleteMapping
2 、常用的请求方式有 get post put delete
但是目前浏览器 只支持   get  和  post ,若在 
form  表单 提交 时,为  method  设置了其他请求
方式的字符串(put  或  delete ),则按照 默认
的请求方式  get  处理
若要发送  put  和  delete  请求,则需要通过 
spring  提供的过滤器  HiddenHttpMethodFilter
RESTful 部分会讲到

(5) @RequestMapping注解的 params 属性 (了解)

@RequestMapping 注解的 params 属性通过请求
的请求参数匹配请求映射
@RequestMapping 注解的 params 属性是一个字
符串类型的数组,可以通过四种表达式设置请求
参数和请求映射的匹配关系
"param" :要求请求映射所匹配的请求必须携带
param 请求参数
"!param" :要求请求映射所匹配的请求必须不能
携带 param 请求参数
"param=value" :要求请求映射所匹配的请求必
须携带 param 请求参数且 param=value
"param!=value" :要求请求映射所匹配的请求必
须携带 param 请求参数但是 param!=value
<a th:href="@{/test(username='admin',password=123456)">测试@RequestMapping的
params属性-->/test</a><br>
@RequestMapping(
        value = {"/testRequestMapping", "/test"}
        ,method = {RequestMethod.GET, RequestMethod.POST}
        ,params = {"username","password!=123456"}
)
public String testRequestMapping(){
    return "success";
}
注:
若当前请求满足 @RequestMapping 注解的  value
和  method  属性,但是 不满足   params   属性,
此时页面回 报错400
Parameter conditions "username, password!=
123456" not met for actual request parameters:
username={admin}, password={123456}

(6)  @RequestMapping注解的 headers 属性 (了解)

@RequestMapping 注解的 headers 属性通过请求
的请求头信息匹配请求映射
@RequestMapping 注解的 headers 属性是一个字
符串类型的数组,可以通过四种表达式设置请求
头信息和请求映射的匹配关系
"header" :要求请求映射所匹配的请求必须携带
header 请求头信息
"!header" :要求请求映射所匹配的请求必须不能
携带 header 请求头信息
"header=value" :要求请求映射所匹配的请求必
须携带 header 请求头信息且 header=value
"header!=value" :要求请求映射所匹配的请求必
须携带 header 请求头信息且 header!=value
若当前请求满足 @RequestMapping 注解的 value
method 属性,但是不满足 headers 属性,
此时页面显示 404错误 ,即资源未找到

(7) SpringMVC 支持 ant 风格的路径

:表示 任意 单个字符
* :表示任意的  个或多个字符
** :表示 任意层数 任意目录
注意:在使用  **  时,只能使用  /**/xxx   的方式

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

原始 方式: /deleteUser?id=1
rest  方式: /user/delete/1
SpringMVC  路径中的占位符常用于  RESTful  风格中,
当请求路径中将某些数据通过路径的方式传输到服务
器中,就可以在相应的 @RequestMapping  注解的
value  属性中通过 占位符 {xxx}  表示传输的数据,在通
@PathVariable 注解,将占位符所表示的 数据赋值
给控制器方法的形参
<a th:href="@{/testRest/1/admin}">测试路径中的占位符-->/testRest</a><br>
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id, @PathVariable("username")
String username){
    System.out.println("id:"+id+",username:"+username);
    return "success";
}
//最终输出的内容为-->id:1,username:admin

4. SpringMVC 获取请求参数

(1) 通过 ServletAPI 获取

HttpServletRequest 作为控制器方法的形参

此时 HttpServletRequest 类型的参数表示封装

当前请求报文的对象

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

(2) 通过控制器方法的形参获取请求参数

在控制器方法的形参位置,设置和请求参数 同名
的形参 ,当浏览器发送请求,匹配到请求映射时,
 DispatcherServlet 中就会将请求参数 赋值给相
应的形参
<a th:href="@{/testParam(username='admin',password=123456)}">测试获取请求参数--
>/testParam</a><br>
@RequestMapping("/testParam")
public String testParam(String username, String password){
    System.out.println("username:"+username+",password:"+password);
    return "success";
}
注:
若请求所传输的请求参数中有 多个同名 的请求参数,
此时可以在控制器方法的形参中设置 字符串数组
字符串类型 的形参接收此请求参数
若使用字符串数组类型的形参,此参数的数组中
含了每一个数据
若使用字符串类型的形参,此参数的值为 每个数据
中间使用逗号拼接 的结果

(3) @RequestParam

@RequestParam 是将请求参数和控制器方法的形参
创建映射关系
@RequestParam 注解一共有三个属性:
value :指定为形参赋值的请求参数的 参数名
required :设置是否必须传输此请求参数,默认值为
         true
        若设置为 true  时,则当前请求 必须传输   value 
         所指定的请求参数 ,若没有传输该请求参数,
        且没有设置 defaultValue 属性,
        则页面 报错400
         Required String parameter 'xxx' is not present;
        若设置为 false,则当前请求不是必须传输 value 
        所指定的请求参数,若没有传输,则注解所标识
        的形参的值为 null
defaultValue :不管  required  属性值为  true  或  false
        当 value  所指定的请求参数 没有传输 传输的值
        为 " " 时,则使用 默认值为形参赋值

(4) @RequestHeader

@RequestHeader 是将请求 头信息 和控制器方法的形参
创建映射关系
@RequestHeader 注解一共有三个属性: value required
defaultValue ,用法同 @RequestParam

(5) @CookieValue

@CookieValue 是将 cookie数据 和控制器方法的形参创建
映射关系
@CookieValue 注解一共有三个属性: value required
defaultValue ,用法同 @RequestParam

(6) 通过 POJO 获取请求参数

可以在控制器方法的形参位置设置一个实体类类型的形参,
此时若浏览器传输的请求参数的 参数名 和实体类中的 属性
一致 ,那么请求参数就会 为此属性赋值
<form th:action="@{/testpojo}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    性别:<input type="radio" name="sex" value="男">男<input type="radio"
name="sex" value="女">女<br>
    年龄:<input type="text" name="age"><br>
    邮箱:<input type="text" name="email"><br>
    <input type="submit">
</form>
@RequestMapping("/testpojo")
public String testPOJO(User user){
    System.out.println(user);
    return "success";
}
//最终结果-->User{id=null, username='张三', password='123', age=23, sex='男',
email='123@qq.com'}

(7) 解决获取请求参数的乱码问题

解决获取请求参数的乱码问题,可以使用SpringMVC 
提供的 编码过滤器   CharacterEncodingFilter ,但是
必须在  web.xml  中进行注册
<!--配置springMVC的编码过滤器-->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>        
    <filterclass>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 中处理编码的过滤器一定要配置到

       其他过滤器之前,否则无效

5. 域对象共享数据

(1) 使用 ServletAPI request 域对象共享数据

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

(2) 使用 ModelAndViewrequest 域对象共享数据

@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
    /**
    * ModelAndView有Model和View的功能
    * Model主要用于向请求域共享数据
    * View主要用于设置视图,实现页面跳转
    */
    ModelAndView mav = new ModelAndView();
    //向请求域共享数据
    mav.addObject("testScope", "hello,ModelAndView");
    //设置视图,实现页面跳转
    mav.setViewName("success");
    return mav;
}

(3) 使用 Model request 域对象共享数据

@RequestMapping("/testModel")
public String testModel(Model model){
    model.addAttribute("testScope", "hello,Model");
    return "success";
}

(4) 使用 map request 域对象共享数据

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

(5) 使用 ModelMap request 域对象共享数据

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

(6) Model、ModelMap、Map 的关系

Model、ModelMap、Map类型的参数其实本质上

都是 BindingAwareModelMap 类型的

public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}

(7) 向 session 域共享数据

@RequestMapping("/testSession")
public String testSession(HttpSession session){
    session.setAttribute("testSessionScope", "hello,session");
    return "success";
}

(8) 向 application 域共享数据

@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
    ServletContext application = session.getServletContext();
    application.setAttribute("testApplicationScope", "hello,application");
    return "success";
}

6. SpringMVC 的视图

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

(1) ThymeleafView

当控制器方法中所设置的视图名称没有任何前缀时,
此时的视图名称会被 SpringMVC 配置文件中所配置
的视图解析器解析,视图名称拼接视图 前缀 和视图
后缀 所得到的 最终路径 ,会通过 转发 的方式实现 跳转
@RequestMapping("/testHello")
public String testHello(){
    return "hello";
}

(2) 转发视图

SpringMVC  中默认的转发视图是  InternalResourceView
SpringMVC  中创建转发视图的情况:
当控制器方法中所设置的视图名称以  " forward: 为前缀时,
创建  InternalResourceView  视图,此时的视图名称不会被
SpringMVC  配置文件中所配置的视图解析器解析,而是会
将前缀  "forward:"  去掉, 剩余部分 作为 最终路径 通过转发
的方式实现跳转
例如  "forward:/" "forward:/employee"
@RequestMapping("/testForward")
public String testForward(){
    return "forward:/testHello";
}

(3) 重定向视图

SpringMVC  中默认的重定向视图是  RedirectView
当控制器方法中所设置的视图名称以  " redirect:
前缀时,创建 RedirectView  视图,此时的视图名称
不会被 SpringMVC  配置文件中所配置的视图解析
器解析,而是会将前缀  " redirect: 去掉,剩余部分
作为最终路径通过重定向的方式实现跳转
例如 "redirect:/" "redirect:/employee"
@RequestMapping("/testRedirect")
public String testRedirect(){
    return "redirect:/testHello";
}

(4) 视图控制器 view-controller

当控制器方法中,仅仅用来实现 页面跳转 ,即只需要
设置视图名称时,可以将处理器方法使用  view-controller
标签进行表示
<!--
    path:设置处理的请求地址
    view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>
注:
SpringMVC 中设置任何一个 view-controller 时,其他
控制器中的请求映射将全部失效,此时需要在
SpringMVC 的核心配置文件中设置开启 mvc 注解驱动的
标签:
<mvc:annotation-driven />

7. 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
操作
传统方式
REST 风格
查询 操作
getUserById?id=1
user/1-->get请求方式
保存 操作
saveUser
user--> post 请求方式
删除 操作
deleteUser?id=1
user/1--> delete 请求方式
更新 操作
updateUser
user--> put 请求方式

(3) HiddenHttpMethodFilter

由于浏览器只支持发送  get   和  post   方式的请求,
SpringMVC 提供了 HiddenHttpMethodFilter
POST 请求转换为 DELETE PUT 请求
请求的条件:
        a>当前请求的请求方式 必须  post
        b>当前请求 必须传输 请求 参数   _method
满足以上条件, HiddenHttpMethodFilter 过滤器
就会将当前请求的 请求方式 转换 请求参数
_method  的值,因此请求参数  _method  的值才
是最终的请求方式 在 web.xml  中注册
HiddenHttpMethodFilter
<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filterclass>
</filter>
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

注: 

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

8. RESTful 案例

(1) 准备工作

和传统 CRUD 一样,实现对员工信息的增删改查

① 搭建环境

② 准备实体类

package com.zh.mvc.bean;

public class Employee {

    private Integer id;
    private String lastName;

    private String email;
    //1 male, 0 female
    private Integer gender;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Employee(Integer id, String lastName, String email, Integer
    gender) {
        super();
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
    }

    public Employee() {
    }
}

③ 准备 dao 模拟数据

package com.zh.mvc.dao;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.zh.mvc.bean.Employee;
import org.springframework.stereotype.Repository;

@Repository
public class EmployeeDao {

     private static Map<Integer, Employee> employees = null;

    static{
        employees = new HashMap<Integer, Employee>();
        employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1));
        employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
        employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
        employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
        employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1));
    }

    private static Integer initId = 1006;

    public void save(Employee employee){
        if(employee.getId() == null){
        employee.setId(initId++);
        }
        employees.put(employee.getId(), employee);
    }

    public Collection<Employee> getAll(){
        return employees.values();
    }

    public Employee get(Integer id){
        return employees.get(id);
    }

    public void delete(Integer id){
        employees.remove(id);
    }
}

(2) 功能清单

功能
URL 地址
请求方式
访问首页
/
GET
查询全部数据
/employee
GET
删除
/employee/2
DELETE
跳转 添加 数据页面
/toAdd
GET
执行保存
/employee
POST
跳转 更新 数据页面
/employee/2
GET
执行更新
/employee
PUT

(3) 具体功能:访问首页

① 配置 view-controller

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

② 创建页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" >
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/employee}">访问员工信息</a>
</body>
</html>

(4) 具体功能:查询所有员工数据

① 控制器方法

@RequestMapping(value = "/employee", method = RequestMethod.GET)
public String getEmployeeList(Model model){
    Collection<Employee> employeeList = employeeDao.getAll();
    model.addAttribute("employeeList", employeeList);
    return "employee_list";
}

② 创建 employee_list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Employee Info</title>
    <script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
</head>
<body>

    <table border="1" cellpadding="0" cellspacing="0" style="text-align:
center;" id="dataTable">
        <tr>
            <th colspan="5">Employee Info</th>
        </tr>
        <tr>
            <th>id</th>
            <th>lastName</th>
            <th>email</th>
            <th>gender</th>
            <th>options(<a th:href="@{/toAdd}">add</a>)</th>
        </tr>
        <tr th:each="employee : ${employeeList}">
            <td th:text="${employee.id}"></td>
            <td th:text="${employee.lastName}"></td>
            <td th:text="${employee.email}"></td>
            <td th:text="${employee.gender}"></td>
            <td>
                <a class="deleteA" @click="deleteEmployee"
th:href="@{'/employee/'+${employee.id}}">delete</a>
                <a th:href="@{'/employee/'+${employee.id}}">update</a>
            </td>
        </tr>
    </table>
</body>
</html>

③ 处理静态资源

配置默认的 servlet 处理静态资源

当前工程的 web.xml 配置的前端控制器

DispatcherServlet url-pattern /tomcat

的 web.xml 配置的 DefaultServlet url-pattern

也是 /

此时,浏览器发送的请求会优先被 

DispatcherServlet 进行处理,但是

DispatcherServlet 无法处理静态资源

若配置了<mvc:default-servlet-handler />,此时

浏览器发送的所有请求都会被 DefaultServlet

若配置了<mvc:default-servlet-handler />

<mvc:annotation-driven />,浏览器发送的请求

先被 DispatcherServlet 处理,无法处理在交

DefaultServlet 处理

<mvc:default-servlet-handler />

 开启 mvc 的注解驱动

<mvc:annotation-driven />

配置视图控制器

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

(5) 具体功能:删除

① 创建处理 delete 请求方式的表单

<!-- 作用:通过超链接控制表单的提交,将post请求转换为delete请求 -->
<form id="delete_form" method="post">
    <!-- HiddenHttpMethodFilter要求:必须传输_method请求参数,并且值为最终的请求方式 -->
    <input type="hidden" name="_method" value="delete"/>
</form>

② 删除超链接绑定点击事件

引入 vue.js

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

删除超链接

<a class="deleteA" @click="deleteEmployee"
th:href="@{'/employee/'+${employee.id}}">delete</a>

通过 vue 处理点击事件

<script type="text/javascript">
    var vue = new Vue({
        el:"#dataTable",
        methods:{
            //event表示当前事件
            deleteEmployee:function (event) {
                //通过id获取表单标签
                var delete_form = document.getElementById("delete_form");
                //将触发事件的超链接的href属性为表单的action属性赋值
                delete_form.action = event.target.href;
                //提交表单
                delete_form.submit();
                //阻止超链接的默认跳转行为
                event.preventDefault();
            }
        }
    });
</script>

③ 控制器方法

@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
    employeeDao.delete(id);
    return "redirect:/employee";
}

(6) 具体功能:跳转到添加数据页面

① 配置 view-controller

<mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:viewcontroller>

② 创建 employee_add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Add Employee</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
    lastName:<input type="text" name="lastName"><br>
    email:<input type="text" name="email"><br>
    gender:<input type="radio" name="gender" value="1">male
    <input type="radio" name="gender" value="0">female<br>
    <input type="submit" value="add"><br>
</form>
</body>
</html>

(7) 具体功能:执行保存

① 控制器方法

@RequestMapping(value = "/employee", method = RequestMethod.POST)
public String addEmployee(Employee employee){
    employeeDao.save(employee);
    return "redirect:/employee";
}

(8) 具体功能:跳转到更新数据页面

① 修改超链接

<a th:href="@{'/employee/'+${employee.id}}">update</a>

② 控制器方法

@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id, Model model){
    Employee employee = employeeDao.get(id);
    model.addAttribute("employee", employee);
    return "employee_update";
}

③ 创建 employee_update.html

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

<form th:action="@{/employee}" method="post">
    <input type="hidden" name="_method" value="put">
    <input type="hidden" name="id" th:value="${employee.id}">
    lastName:<input type="text" name="lastName" th:value="${employee.lastName}">
<br>
    email:<input type="text" name="email" th:value="${employee.email}"><br>
    <!--
        th:field="${employee.gender}"可用于单选框或复选框的回显
        若单选框的value和employee.gender的值一致,则添加checked="checked"属性
    -->
    gender:<input type="radio" name="gender" value="1"
th:field="${employee.gender}">male
    <input type="radio" name="gender" value="0"
th:field="${employee.gender}">female<br>
    <input type="submit" value="update"><br>
</form>

</body>
</html>

(9) 具体功能:执行更新

① 控制器方法

@RequestMapping(value = "/employee", method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
    employeeDao.save(employee);
    return "redirect:/employee";
}

9. SpringMVC 处理 ajax 请求

(1) @RequestBody

@RequestBody可以获取请求体信息,使用@RequestBody

注解标识控制器方法的形参,当前请求的请求体就会为当前

注解所标识的形参赋值

<!--此时必须使用post请求方式,因为get请求没有请求体-->
<form th:action="@{/test/RequestBody}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit">
</form>
@RequestMapping("/test/RequestBody")
public String testRequestBody(@RequestBody String requestBody){
    System.out.println("requestBody:"+requestBody);
    return "success";
}

输出结果:

requestBody:username=admin&password=123456

(2) @RequestBody获取 json 格式的请求参数

在使用了 axios 发送 ajax 请求之后,浏览器发送到服务

器的请求参数有两种格式:

①  name=value&name=value... ,此时的请求参数可
     以通过  request.getParameter()   获取,对应
     SpringMVC中,可以直接通过控制器方法的形参
     获取此类请求参数
  {key:value,key:value,...} ,此时 无法 通过 request
      .getParameter() 获取,之前我们使用操作 json
      的相关  jar   包  gson  或  jackson   理此类请求参数,
      可以将其转换为指定的 实体类对象 map集合
      在 SpringMVC 中,直接使用 @RequestBody
      解标识控制器方法的 形参 即可将此类请求参数
      换为 java 对象

使用@RequestBody获取 json 格式的请求参数的条件:

① 导入 jackson 的依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>

② SpringMVC的配置文件中设置开启 mvc 的注解驱动

<!--开启mvc的注解驱动-->
<mvc:annotation-driven />

③ 在控制器方法的形参位置,设置 json 格式的请求参数

要转换成的 java 类型 (实体类或 map) 的参数,并使用

@RequestBody注解标识

<input type="button" value="测试@RequestBody获取json格式的请求参数"
@click="testRequestBody()"><br>

<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">
    var vue = new Vue({
        el:"#app",
        methods:{
            testRequestBody(){
                axios.post(
                    "/SpringMVC/test/RequestBody/json",
                    {username:"admin",password:"123456"}
                ).then(response=>{
                    console.log(response.data);
                });
            }
        }
    });
</script>
//将json格式的数据转换为map集合
@RequestMapping("/test/RequestBody/json")
public void testRequestBody(@RequestBody Map<String, Object> map,
HttpServletResponse response) throws IOException {
    System.out.println(map);
    //{username=admin, password=123456}
    response.getWriter().print("hello,axios");
}
//将json格式的数据转换为实体类对象
@RequestMapping("/test/RequestBody/json")
public void testRequestBody(@RequestBody User user, HttpServletResponse
response) throws IOException {
    System.out.println(user);
    //User{id=null, username='admin', password='123456', age=null,
gender='null'}
    response.getWriter().print("hello,axios");
}

(3) @ResponseBody

@ResponseBody用于标识一个控制器方法,可以

将该方法的返回值直接作为响应报文的响应体响应

到浏览器

@RequestMapping("/testResponseBody")
public String testResponseBody(){
    //此时会跳转到逻辑视图success所对应的页面
    return "success";
}
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
    //此时响应浏览器数据success
    return "success";
}

(4) @ResponseBody响应浏览器 json 数据

服务器处理  ajax  请求之后,大多数情况都需要向
浏览器响应一个  java  对象,此时必须将  java 对象
转换 json 字符串 才可以响应到浏览器,之前我
们使用操作  json  数据的  jar  包  gson  或  jackson 
java  对象转换为 json 字符串。在 SpringMVC 中,
我们可以直接使用 @ResponseBody 注解实现此功

@ResponseBody响应浏览器 json 数据的条件:

① 导入 jackson 的依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>

② SpringMVC 的配置文件中设置开启 mvc 的注解驱动

<!--开启mvc的注解驱动-->
<mvc:annotation-driven />

③ 使用@ResponseBody注解标识控制器方法,在方法中,

将需要转换为 json 字符串并响应到浏览器的 java 对象作为

控制器方法的返回值,此时 SpringMVC 就可以将此对象直

接转换为 json 字符串并响应到浏览器

<input type="button" value="测试@ResponseBody响应浏览器json格式的数据"
@click="testResponseBody()"><br>

<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">
    var vue = new Vue({
        el:"#app",
        methods:{
            testResponseBody(){
                axios.post("/SpringMVC/test/ResponseBody/json").then(response=>{
                    console.log(response.data);
                });
            }
        }
    });
</script>
//响应浏览器list集合
@RequestMapping("/test/ResponseBody/json")
@ResponseBody
public List<User> testResponseBody(){
    User user1 = new User(1001,"admin1","123456",23,"男");
    User user2 = new User(1002,"admin2","123456",23,"男");
    User user3 = new User(1003,"admin3","123456",23,"男");
    List<User> list = Arrays.asList(user1, user2, user3);
    return list;
}

//响应浏览器map集合
@RequestMapping("/test/ResponseBody/json")
@ResponseBody
public Map<String, Object> testResponseBody(){
    User user1 = new User(1001,"admin1","123456",23,"男");
    User user2 = new User(1002,"admin2","123456",23,"男");
    User user3 = new User(1003,"admin3","123456",23,"男");
    Map<String, Object> map = new HashMap<>();
    map.put("1001", user1);
    map.put("1002", user2);
    map.put("1003", user3);
    return map;
}

//响应浏览器实体类对象
@RequestMapping("/test/ResponseBody/json")
@ResponseBody
public User testResponseBody(){
    return user;
}

(5) @RestController注解

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

10. 文件上传和下载

(1) 文件下载

ResponseEntity 用于控制器方法的 返回值类型 ,该控
制器方法的返回值就是响应到浏览器的响应报文 使用
ResponseEntity  实现 下载文件 的功能
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws
IOException {
    //获取ServletContext对象
    ServletContext servletContext = session.getServletContext();
    //获取服务器中文件的真实路径
    String realPath = servletContext.getRealPath("/static/img/1.jpg");
    //创建输入流
    InputStream is = new FileInputStream(realPath);
    //创建字节数组
    byte[] bytes = new byte[is.available()];
    //将流读到字节数组中
    is.read(bytes);
    //创建HttpHeaders对象设置响应头信息
    MultiValueMap<String, String> headers = new HttpHeaders();
    //设置要下载方式以及下载文件的名字
    headers.add("Content-Disposition", "attachment;filename=1.jpg");
    //设置响应状态码
    HttpStatus statusCode = HttpStatus.OK;
    //创建ResponseEntity对象
    ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers,
statusCode);
    //关闭输入流
    is.close();
    return responseEntity;
}

(2) 文件上传

文件上传要求  form  表单的请求方式必须为   post ,并且
添加属性  enctype="multipart/form-data"
SpringMVC 中将上传的文件 封装 到  MultipartFile   对象中,
通过此对象可以获取文件相关信息上传步骤:

① 添加依赖:

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --
>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

② 在SpringMVC的配置文件中添加配置:

<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>

③ 控制器方法:

@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws
IOException {
    //获取上传的文件的文件名
    String fileName = photo.getOriginalFilename();
    //处理文件重名问题
    String hzName = fileName.substring(fileName.lastIndexOf("."));
    fileName = UUID.randomUUID().toString() + hzName;
    //获取服务器中photo目录的路径
    ServletContext servletContext = session.getServletContext();
    String photoPath = servletContext.getRealPath("photo");
    File file = new File(photoPath);
    if(!file.exists()){
    file.mkdir();
    }
    String finalPath = photoPath + File.separator + fileName;
    //实现上传功能
    photo.transferTo(new File(finalPath));
    return "success";
}

11. 拦截器

(1) 拦截器的配置

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

SpringMVC中的拦截器需要实现 HandlerInterceptor

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

进行配置:

<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
<ref bean="firstInterceptor"></ref>
<!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 -->
<mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/testRequestEntity"/>
    <ref bean="firstInterceptor"></ref>
</mvc:interceptor>
<!--
    以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
-->

(2) 拦截器的三个抽象方法

SpringMVC中的拦截器有三个抽象方法:

preHandle:控制器方法执行之前执行 preHandle(),

                    其 boolean 类型的返回值表示是否拦截

                    器或放行,

                    返回 true 放行,即调用控制器方法

                    返回 false 表示拦截,即不调用控制器方法

postHandle:控制器方法执行之后执行 postHandle()

afterCompletion:处理完视图模型数据,渲染视图

                             完毕之后执行 afterCompletion()

(3) 多个拦截器的执行顺序

① 若每个拦截器的  preHandle()  都返回  true
    此时多个拦截器的执行顺序和拦截器在SpringMVC
     的配置文件的 配置顺序有关
    preHandle() 会按照配置的 顺序 执行,而  postHandle()
    和  afterCompletion()  会按照配置的 反序 执行
② 若某个拦截器的  preHandle()  返回了  false
     preHandle()  返回  false  和它之前的拦截器的 
     preHandle() 都会执行 postHandle() 不执行 ,返回
     false 的拦截器之前的拦截器的 afterCompletion()  会
     行

12. 异常处理器

(1) 基于配置的异常处理

SpringMVC提供了一个处理控制器方法执行过程中所出现的

异常的接口:HandlerExceptionResolver

HandlerExceptionResolver 接口的实现类有:

DefaultHandlerExceptionResolver

SimpleMappingExceptionResolver

SpringMVC提供了自定义的异常处理器 

SimpleMappingExceptionResolver,使用方式:

<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <!--
                properties的键表示处理器方法执行过程中出现的异常
                properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
            -->
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
    <!--
        exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
    -->
    <property name="exceptionAttribute" value="ex"></property>
</bean>

(2) 基于注解的异常处理

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

    //@ExceptionHandler用于设置所标识方法处理的异常
    @ExceptionHandler(ArithmeticException.class)
    //ex表示当前请求处理中出现的异常对象
    public String handleArithmeticException(Exception ex, Model model){
        model.addAttribute("ex", ex);
        return "error";
    }
}

13. 注解配置 SpringMVC

使用配置类和注解代替 web.xml 和SpringMVC配置文

件的功能

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

在  Servlet3.0  环境中,容器会在类路径中查找实现
javax.servlet.ServletContainerInitializer 接口的类,
如果找到的话就用它来配置  Servlet  容器。
Spring 提供了这个接口的实现,名为
SpringServletContainerInitializer ,这个类反过来又
会查找实现 WebApplicationInitializer   的类并将配置
的任务交给它们来完成。Spring3.2  引入了一个便利
的  WebApplicationInitializer  基础实现,名为
AbstractAnnotationConfigDispatcherServletInitializer
当我们的类扩展了
AbstractAnnotationConfigDispatcherServletInitializer
并将其部署到  Servlet3.0  容器的时候,容器会自动发
现它,并用它来配置Servlet 上下文。
public class WebInit extends
AbstractAnnotationConfigDispatcherServletInitializer {

    /**
    * 指定spring的配置类
    * @return
    */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    /**
    * 指定SpringMVC的配置类
    * @return
    */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    /**
    * 指定DispatcherServlet的映射规则,即url-pattern
    * @return
    */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
    * 添加过滤器
    * @return
    */
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        encodingFilter.setForceRequestEncoding(true);
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new
                                                       HiddenHttpMethodFilter();
        return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
    }
}

(2) 创建 SpringConfig 配置类,代替 spring 的配置文件

@Configuration
public class SpringConfig {
    //ssm整合之后,spring的配置信息写在此类中
}

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

@Configuration
//扫描组件
@ComponentScan("com.atguigu.mvc.controller")
//开启MVC注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    //使用默认的servlet处理静态资源
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer
configurer) {
        configurer.enable();
    }

    //配置文件上传解析器
    @Bean
    public CommonsMultipartResolver multipartResolver(){
        return new CommonsMultipartResolver();
    }

    //配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        FirstInterceptor firstInterceptor = new FirstInterceptor();
        registry.addInterceptor(firstInterceptor).addPathPatterns("/**");
    }

    //配置视图控制

    /*@Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }*/

    //配置异常映射
    /*@Override
    public void
configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver exceptionResolver = new
SimpleMappingExceptionResolver();
        Properties prop = new Properties();
        prop.setProperty("java.lang.ArithmeticException", "error");
        //设置异常映射
        exceptionResolver.setExceptionMappings(prop);
        //设置共享异常信息的键
        exceptionResolver.setExceptionAttribute("ex");
        resolvers.add(exceptionResolver);
    }*/

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

    //生成模板引擎并为模板引擎注入模板解析器
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver
templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }

    //生成视图解析器并未解析器注入模板引擎
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }
}

(4) 测试功能

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

14. SpringMVC 执行流程

(1) SpringMVC常用组件

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

 (2) DispatcherServlet 初始化过程

DispatcherServlet 本质 上是一个 Servlet ,所以
天然的遵循 Servlet 生命周期 。所以宏观上是
Servlet 生命周期来进行调度。

 ① 初始化 WebApplicationContext

所在类:

org.springframework.web.servlet.FrameworkServlet

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =

WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac =
(ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services
such as
                // setting the parent context, setting the application context
id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit
parent -> set
                    // the root application context (if any; may be null) as the
parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is
assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        // 创建WebApplicationContext
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with
refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        synchronized (this.onRefreshMonitor) {
            // 刷新WebApplicationContext
            onRefresh(wac);
        }
    }


    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        // 将IOC容器在应用域共享
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

② 创建 WebApplicationContext

所在类:

org.springframework.web.servlet.FrameworkServlet

protected WebApplicationContext createWebApplicationContext(@Nullable
ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))
{
        throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" +
getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
    }
    // 通过反射创建 IOC 容器对象
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext)
BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // 设置父容器
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

③ DispatcherServlet 初始化策略

FrameworkServlet  创建  WebApplicationContext  后,
刷新容器,调用  onRefresh(wac) ,此方法在
DispatcherServlet  中进行了 重写 ,调用了
initStrategies(context)   方法,初始化策略,即 初始化
DispatcherServlet  各个组件
所在类: org.springframework.web.servlet.DispatcherServlet
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

(3) DispatcherServlet调用组件处理请求

① processRequest()

FrameworkServlet  重写   HttpServlet  中的 
service()  和 doXxx() ,这些方法中 调用
processRequest(request, response)
所在类: org.springframework.web.servlet.FrameworkServlet
protected final void processRequest(HttpServletRequest request,
HttpServletResponse response)
    throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext =
LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes =
RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request,
response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(),
new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
        // 执行服务,doService()是一个抽象方法,在DispatcherServlet中进行了重写
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

② doService

所在类: org.springframework.web.servlet.DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse
response) throws Exception {
    logRequest(request);

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude ||
attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName,
request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,
getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request,
response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE,
Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    RequestPath requestPath = null;
    if (this.parseRequestPath &&
!ServletRequestPathUtils.hasParsedRequestPath(request)) {
        requestPath = ServletRequestPathUtils.parseAndCache(request);
}

    try {
        // 处理请求和响应
        doDispatch(request, response);
    }
    finally {
        if
(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
        if (requestPath != null) {
            ServletRequestPathUtils.clearParsedRequestPath(request);
        }
    }
}

③ doDispatch()

所在类: org.springframework.web.servlet.DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

        // Determine handler for the current request.
        /*
            mappedHandler:调用链
            包含handler、interceptorList、interceptorIndex
            handler:浏览器发送的请求所匹配的控制器方法
            interceptorList:处理控制器方法的所有拦截器集合
            interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
        */
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }

        // Determine handler adapter for the current request.
        // 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // Process last-modified header, if supported by the handler.
        String method = request.getMethod();
        boolean isGet = "GET".equals(method);
        if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request,
mappedHandler.getHandler());
            if (new ServletWebRequest(request,
response).checkNotModified(lastModified) && isGet) {
                return;
            }
        }

        // 调用拦截器的preHandle()
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        // Actually invoke the handler.
        // 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
        mv = ha.handle(processedRequest, response,
mappedHandler.getHandler());

        if (asyncManager.isConcurrentHandlingStarted()) {
            return;
        }

        applyDefaultViewName(processedRequest, mv);
        // 调用拦截器的postHandle()
        mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
        dispatchException = ex;
    }
    catch (Throwable err) {
        // As of 4.3, we're processing Errors thrown from handler methods as
well,
        // making them available for @ExceptionHandler methods and other
scenarios.
        dispatchException = new NestedServletException("Handler dispatch
failed", err);
}
        // 后续处理:处理模型数据和渲染视图
        processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                                    new NestedServletException("Handler processing
failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {

mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

④ processDispatchResult

private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response,
                                @Nullable HandlerExecutionChain
mappedHandler, @Nullable ModelAndView mv,
                                @Nullable Exception exception) throws
Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered",
exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler()
: null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        // 处理模型数据和渲染视图
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        // 调用拦截器的afterCompletion()
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

(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) 根据该 URI ,调用  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) 将渲染结果 返回给客户端

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值