SpringMVC 介绍

目录

一、SpringMVC 简介

1、SpringMVC 基本概念

2、SpringMVC 的特点

二、实现 Hello World 

三、RequestMapping 注解

1、@RequestMapping 的 作用

2、@RequestMapping 的 位置

3、RequestMapping 中的 value 属性

 4、@RequestMapping 中的 method 属性

5、@RequestMapping 中的 params 属性(了接)

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

四、SpringMVC 获取请求参数

1、通过 ServletAPI 获取

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

3、 @RequestParam 注解

 4、@RequestHeader 注解

5、@CookieValue 注解

 6、通过 pojo 获取请求参数

7、解决请求参数乱码问题

五、域对象共享数据

 1、使用ServletAPI 共享 request域中的数据

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

3、使用Model向request域对象共享数据

4、使用map向request域对象共享数据

5、使用ModelMap向request域对象共享数据

6、Model、ModelMap、Map 之前的关系

7、使用 ServletAPI 向 session 共享数据

 8、使用 ServletAPI 向 application 共享数据

六、SpringMVC 的视图

1、ThymeleafView 

 2、InternalResourceView

3、 RedirectView

4、视图控制器 view-controller

5、配置 jsp 视图解析器

七、RESUTFul 简介 

1、什么是RESTFul 

2、RESTFul的特性

3、RESTful 的实现

 4、HiddenHttpMethodFilter 过滤器

5、使用 RESTFul 风格 实现CURD 操作

(1)准备工作

(2)实现查询用户列表的功能

(3)实现删除功能

(4)实现增加功能

(5)实现修改功能

(6)关于 SpringMVC 处理静态资源的过程

八、HttpMessageConverter

 1、@RequestBody

2、RequestEntity

3、@ResponseBody

4、springMVC 处理 json步骤

 5、SpringMVC 处理 Ajax 请求步骤

6、@RestController 注解

7、ResponseEntity

8、文件下载

9、文件上传

10、解决文件名重复问题

九、拦截器

1、配置拦截器

 2、多个拦截器的执行顺序

十、异常处理器

1、使用配置文件处理异常

2、使用注解处理异常

十一、注解配置SpringMVC配置文件

十二、SpringMVC 的执行流程

1、SpringMVC常用组件

2、SpringMVC 初始化过程

 3、SpringMVC 处理请求的过程

 4、SpringMVC 调用组件执行流程

5、SpringMVC 整体的执行流程


一、SpringMVC 简介

1、SpringMVC 基本概念

MVC 全称:Model 模型、 View 视图、 Controller 控制器。

MVC 最早出现在 JavaEE 三层中的 Web 层,它可以有效的指导 Web 层的代码如何有效分离,单独工作。

View 视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作—— JSP/HTML。

Controller 控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet 转到某个页面。或者是重定向到某个页面。

Model 模型:Model,模型层,指工程中的JavaBean,作用是处理数据

  • 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等

  • 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。

MVC 是一种思想

MVC 的理念是将软件代码拆分成为组件,单独开发,组合使用(目的还是为了降低耦合度)。

2、SpringMVC 的特点

  • Spring 家族原生产品,与 IOC 容器等基础设施无缝对接。

  • 基于原生的Servlet,通过了功能强大的前端控制器 DispatcherServlet,对请求和响应进行统一处理

  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案

  • 代码清新简洁,大幅度提升开发效率

  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可

  • 性能卓著,尤其适合现代大型、超大型互联网项目要求

二、实现 Hello World 

(1)、创建 基于 maven 的 web 工程。

(2)在 pom.xml 文件中 <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>

(3)、在 web.xml 文件中,配置 SpringMVC 前端控制器。

        我们前面说了,SpringMVC 基于原生的 Servlet 的一个 前端控制器:   DispatcherServlet。既然是 Servlet 我们就需要在 文件中配置。

《1》默认配置方式:再次配置下,SpringMVC 配置文件默认在 WEB-INF 目录下。

  <!--配置 SpringMVC 前端控制器。 默认配置方式。-->
  <servlet>
    <servlet-name>SpringMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>SpringMVC</servlet-name>
    <!--
        设置 SpringMVC 前端控制器的的请求路径
        / :表示除了 .jsp 请求路径的请求,都能匹配。比如:/login,/a...   .html .css  .cs
    -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

《2》扩展配置方式:通过 <init-param> 标签设置 SpringMVC 配置文件的位置。

在 resources 目录下创建 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:SpringMVC.xml</param-value>
    </init-param>
    <!--设置启动服务器时就加载配置文件
        默认方式是接受第一次请求的时候才会执行初始化操作。
        由于 SpringMVC 组件 初始化有大量的操作,所以尽量在提前操作,避免影响访问速度。
    -->
    <load-on-startup>1</load-on-startup>

  </servlet>
  <servlet-mapping>
    <servlet-name>SpringMVC</servlet-name>
    <!--
        设置 SpringMVC 前端控制器的的请求路径
        / :表示除了 .jsp 请求路径的请求,都能匹配。比如:/login,/a...   .html .css  .cs
    -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

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="controller"></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>

(4)、创建controller 层 或者 handler 层。并创建普通java类

@Controller //注解创建对象
public class HelloController {

    //  访问:"WEB-INF/templates/index.html"
    @RequestMapping(value = "/") //设置映射关系
    public String index(){

        //返回视图名称。由于我们 配置Thymeleaf视图解析器 的时候,配置了前缀是:templates  后缀是:.html
        // 那么视图名称就是 index 。他会被  Thymeleaf视图解析器 解析 。
        return "index";
    }

    //映射的网页地址。
    @RequestMapping("/target")
    public String hello(){
        //视图名称
        return "target";
    }

}

(5)在 WEB-INF ---- templates 目录下创建俩个网页。

(6)总结:

《1》浏览器发送请求,若请求地址符合 前端控制器的 url-pattern,该请求就会被前端控制器DispatcherServlet处理。

《2》前端控制器会读取 SpringMVC 的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中 @RequestMapping 注解的 value 属性值进行匹配。

《3》若匹配成功,@Controller 所标识的控制器方法就是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面。

三、RequestMapping 注解

1、@RequestMapping 的 作用

@RequestMapping 注解的作用:将 请求 处理请求 的控制器方法关联起来,建立映射关系。

SpringMVC 接受到这个请求,会执行对应的方法进行处理请求。

2、@RequestMapping 的 位置

如果多个 controller 对同一个请求有相同的处理方法,就会报错。

 这段错误表示 /  这个请求已经在 RequestMappingController 控制器中有了映射方法 index()

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

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

 如果我们在类和方法上都加上了 @RequestMapping 注解,那么在映射到 控制器中的方法时,需要将俩个路径拼接在一起。

这种方式还是 很重要的,举个例子:

比如:在一个项目中有 用户模块,还有订单模块。对应俩个 controller控制器。都需要使用 list 方法查询数据库。

 那么前面说了,如果多个 controller 对同一个方法 进行请求处理。就会报错。所以我们需要在前面多加一层请求信息:

用户模块: / user / list

订单模块:/ order / list      这样就将俩个方法区分开了,当然在前端发送请求时,也需要加上这一层请求信息。

3、RequestMapping 中的 value 属性

@RequestMapping 注解的value属性通过请求的请求地址匹配请求映射

@RequestMapping 注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求。

@RequestMapping 注解的value属性必须设置,至少通过请求地址匹配请求映射。其他属性写不写无所谓。

 4、@RequestMapping 中的 method 属性

@RequestMapping 注解的method属性通过请求的请求方式(get或post)匹配请求映射。如果不写 method 属性,可以匹配所有的请求方式。

@RequestMapping 注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求

若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错405:Request method 'POST' not supported

注:

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

注: 

若当前请求满足@RequestMapping注解的value和method属性,但是不满足params属性,此时页面回报错  400:HTTP Status 400 – Bad Request

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

原始方式:/deleteUser?id=1

rest方式:/deleteUser/1       

其实就是将参数转换成请求路径的方式。

SpringMVC路径中的占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符 {xxx} 表示传输的数据,在通过 @PathVariable 注解,将占位符所表示的数据赋值给控制器方法的形参

注: 

如果在 @RequestMapping 中使用了占位符,在发送请求时也必须带参数,不然就会报:

HTTP Status 404 – Not Found  错误 。一个占位符有一个参数,有俩个占位符有俩个参数。。。

四、SpringMVC 获取请求参数

1、通过 ServletAPI 获取

bug提示:

如果一致显示 500 错误,并提示 springMVC.xml 不存在

查看你的 target/classes 目录下是否有  springMVC.xml 这个配置文件,如果没有,就把你 pom.xml 文件中的打包方式改成 war 

<a th:href="@{/testServletAPI(username='jsck',password=1233)}">测试原生ServletAPI获取请求参数</a>
    //通过原生的 HttpServletRequestAPI 获取请求参数
    @RequestMapping("/testServletAPI")
    //request 表示当前请求
    //SpringMVC 已经把 HttpServletRequest 封装好了
    public String servletAPI(HttpServletRequest request) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        System.out.println("username = " + username + ",password = " + password);
        return "success";

    }

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

使用控制器方法来获取请求参数,只需要将 控制器方法的形参名称 与 请求参数的名称 保持一致,DispatcherServlet 会自动赋值

<form th:action="@{/testController}">
    用户名:<input type="text" name="username" ><br>
    密码 : <input type="password" name="password"><br>
    爱好 : <input type="checkbox" name="hobby" value="smoke">smoke
           <input type="checkbox" name="hobby" value="drink">drink
          <input type="checkbox" name="hobby" value="tangtou">tangtou
    <br>
    <input type="submit" value="测试控制器获取请求参数">
</form>
    //通过控制器传参,只需将形参名和请求中的参数名保持一致,就会自动赋值
    @RequestMapping("/testController")
    //如果获取多个 同名的请求参数,有俩种方式:
    // 1、将形参名和请求参数名保持一致,他会自动将请求参数的值用 "," 拼接起来。
    // 2、也是使用一个 String[] 的形式
    // public String test_controller(String username,String password,String hobby){
    public String test_controller(String username,String password,String[] hobby){
        System.out.println("username = " + username +
                ",password = " + password + ",hobby = " + Arrays.toString(hobby));
        return "success";
    }

如果想要获取多个同名的参数:

    // 1、将形参名和请求参数名保持一致,他会自动将请求参数的值用 "," 拼接起来。此时只包含一个数据。就是拼接完的字符串。
    // 2、也可以使用一个 String[] 的形式,将所有的请求参数都保存在数组中。

3、 @RequestParam 注解

如果 请求参数 和 控制器方法形参名不一致时,就可以使用 @RequestParam 注解赋值。

@RequestParam是将请求参数和控制器方法的形参形成映射关系。

@RequestParam 注解有三个属性:

  • value如果控制器方法的形参名请求参数的参数名不一致,用 @RequestParam 中的value属性指定 请求参数的参数名。
  • required : 是否必须传输次请求参数,默认是true。
    • 若设置为true:表示必须传输此注解标识的请求参数,如果不传,且没有设置 default 属性  就会报错:400:Required String parameter 'xxx' is not present
    • 若设置为 false,表示不是必须传输 value 所指定的请求参数,如果不传,默认值为 null 
    • 只有参数名,不传值他是不会报错的。格式:username=&password=   这样不会报错。
  • default Value不管required 是true还是false,只要 value的参数值为空或者不存在时,都会使用默认值为参数赋值。
    • username=&password=    也会设置为 default 的值。
<!--测试@RequestParam-->
<form th:action="@{/testRequestParam}">
    用户名:<input type="text" name="user_name" ><br>
    密码 : <input type="password" name="password"><br>
    爱好 : <input type="checkbox" name="hobby" value="smoke">smoke
    <input type="checkbox" name="hobby" value="drink">drink
    <input type="checkbox" name="hobby" value="tangtou">tangtou
    <br>
    <input type="submit" value="测试@RequestParam">
</form>
    /**
     *  @RequestParam 注解有三个属性
     * value : 设置请求参数的参数名,与形参名形成映射
     * required : 是否必须传输此参,true:必须传入 value 所对应的参数,false:不必须传参。默认是true
     *defaultValue :参数值为空或者不存在,使用默认值为参数赋值
     *
     */
    @RequestMapping("/testRequestParam")
    public String test_RequestParam(
            //当请求参数和形参名不一致时,是获取不到参数值的
            //可以使用 @RequestParam 注解,指定参数名(不常用)
            @RequestParam(value = "user_name",defaultValue = "jack") String username,
            @RequestParam(required = true) String password,
            String[] hobby
    ){
        System.out.println("username = " + username +
                ",password = " + password + ",hobby = " + Arrays.toString(hobby));
        return "success";
    }

 4、@RequestHeader 注解

@RequestHeader 是将 请求头信息 和 控制器方法的形参形成映射关系。

 @RequestHeader  也有三个属性:value 、required、defaultValue 。用法和@RequestParam一样。

注意: @RequestHeader  是没有默认映射关系的。想要获取请求头信息必须使用该注解。@RequestParam 是由默认映射关系的,获取请求参数时,使不使用 @RequestParam 都能获取到参数

5、@CookieValue 注解

@CookieValue 注解是 将 cookie信息 和 控制器方法的新参形成映射关系。 

@CookieValue 注解也有三个属性: value 、required、defaultValue 。用法和@RequestParam一样。

当浏览器中没有JSESSIONID 时,服务器第一个创建 session 对象,并将 JSESSIONID 响应给浏览器。浏览器将 JSESSIONID 为 key 值,session 对象为 value值保存到 Cookie中。

 6、通过 pojo 获取请求参数

创建一个实体类,属性名 要和 请求参数名 保持一致,并且实体类中要有set、get 方法前端控制器 DispatcherServlet 会自动收集参数,并赋值给 实体类。

<!--测试实体类传参。-->
<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>
    //使用一个pojo实体类获取请求参数
    //但是要保证 pojo 实体类的属性名 和 请求参数 的属性名保持一致。
    @RequestMapping("/testpojo")
    public String test_pojo(User user){
        System.out.println(user);
        return "success";
    }

7、解决请求参数乱码问题

在我们使用 实体类获取参数时,会发现有乱码问题:这是因为在发送的请求是 post 请求,需要我们自己设置响应编码。GET 请求乱码问题在Tomcat8之后就自动解决了。

思考一下:当我们在 JavaWeb阶段设置 请求乱码或者响应乱码是不是在BaseServlet中设置。意思是在 发送请求前就设置编码形式。那么就可以使用 过滤器。服务器每次发送请求前都会经过过滤器。 

 在 web.xml 文件中配置过滤器:

  <!--配置过滤器,解决请求参数乱码问题-->
    <filter>
        <filter-name>filter</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>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

五、域对象共享数据

request域:请求域对象。再同一次请求中能共享数据

application域:ServletContext 域对象,在服务器启动时创建,服务器关闭时销毁。

session域:与服务器没有关系,只与浏览器有关系,打开浏览器----关闭浏览器。

pageContext域:只在当前页面能共享数据。

 1、使用ServletAPI 共享 request域中的数据

<a th:href="@{/testRequestByServletAPI}">通过servletAPI获取request域中获取数据</a>

 使用 thymeleaf 获取数据,可能会爆红,但是不影响是使用。

    //使用原生 HttpServletRequest 往 request域中存放数据。
    @RequestMapping("/testRequestByServletAPI")
    public String testRequestByServletAPI(HttpServletRequest request){
        //往请求域中存放数据
        request.setAttribute("testRequestScope","hello,servletAPI");

        return "success"; //这个就相当于转发
    }

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

    //使用 ModelAndView 共享request域数据。ModelAndView必须作为返回值返回
    //告诉控制器该方法中有一个 ModelAndView对象。
    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        ModelAndView mav = new ModelAndView();
        /*
            ModelAndView 有俩个功能:
                Model:负责 request域中共享数据
                View: 主要用于视图设置,实现页面跳转。
         */
        //往request 域中存放数据  和 request.setAttribute()一样,
        mav.addObject("testRequestScope","hello,ModelAndView");
        //设置视图名称,实现页面跳转。
        mav.setViewName("success");
        return mav ;
    }

3、使用Model向request域对象共享数据

    //使用 Model 共享request域数据。
    @RequestMapping("/testModel")
    public String testModel(Model model){
        model.addAttribute("testRequestScope","hello,Model");
        return  "success";
    }

4、使用map向request域对象共享数据


    //使用 map 集合,往map集合中存放值,就相当于往request域中存放值。
    //map的key相当于request中的key,value相当于request中的value
    @RequestMapping("/testMap")
    public String testMap(Map<String,Object> map){
        map.put("testRequestScope","hello,Map");
        return "success";
    }

5、使用ModelMap向request域对象共享数据

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

6、Model、ModelMap、Map 之前的关系

Model 、Map 都是一个接口,ModelMap是一个类,那么他们的关系是什么?

我们可以通过反射机制获取他们的全类名。

 他们三个的全类名其实都是一样,由此可见他们:

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

本质上都是一样的。不管使用哪种方式,最终都会被封装为 ModelAndView 对象中。然后返回 ModelAndView 对象。

7、使用 ServletAPI 向 session 共享数据

<a th:href="@{/testSession}">通过ServletAPI集合获取session域中获取数据</a>

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

 8、使用 ServletAPI 向 application 共享数据

<a th:href="@{/testSession}">通过ServletAPI集合获取session域中获取数据</a>
    //ServletContext 域共享数据
    @RequestMapping("/testApplication")
    public String testApplication(HttpSession session){
        ServletContext context = session.getServletContext();
        context.setAttribute("testApplicationScope","hello,application");
        return "success";
    }

六、SpringMVC 的视图

1、ThymeleafView 

如果使用的是 Thymeleaf 视图解析器:

 当控制器方法设置的视图名称没有 前缀 时,他会被 Thymeleaf 解析器解析。在 ThymeleafView 中我们设置了前缀和后缀,加上视图名称,通过转发跳转到某个页面。

如果使用的不是 Thymeleaf ,而是在 jsp 中,无论带不带前缀他都是 InternalResourceView 

    @RequestMapping("/testThymeleafView")
    public String testThymeleafView(){
        //只要不带任何的前缀,就会被视图解析器 thymeleaf 解析成 thymeleafView

        return "success";
    }

 他的 viewName 就是在控制器方法中的视图名称。

 2、InternalResourceView

SpringMVC 中的转发视图:InternalResourceView

 在控制器方法中视图名称以:"forward:" 为前缀时,会创建一个 InternalResourceView 视图。他不会被 Thymeleaf 解析器解析,而是将 " forward :" 前缀去掉,剩下的部分通过最终路径的方式完成跳转。

<a th:href="@{/testForward}">测试InternalResourceView视图</a><br>
    @RequestMapping("/testForward")
    public String testForward(){
        //转发视图,无法跳到具体的某一个页面。
        //只能转发到控制器方法的某一个 RequestMapping 映射的某一个路径中。
        // return "forward:/success"; 这是错误的
        return "forward:/testThymeleafView";
    }

  转发一次请求,通过链接中的路径,被前端控制器解析,找到控制器对应的方法,根据视图名称加上前缀后缀,最终实现跳转。

注意:

由于我们HTML页面都在WEB-INF的templates目录下,所以使用 forward/Redirect 是无法跳转到具体的某个页面的,只能通过转发到 控制器中某一个方法的RequestMapping 匹配的请求路径

3、 RedirectView

重定向视图:RedirectView

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

<a th:href="@{/testRedirect}">测试RedirectView视图</a><br>
    //RedirectView视图
    @RequestMapping("testRedirect")
    public String testRedirect(){
        //因为在WEB-INF/templates 目录下都是需要被解析器解析的
        // 无法跳转到具体的某个页面。
        return "redirect:/testThymeleafView";
    }

重定向是俩次请求,第一次请求通过链接被前端控制器解析,然后 Redirect 重定向再次向浏览器发送  /testThymeleafView 路径,浏览器第二次发送请求。

4、视图控制器 view-controller

如果控制器方法中没有处理请求的过程,只需要实现页面跳转,可以使用 <mvc:view-controller> 标签设置视图名称,实现跳转。

path:匹配符合跳转的路径

view-name:视图名称

    <!--如果控制器方法中没有任何的请求处理过程,只需要实现跳转,就可以使用 view-controller 标签实现-->
    <mvc:view-controller path="/" view-name="index"></mvc:view-controller>

    <!--由于设置了 view-controller 控制器中的请求映射全部都会失效
        还需要设置一个 MVC 注解扫描器
    -->
    <mvc:annotation-driven />

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

当使用标签设置视图名称之后,控制器方法中的映射关系全部都会失效。是需要再开启MVC注解扫描:    <mvc:annotation-driven />

5、配置 jsp 视图解析器

jsp 视图解析器 使用的就是:InternalResourceViewResolver

 jsp 中创建的视图就有俩种:

一种使用 :forward 前缀创建:InternalResourceView视图

一种使用: redirect 前缀创建:RedirectView 视图。

springMVC.xml 文件:

    <!--在 jsp 中使用的是 InternalResourceViewResolver 视图解析器-->
    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置前缀 /WEB-INF/templates/ -->
        <property name="prefix" value="/WEB-INF/templates/"></property>
        <!--配置后缀是 .jsp-->
        <property name="suffix" value=".jsp"></property>
        
    </bean>
<a href="${pageContext.request.contextPath}/success">访问 success.jsp 页面</a>
@Controller
public class JSPController {
    @RequestMapping("/success")
    public String success(){
        return "success";
    }

注意:

如果在访问页面的时候,发现 解析不出来EL表达式,并且地址栏上出现这样的乱码:

$%7BrequestScope.path%7D

这是因为 jsp子啊 2.4版本之前不支持EL表达式,需要设置 page 标签:

<%--由于 jsp 2.4 版本之前是不解析EL表达式的。--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>

七、RESUTFul 简介 

1、什么是RESTFul 

REST:指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful

2、RESTFul的特性

资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特性的URI。要获取这个资源,访问它的URI就可以,因此URI即为每一个资源的独一无二的识别符。简单来说,万物皆资源

表现层(Representation)把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式。

状态转换(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转换”(State Transfer)。而这种转换是建立在表现层之上的,所以就是“表现层状态转换”。具体说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。

3、RESTful 的实现

 REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。

操作传统方式REST风格
查询操作getUserById?id=1user/1-->get请求方式
保存操作saveUseruser-->post请求方式
删除操作deleteUser?id=1user/1-->delete请求方式
更新操作updateUseruser-->put请求方式

 4、HiddenHttpMethodFilter 过滤器

由于 我们普遍发送请求的方式只支持 POST 或者 GET 方式,那么怎么获取 PUT、DELETE 请求呢?

使用 springMVC 提供的 HiddenHttpMethodFilter 过滤器。使用 Ajax 其实也可以,但是Ajax请求只在部分浏览器中支持:

只要使用过滤器就需要在 web.xml 文件中进行注册:

    <!--设置  HiddenHttpMethodFilter 过滤器-->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

获取 PUT 、DELETE 源码解析:

注:

目前为止,SpringMVC中提供了两个过滤器:CharacterEncodingFilter HiddenHttpMethodFilter

在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter

原因:

  • 在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的

  • request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作

  • 而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:

  • String paramValue = request.getParameter(this.methodParam);

5、使用 RESTFul 风格 实现CURD 操作

(1)准备工作

准备数据库:

CREATE TABLE `t_user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `userName` varchar(255) DEFAULT NULL,
  `passwd` varchar(255) DEFAULT NULL,
  `realName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8

《1》新建基于maven的web模块

《2》在 web.xml 文件中配置 HiddenHttpMethodFilter 、  CharacterEncodingFilter 过滤器、DispatcherServlet 前端控制器。一定要先配置 CharacterEncodingFilter。

《3》配置 springMVC.xml 文件,开启组件扫描 以及 thymeleaf 视图解析器,以及 <mvc-controller> 实现首页跳转

《4》在 WEB-INF 目录下创建 templates 目录 并 创建 首页

《5》使用 Spring5中的 JDBCTemplate 工具类 实现数据库的增删改查。

springMVC.xml 文件配置 数据库连接池以及 JDBCTemplate

  <!--创建数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root" ></property>
        <property name="password" value="root"></property>
    </bean>

    <!--注册 JdbcTemplate 操作数据库-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

UserDao:

public interface UserDao {

    //增
    int add(User user);

    int del(Integer id);

    int update(User user);

    User queryByID(Integer id);

    List<User> queryForList();

}

UserDaoImpl:

@Repository
public class UserDaoImpl implements UserDao {

    @Autowired  //根据数据类型注入对象属性
    private JdbcTemplate jdbcTemplate ;

    @Override
    public int add(User user) {
        String sql = "insert into t_user(userName,passwd,realName) values(?,?,?)";
        return  jdbcTemplate.update(sql,user.getUserName(),user.getpasswd(),user.getRealName());
    }

    @Override
    public int del(Integer id) {
        String sql = "delete from  t_user where id=?";
        return jdbcTemplate.update(sql,id);
    }

    @Override
    public int update(User user) {
        String sql = "update t_user set userName=?,passwd=?,realName=? where id=?";
        return jdbcTemplate.update(sql,user.getUserName(),user.getpasswd(),
                user.getRealName(),user.getId());
    }

    @Override
    public User queryByID(Integer id) {
        String sql = "select * from t_user where id =?";
        return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class),id);
}

    @Override
    public List<User> queryForList() {
        String sql = "select * from t_user";
        return jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class));
    }
}

(2)实现查询用户列表的功能

思路分析:

1、首先在 Controller 层 注入 dao 层对象。

2、每一个功能应该对应一个方法,创建查询所有用户信息的方法

3、方法里调用 dao 层的 queryForList 方法查询数据库。

4、将查询到的结果保存到 request 域中。并返回一个视图名称,实现跳转。

5、在跳转的页面中,使用 each 循环取出 request域中的数据,显示到页面中。

Controller 层: 

    @Autowired //根据数据类型自动输入属性
    private UserDao userDao ;
    
    //使用 RESTFul风格  查询所有的用户信息
    // @RequestMapping(value = "/findAllUser",method = RequestMethod.GET)
    @GetMapping("/user")
    public String findAllUser(Model model){
        List<User> userList = userDao.queryForList();
        //将查询出来的数据保存到 request域中。
        model.addAttribute("user",userList);
        return  "user_list";
    }

 user_list 页面:

<table align="center">
    <tr>
        <th>id</th>
        <th>userName</th>
        <th>password</th>
        <th>realName</th>
        <th>options</th>
    </tr>

    <!--thymeleaf 语法 。和 jsp <c:forEach> 功能一样-->
    <!--userList:相当于<c:forEach>中的var,${userList}相当于<c:forEach>中的item -->
    <tr th:each="user : ${user}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.userName}"></td>
        <td th:text="${user.passwd}"></td>
        <td th:text="${user.realName}"></td>
        <td>
            <a href="">delete</a>
            <a href="">update</a>
        </td>
    </tr>
</table>

(3)实现删除功能

 思路分析:

1、在 user_list 页面中的删除链接中,携带 id 参数发送请求到 Controller 层。

2、将 删除的请求设置为DELETE 请求。

        设置 DELETE 请求的条件:1、发送的请求为POST请求。2、参数名为 _method 参数值为:DELETE 

        由于我们的超链接只能发送 GET 请求,所以我们需要用到表单来发送POST请求,并使用 jQuery 设置表单中的 action 属性 实现跳转。

3、在 Controller 层 调用 userDao.del 方法,实现功能

4、返回视图名称,重定向到 查询列表。 redirect : /user  

        删除完用户信息,已经和上一次请求没有关系了,所以推荐用重定向。

 Controller 层:

    //删除功能
    @DeleteMapping("/user/{id}")
    public String delete(@PathVariable("id") Integer id){
        userDao.del(id);
        return "redirect:/user";
    }

删除链接:

<a class="del" th:href="@{'/user/' + ${user.id}}">delete</a>



<!--table标签外创建表单,用于发送DELETE请求。-->
<form action="" method="post">
    <input type="hidden" name="_method" value="DELETE">
</form>

jQuery 代码:

在webapp下创建 static 目录,引入 js 文件。

    <!--引入js文件,不要忘记加上根路径-->
    <script type="text/javascript" th:src="@{static/jquery-3.6.0.js}"></script>
    <script type="text/javascript">
        $(function () {
            // 给删除绑定单击事件
            $(".del").click(function () {
                if (confirm("确定删除吗?")) {
                    // 1、设置表单的action的跳转路径为 删除连接中的href
                    $("form").attr("action", this.href);
                    $("form").submit(); //2、提交表单
                    return false;//3、阻止超链接的跳转行为。
                }else{
                    return false ;
                }
            });
        });
    </script>

实现删除功能可能出现的问题:

1、在设置超链接中的路径时, @{/delete/ ${user.id} } 这样直接拼接是错误的。@{/delete/} + ${user.id} 可以使用这样的形式进行拼接起来。也可以使用代码块中的格式。

2、在引入jQuery文件时,如果发现 无论怎么修改 js 文件路径,始终提示没有找到报404,说明 js 文件的路径被SpringMVC解析,一旦每SpringMVC解析当然找不到 js 文件,因为js文件是一个静态资源。所以我们需要在 springMVC.xml 配置文件中开启静态资源访问。

当配置这个之后,静态资源还是会交给  SpringMVC 处理,但是当它找不到匹配的请求的路径时,他会交给  default-servlet  处理,最终找到静态资源,当然如果这个资源不存在还是会报 404 。

<mvc:default-servlet-handler />

还有一种可能:如果你的 target 目录并没有把 js 文件打包,需要我们需要重新打包一下。

       

(4)实现增加功能

 思路分析:

1、在 user_list 页面增加跳转到增加页面的超链接。

2、由于不需要处理其他业务请求,所以直接在springMVC.xml 文件中增加<mvc-controller>标签实现跳转

3、在 增加页面 填写数据之后,点击 提交按钮,向 controller 提交请求参数。

4、在 controller 层接受参数,并调用 userdao.add 方法 对数据库增加数据。

5、重定向到 redirect: / user  查询用户列表 。不要使用转发,因为会存在刷新问题。

user_list  页面:

        <!--跳转到增加页面,不需要处理其他业务,所以可以配置<mvc-controller>直接实现跳转-->
        <th>options(<a th:href="@{/add}">add</a>)</th>

add 页面:

<!--增加用户功能,发送 POST 请求-->
<form th:action="@{/user}" method="post">

    userName:<input type="text" name="userName"><br>
    passwd :<input type="text" name="passwd"><br>
    realName:<input type="text" name="realName"><br>
    <input type="submit" value="增加">
</form>

springMVC.xml 文件:

 <!--跳转到增加页面-->
    <mvc:view-controller path="add" view-name="add"></mvc:view-controller>

controller 层: 

    //增加用户
    @PostMapping("/user")
    public String addUser(User user){
        userDao.add(user);
        //重定向到 查询列表功能
        return "redirect:/user";
    }

(5)实现修改功能

思路分析:

 1、在 user_list 页面增加修改的超链接,并传输 id 参数,发送请求到 controller 层。

2、在 controller 层的queryByID方法中接收 参数,并调用dao层 userDao.queryByID 查询用户信息,保存在 request域中。

3、设置视图名称,跳转到修改页面。

4、在修改页面,获取 request域中的数据,回显到文本框中。

5、设置俩个隐藏域,一个隐藏域传输id参数,一个隐藏域传输PUT请求。

6、在controller 层中的update方法,调用 userDao.update 方法实现修改用户信息。

user_list 页面: 

            <!--修改用户信息-->
            <a th:href="@{'/user/' + ${user.id}}" class="update">update</a>

 controller :

    //根据id查询用户信息
    @GetMapping("/user/{id}")
    public String queryByID(@PathVariable Integer id ,Model model){
        //根据id查询信息
        User user = userDao.queryByID(id);
        //将 用户信息存放到 请求域中
        model.addAttribute("user",user);
        return "update";
    }

    //修改用户信息
    @PutMapping("/user")
    public String update(User user){
        userDao.update(user);
        return "redirect:/user";
    }

update 页面:

<h1>修改用户</h1>
<!--修改用户功能,发送 POST 请求-->
<form th:action="@{/user}" method="post">
    <input type="hidden" name="id" th:value="${user.id}">
    <!--修改功能发送PUT请求-->
    <input type="hidden" name="_method" value="PUT">
    userName:<input type="text" name="userName" th:value="${user.userName}"><br>
    passwd :<input type="text" name="passwd" th:value="${user.passwd}"><br>
    realName:<input type="text" name="realName" th:value="${user.realName}"><br>
    <input type="submit" value="update">
</form>

(6)关于 SpringMVC 处理静态资源的过程

上面我们说,如果我们不开放静态资源的访问,SpringMVC 就会处理访问静态资源的请求。

其实我们可以看看Tomcat中的web.xml 文件,其实在Tomcat 中 web.xml 文件配置了default-servlet的匹配路径,那么Tomcat中的web.xml 和我们工程中的 web.xml 文件是什么关系呢?

是继承关系,以工程中的 web.xml 文件优先,由于我们在工程中的 web.xml 文件配置了 前端控制器,所以他会优先交给 SpringMVC 中的前端控制器处理,因此会找不到静态资源。如果我们在 工程中的web.xml 文件中配置了 default-Servlet,前端控制器找不到就会交给 default-servlet 处理。

 这俩个配置要配合使用:

    <!--配置 mvc-controller 会影响其他映射关系的跳转-->
    <!--需要配置mvc:annotation-driven -->
    <mvc:annotation-driven></mvc:annotation-driven>

    <!--开放对静态资源的访问 -->
    <mvc:default-servlet-handler />

八、HttpMessageConverter

HttpMessageConverter:报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文

HttpMessageConverter 提供了两个注解和两个类型:@RequestBody,@ResponseBody,RequestEntity,ResponseEntity 俩个关于请求报文的,俩个关于响应报文的。

 1、@RequestBody

@RequestBody:获取请求体,转换成 JAVA 对象。需要将控制器方法中设置一个形参,将请求体赋值给形参。

只有发送POST 请求时,请求参数才会在请求体中。

<form th:action="@{/testRequestBody}" method="post">
    username:<input type="text" name="username">
    password:<input type="password" name="password" >
    <input type="submit" value="提交">
</form>
    @RequestMapping("/testRequestBody")
    public String testRequestBody(@RequestBody String body){
        System.out.println(body); //username=admin&password=123
        return "success";
    }

2、RequestEntity

RequestEntity 封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息

<form th:action="@{/testRequestEntity}" method="post">
    username:<input type="text" name="username"><br>
    password:<input type="password" name="password" ><br>
    <input type="submit" value="测试RequestEntity">
</form>
    @RequestMapping("/testRequestEntity")
    public String testRequestEntity(RequestEntity<String> requestEntity){
        System.out.println(requestEntity.getHeaders()); //获取请求头
        System.out.println(requestEntity.getBody()); //获取请求体
        return "success";
    }

3、@ResponseBody

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

<a th:href="@{/testResponseBody}">使用@ResponseBody响应浏览器</a>
    @RequestMapping("/testResponseBody")
    @ResponseBody
    public String testResponseBody(){
        //不加 @ResponseBody 是视图名称,实现跳转
        //加上 @ResponseBody 将 success作为响应体响应给浏览器
        return "success";
    }

4、springMVC 处理 json步骤

1、导入 jackson 依赖

       <!--json依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.4</version>
        </dependency>

2、在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串

    <mvc:annotation-driven />

这段配置到目前有三个作用:

1、设置<mvc-controller>时,能够匹配其他映射路径

2、配合开启访问静态资源使用

3、转换 json 字符串时使用。

3、 在处理器方法上使用@ResponseBody注解进行标识

    // 使用 @ResponseBody 返回对象
    @RequestMapping("/testResponseUser")
    @ResponseBody
    public User testResponseUser(){
        return  new User(1,"rose");
    }

4、将Java对象直接作为控制器方法的返回值返回,就会自动转换为 Json 格式的字符串

注意:是转换为 json 格式的字符串,浏览器中只能接受字符串,并不是 json 对象。json 对象只在 JavaScript 中存在。在 java 中的是Java字符串。

{"id":1,"name":"rose"}

 5、SpringMVC 处理 Ajax 请求步骤

超链接请求:

<a th:href="@{/testResponseAjax}" id="ajax">测试 Ajax 请求</a>

Ajax 请求:

<script type="text/javascript" th:src="@{/static/js/jquery-3.6.0.js}"></script>
<script type="text/javascript">
    $(function () {
       $("#ajax").click(function(){
           $.ajax({
               //超链接的 href
               url:this.href,
                //请求方式
               type:"post",
               //请求参数
               data:"username=zhangsan&password=123",
               //data:将controller响应的数据赋值给data
               success:function(data){
                   alert(data);
               }
           });
           return false ;
       });

    });
</script>

controller:

    // 使用 @ResponseBody 返回对象
    @RequestMapping("/testResponseAjax")
    @ResponseBody
    public String testResponseAjax(String username, String password){
        System.out.println("username = " +username + "password = "+ password);
        return  "hello ajax";
    }

6、@RestController 注解

@RestController 是一个复合注解,标识在控制器的类上,相当于为类增加 @Controller 注解,并且为类中的所有方法都加上了 @ResponseBody注解。

7、ResponseEntity

ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。多用于文件下载。

8、文件下载

<a th:href="@{/testDown}">下载图片</a>
    @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;
    }

9、文件上传

(1)创建表单

<!--上传文件必须是post请求
enctype="multipart/form-data   以二进制流的方式传输
-->
<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
    <input type="file" name="photo"><br>
    <input type="submit" value="上传">
</form>

(2)配置文件上传服务器,id不要错,因为 SpringMVC 中是根据id注入对象的。

    <!--配置文件上传解析器,id不能错,必须这个名字。-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    </bean>

(3)controller 层:

  @RequestMapping("/testUp")
    //参数名要与表单中的name一致。
    public String testUp(MultipartFile photo,HttpSession session) throws IOException {
        // 获取 file 类型的属性名。
        // System.out.println(photo.getName());

        //获取上传的文件名。
        String filename = photo.getOriginalFilename();
        // System.out.println(photo.getOriginalFilename());
        ServletContext context = session.getServletContext();
        //1、获取文件的真实路径
        String photoPath = context.getRealPath("photo");
        System.out.println(photoPath);
        //2、通过给定的 路径 创建File实例对象。
        File file = new File(photoPath);
        if (!file.exists()){
            //3、判断以photoPath为路径名的文件或目录是否存在,不存在则创建对应的目录
            file.mkdir();
        }
        // 4、拼接最终的路径,File.separator  自动分隔符。
        String finalPath = photoPath + File.separator + filename ;
        //5、将文件上传到指定位置。只能用一次。用完自动关闭。
        photo.transferTo(new File(finalPath));
        return "success" ;
    }

10、解决文件名重复问题

如果我们上传的文件是重复的,那么他之前的文件是会被删除的,这样是错误的。

那么,怎么才能让文件名不重复呢?

在 java.uitl 有一个 UUID 类,他可以自动随机生成32位不同的数字。我们可以使用 UUID  加文件后缀的方法生成文件名。

        //防止文件名重复问题
        //对"."进行截取。得到文件名的后缀。
        //一定要最后一次出现 .  的位置开始截取,因为文件名有可能:1.1.1.jpg
        String suffix = filename.substring(filename.lastIndexOf("."));
        //使用uuid
        String uuid = UUID.randomUUID().toString();
        //将uuid和后缀拼接在一起。
        filename =  uuid + suffix ;

九、拦截器

拦截器是对控制器中的方法进行拦截

拦截器中的三个方法:

1、配置拦截器

(1)创建一个普通的类,实现 HandlerInterceptor 接口,并重写 preHandle,postHandle,afterCompletion 三个方法。

 在拦截器 中的 preHandle 方法,是对控制器方法进行拦截处理的。我们可以看看DispatcherServlet源码:

 通过源码也可以看出来,当 preHandle  中的返回值为 false 时,才会对请求进行拦截,返回值为true,放行。

(2)在springMVC.xml 文件中配置拦截器。

  <!--配置拦截器-->
    <mvc:interceptors>
        <!--第一种配置方式:对所有请求都会拦截-->
       <!-- <bean class="interceptors.FirstInterceptors"></bean>-->

        <!--第二种配置方式:需要在拦截类加上注解,创建对象。
        和第一种配置方式一样,对所有请求都会拦截-->
        <!--<ref bean="firstInterceptors"></ref>-->

        <!--第三种配置方式:-->
        <mvc:interceptor>
            <!--/* 不是拦截所有请求,/** 是拦截所有请求-->
            <mvc:mapping path="/*"/>
            <!--<mvc:mapping path=""/> 可以配置多条拦截路径-->
            <!--设置不拦截的路径-->
            <mvc:exclude-mapping path="/"/>
            <ref bean="firstInterceptors"></ref>
        </mvc:interceptor>

    </mvc:interceptors>

拦截器工作流程:

 2、多个拦截器的执行顺序

当多个拦截器中的preHandle返回值都是true时:

preHandle 方法 按照  springMVC 配置文件配置拦截器的顺序执行。

postHandle 和 afterCompletion 按照 springMVC 配置文件配置拦截器的反序执行

 为什么会这么执行呢?我们看看源码:

首先我们看 preHandle 方法 执行顺序:

 postHandle 方法的执行顺序:

 afterCompletion 执行顺序:

当有一个拦截器的返回值是false时:

 (1)执行配置它之前拦截器的 preHandle 方法。比如说:我先配置 FirstInterceptor 后配置 SecondInterceptor , 在 SecondInterceptor 的 preHandle 方法中设置返回值false。那么FirstInterceptor 中的preHandle 会执行。

(2)所有的 postHandle 方法都不会执行。

(3)返回值为 false 之前的拦截器的  afterCompletion 方法会执行。也就是 FirstInterceptor  的 afterCompletion 方法会执行。

十、异常处理器

1、使用配置文件处理异常

SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver

HandlerExceptionResolver 接口的实现类有:DefaultHandlerExceptionResolver SimpleMappingExceptionResolver

SpringMVC提供了自定义的异常处理器 SimpleMappingExceptionResolver,使用方式:

   <!--配置异常处理器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--算术异常
                    value就是跳转的页面,符合视图规则。没有前缀被 thymeleaf解析。
                -->
                <prop key="java.lang.ArithmeticException">error</prop>
            </props>
        </property>
        <!--显示异常信息到错误页面
            将错误信息放到请求域中,以value为键,错误信息为值。
        -->
        <property name="exceptionAttribute" value="error" />
    </bean>
错误页面
<p th:text="${error}"></p>

2、使用注解处理异常


@ControllerAdvice // @Controller 增强注解,为Controller增加一些功能。
public class ExceptionController {

    @ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
    public String testException(Exception ex, Model model){
        //将错误信息保存在请求域中。
        model.addAttribute("error",ex);
        return "error";
    }
}

十一、注解配置SpringMVC配置文件

创建一个类继承  AbstractAnnotationConfigDispatcherServletInitializer 类。

/*
    代替 工程下的 web.xml 文件:
        1、配置过滤器
        2、配置前端控制器
        3、指定springMVC.xml 文件
 */
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 characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceResponseEncoding(true);
        //设置转换PUT、DELETE请求过滤器
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();


        return  new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
    }
}

创建SpringConfig 和 WebConfig 类 :

@Configuration
public class SpringConfig {
}

 WebConfig 相当于 springMVC.xml 文件:

/*
    代理 springMVC.xml 文件 :
        1、开启组件扫描
        2、配置视图解析器
        3、开启静态资源的访问 <mvc:default-servlet-handler>
        4、开启mvc注解 <mvc:annotation-driven>
        5、设置 mvc-controller
        6、异常处理器
        7、配置拦截器
        8、配置文件上传解析器
 */
@Configuration
//1、开启组件扫描
@ComponentScan({"controller"})
// 4、开启MVC注解
@EnableWebMvc
public class WebConfig  implements WebMvcConfigurer {

    //3、开启静态资源的访问 <mvc:default-servlet-handler>
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    // 5、设置 mvc-controller
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //addViewController :设置请求路径  setViewName:设置视图名称
        registry.addViewController("/hello").setViewName("hello");
    }

    // 7、配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        FirstInterceptors first = new FirstInterceptors();
        //增加拦截器,addPathPatterns 设置拦截路径。excludePathPatterns:不拦截的路径
        registry.addInterceptor(first).addPathPatterns("/**").excludePathPatterns("/");
    }

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


    //  6、异常处理器
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        //异常处理器
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
        //Properties集合
        Properties prop = new Properties();
        //key:产生的异常类型,value“:跳转的页面。
        prop.setProperty("java.lang.NullPointerException","error");
        //映射关系
        exceptionResolver.setExceptionMappings(prop);
        //将异常信息保存在request域中。键值名:exception
        exceptionResolver.setExceptionAttribute("exception");
        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;
    }

}

十二、SpringMVC 的执行流程

1、SpringMVC常用组件

  • DispatcherServlet:前端控制器,不需要工程师开发,由框架提供

作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求

  • HandlerMapping:处理器映射器,不需要工程师开发,由框架提供

作用:根据请求的url、method等信息查找Handler,即控制器方法.@RequestMapping

  • Handler(Controller):处理器(控制器),需要工程师开发

作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理

  • HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供

作用:通过HandlerAdapter对处理器(控制器方法)进行执行。在我们DispatcherServlet源码中有一个  mv = ha.handle() 方法,ha 就是:HandlerAdapter。执行控制器方法,返回一个ModelAndView对象。

  • dViewResolver:视图解析器,不需要工程师开发,由框架提供

作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView

  • View:视图

作用:将模型数据通过页面展示给用户

2、SpringMVC 初始化过程

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

 我们追踪看一下源码:

 

 3、SpringMVC 处理请求的过程

 

 

 4、SpringMVC 调用组件执行流程

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);
            }
        }
    }
}

render方法 渲染视图:

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);
    }
}

5、SpringMVC 整体的执行流程

(1)浏览器向服务器发送请求。前端控制器接受请求,并解析请求路径中的URI(统一资源定位符)。

(2)判断URI与控制器中的请求映射是否一致。

        若请求映射不存在

                a、判断是否配置了 <mvc:default-servlet>(静态资源访问),若没有配置,直接报404错误。

(3)若配置了  <mvc:default-servlet> ,会交给默认的Servlet 处理,如果还是找不到也是会报 404  错误。

        若请求映射存在

(4)调用 HandleMapping 获取对应的 Handle(Controller) 对象。这个 Handle 中保存了Handle对象(控制器中的方法)、拦截器、拦截器索引

(5)得到Handle对象之后, 获取HandleAdapter 处理器适配器,用于调用控制器中的方法。

(6)获取 HandleAdapter 之后,会执行拦截器中的 preHandle 方法(正序)

(7)通过 处理器适配器调用控制器中的方法。在执行方法的时候,前端控制器会将请求中的信息赋给控制器方法中的形参中,在赋值的过程中 SpringMVC 会做一些以下事情:

        a、HttpMessageConveter :通过请求报文信息转换器,将请求中的信息(如json、xml格式)转换成对应的对象,在响应到浏览器中

        b、数据转换:由于我们浏览器只能发送字符串形式的参数,当你的参数不是String类型时,他会自动给你转换。

        c、数据格式化:将参数转换成日期、数字等形式

 (8)在调用方法完成之后,会返回一个ModelAndView 对象,Model 存放数据,View存放视图名称。

(9)此时开始执行 拦截器中的 PostHandle 方法。

 (10)根据返回的 ModelAndView 对象,如果有异常会通过HandleExceptionResolver处理异常,如果没有异常,选择一个适合视图解析器(Thymeleaf,InternalResourceView,RedirectView),进行视图渲染。

(11)此时执行 拦截器中的 afterCompletion 方法。

(12)将渲染结果返回到浏览器中

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鲨瓜2号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值