当前位置:首页 » 《休闲阅读》 » 正文

SpringMvc完整知识点二(完结)

2 人参与  2024年12月08日 12:01  分类 : 《休闲阅读》  评论

点击全文阅读


SpringMVC获取请求参数

环境准备工作等均省略,可详见快速入门,此处只写非共有部分代码

该部分示例项目SpringMvcThree已上传至Gitee,可自行下载

客户端请求参数的格式为:name=value&password=value... ...

服务端想要获取请求参数有时需要对数据进行封装,SpringMVC可接收如下类型的参数

普通数据类型(基本数据类型以及字符串类型)POJO类型数组类型集合类型

获取普通数据类型

注意

Controller中的业务方法的参数名称要与请求参数的名称一致,如图所示
在这里插入图片描述

在该截图中返回值为void,代表controller控制器中的该业务方法quickMethod9不回写数据,但仍然需要@ResponseBody注解,此时代表ResponseBody的响应体为空,也就是说前端页面为空。

针对获取普通数据类型来说,参数未自动映射匹配有两种方法:

在pom.xml文件中添加maven插件

使用@RequestParam注解

以上两种方式详见代码示例部分

代码示例

此处只写controller控制器类,其余部分搭建可详见快速入门以及数据响应部分内容

在controller包下创建UserController类,代码如下

//将Usercontroller放到Spring容器中@Controller@RequestMapping("/user")public class UserController {    @ResponseBody    @RequestMapping(value = "/quick1")    public void save1(String username, int age) {        System.out.println("name:" + name);        System.out.println("age:" + age);    }}

此时运行截图如下,会报错

在这里插入图片描述

报错原因:错误说明 Spring MVC 无法推断参数名称,通常是因为编译时没有启用参数名的保留功能,或者方法参数缺少显式绑定的注解

解决方法

方案一: 启用参数名的保留功能,即在pom.xml文件中加上maven插件,插件代码如下:

<plugin>    <groupId>org.apache.maven.plugins</groupId>    <artifactId>maven-compiler-plugin</artifactId>    <!-- maven插件版本 -->    <version>3.13.0</version>     <configuration>        <!-- 所使用的Java版本 -->        <source>21</source>         <target>21</target>        <compilerArgs>            <arg>-parameters</arg>        </compilerArgs>    </configuration></plugin>

在这里插入图片描述

方案二: 为方法参数添加 @RequestParam 注解,明确指定参数名称,更改后的UserController类,代码如下

注意:获取普通数据类型的参数以方案二为准,以此来避免影响后续获取其它类型参数的示例操作(即以pom.xml文件中未添加maven插件来演示后续获取各种类型的请求参数的操作,以此来证明其它类型均可自动映射匹配)
//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    @RequestMapping(value = "quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }}

此时运行结果如下

GET请求:运行后前端页面为空白,但是控制台会有输出,如图所示

在这里插入图片描述

:POST请求:运行后前端页面为空白,但是控制台会有输出,如图所示

在这里插入图片描述

获取POJO类型

注意

Controller中业务方法的参数为pojo类对象,且该pojo类中的的属性名要与请求参数的名称一致,如图所示

在这里插入图片描述

获取POJO类型时,不需要去添加maven插件或使用@RequestParam注解来使参数自动映射匹配,在本代码示例中的获取普通数据类型使用的是注解方式来进行参数匹配,以此来区别说明POJO类型不需要去添加maven插件或使用@RequestParam注解。

获取普通POJO类型代码示例

创建pojo包,并在该包下创建一个User类,代码如下

package at.guigu.pojo;public class User {    private String name;    private int age;    public User() {}    public User(String name, int age) {        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}

在controller包下的UserController类中添加save2方法,完整代码如下:

package at.guigu.controller;import at.guigu.pojo.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取普通pojo类型    @ResponseBody    @RequestMapping("/quick2")    public void save2(User user) throws IOException {        System.out.println(user);    }}

在这里插入图片描述

获取嵌套POJO类型代码示例

在pojo包中创建Address类、Brand类,并将其嵌套在Brand类中,此时Brand类及Address类简要代码如下

public class Brand {    private String brandName;    private int brandPrice;    private Address address;    ......}public class Address {    private String province;    private String city;    ......}

在controller包下的UserController类中添加save22方法,完整代码如下:

package at.guigu.controller;import at.guigu.pojo.Brand;import at.guigu.pojo.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取普通pojo类型    @ResponseBody    @RequestMapping("/quick2")    public void save2(User user) throws IOException {        System.out.println(user);    }        // 获取嵌套POJO类型    @ResponseBody    @RequestMapping("/quick22")    public void save22(Brand brand) throws IOException {        System.out.println(brand);    }}

在这里插入图片描述

获取JSON对象的POJO类型

定义:前端传递的是JSON对象

在controller包下的UserController类中添加save222方法,完整代码如下:

传递JSON对象时参数需加上@RequestBody注解

package at.guigu.controller;import at.guigu.pojo.Brand;import at.guigu.pojo.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取普通pojo类型    @ResponseBody    @RequestMapping("/quick2")    public void save2(User user) throws IOException {        System.out.println(user);    }        // 获取嵌套POJO类型    @ResponseBody    @RequestMapping("/quick22")    public void save22(Brand brand) throws IOException {        System.out.println(brand);    }        // 获取JSON对象的POJO类型    @ResponseBody    @RequestMapping("/quick222")    public void save222(@RequestBody User user) throws IOException {        System.out.println(user);    }}

在这里插入图片描述

获取数组类型

注意:

Controller中业务方法的参数的数组名称要与请求参数的名称一致,如图所示

在这里插入图片描述

针对获取数组类型来说,参数未自动映射匹配有两种方法:

在pom.xml文件中添加maven插件

使用@RequestParam注解

以上两种方法的操作与获取获取普通数据类型一致,可详见获取普通数据类型部分的代码示例,此处仅以注解形式示例

代码示例

在controller包下的UserController类中添加save3方法,完整代码如下:

package at.guigu.controller;import at.guigu.pojo.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;import java.util.Arrays;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取普通POJO类型    @ResponseBody    @RequestMapping("/quick2")    public void save2(User user) throws IOException {        System.out.println(user);    }        // 获取嵌套POJO类型    @ResponseBody    @RequestMapping("/quick22")    public void save22(Brand brand) throws IOException {        System.out.println(brand);    }        // 获取JSON对象的POJO类型    @ResponseBody    @RequestMapping("/quick222")    public void save222(@RequestBody User user) throws IOException {        System.out.println(user);    }    // 获取数组类型    @ResponseBody    @RequestMapping("/quick3")    public void save3(@RequestParam("strs")String[] strs) throws IOException {        // 由于直接打印数组时只会打印出其地址,所以将其转为List集合输出到控制台        System.out.println(Arrays.asList(strs));    }}

在这里插入图片描述

获取集合类型—集合保存pojo类的对象

获取普通集合类型

在controller包下的UserController类中添加savepre4方法,代码如下

package at.guigu.controller;import at.guigu.pojo.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;import java.util.Arrays;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取普通POJO类型    @ResponseBody    @RequestMapping("/quick2")    public void save2(User user) throws IOException {        System.out.println(user);    }        // 获取嵌套POJO类型    @ResponseBody    @RequestMapping("/quick22")    public void save22(Brand brand) throws IOException {        System.out.println(brand);    }        // 获取JSON对象的POJO类型    @ResponseBody    @RequestMapping("/quick222")    public void save222(@RequestBody User user) throws IOException {        System.out.println(user);    }    // 获取数组类型    @ResponseBody    @RequestMapping("/quick3")    public void save3(@RequestParam("strs")String[] strs) throws IOException {        // 由于直接打印数组时只会打印出其地址,所以将其转为List集合输出到控制台        System.out.println(Arrays.asList(strs));    }        // 获取普通集合类型    @ResponseBody    @RequestMapping("/quickpre4")    public void savepre4(@RequestParam("strs") List<String> strs) throws IOException {        System.out.println(strs);    }}

在这里插入图片描述

针对获取数组类型来说,参数未自动映射匹配有两种方法:

在pom.xml文件中添加maven插件

使用@RequestParam注解

以上两种方法的操作与获取获取普通数据类型一致,可详见获取普通数据类型部分的代码示例,此处仅以注解形式示例

获取JSON集合类型

在controller包下的UserController类中添加savepre4方法,代码如下

获取数组集合类型的参数需要用@RequestBody注解修饰

package at.guigu.controller;import at.guigu.pojo.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;import java.util.Arrays;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取普通POJO类型    @ResponseBody    @RequestMapping("/quick2")    public void save2(User user) throws IOException {        System.out.println(user);    }        // 获取嵌套POJO类型    @ResponseBody    @RequestMapping("/quick22")    public void save22(Brand brand) throws IOException {        System.out.println(brand);    }        // 获取JSON对象的POJO类型    @ResponseBody    @RequestMapping("/quick222")    public void save222(@RequestBody User user) throws IOException {        System.out.println(user);    }    // 获取数组类型    @ResponseBody    @RequestMapping("/quick3")    public void save3(@RequestParam("strs")String[] strs) throws IOException {        // 由于直接打印数组时只会打印出其地址,所以将其转为List集合输出到控制台        System.out.println(Arrays.asList(strs));    }        // 获取普通集合类型    @ResponseBody    @RequestMapping("/quickpre4")    public void savepre4(@RequestParam("strs") List<String> strs) throws IOException {        System.out.println(strs);    }        // 获取JSON集合类型    @ResponseBody    @RequestMapping("/quickpre44")    public void savepre44(@RequestBody List<String> strs) throws IOException {        System.out.println(strs);    }}

在这里插入图片描述

获取对象集合类型

有三种获取方式 使用POJO类进行集合封装,然后获取集合类型的请求参数不使用POJO类进行集合封装
方式一:使用POJO类进行集合封装
注意 获取集合参数时,要将集合参数封装到一个POJO类中才可以,也就是说,集合要封装到一个对象中(一般定义为VO类),作为这个对象里面的私有属性存在.此时就相当于获取POJO类型,详见代码示例
代码示例

Step1:POJO类中创建User类(代码详见获取POJO类型),创建VO类,代码如下

package at.guigu.pojo;import java.util.List;public class VO {    // 将想要获取的集合封装到对象中    private List<User> userList;    public List<User> getUserList() {        return userList;    }    public void setUserList(List<User> userList) {        this.userList = userList;    }    @Override    public String toString() {        return "VO{" +                "userList=" + userList +                '}';    }}

Step2:在controller包下的UserController类中添加save4方法,完整代码如下:

package at.guigu.controller;import at.guigu.pojo.User;import at.guigu.pojo.VO;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;import java.util.Arrays;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取普通POJO类型    @ResponseBody    @RequestMapping("/quick2")    public void save2(User user) throws IOException {        System.out.println(user);    }    // 获取嵌套POJO类型    @ResponseBody    @RequestMapping("/quick22")    public void save22(Brand brand) throws IOException {        System.out.println(brand);    }    // 获取JSON对象的POJO类型    @ResponseBody    @RequestMapping("/quick222")    public void save222(@RequestBody User user) throws IOException {        System.out.println(user);    }    // 获取数组类型    @ResponseBody    @RequestMapping("/quick3")    public void save3(@RequestParam("strs")String[] strs) throws IOException {        // 由于直接打印数组时只会打印出其地址,所以将其转为List集合输出到控制台        System.out.println(Arrays.asList(strs));    }        // 获取普通集合类型    @ResponseBody    @RequestMapping("/quickpre4")    public void savepre4(@RequestParam("strs") List<String> strs) throws IOException {        System.out.println(strs);    }        // 获取JSON集合类型    @ResponseBody    @RequestMapping("/quickpre44")    public void savepre44(@RequestBody List<String> strs) throws IOException {        System.out.println(strs);    }    // 获取对象集合类型方式一    @ResponseBody    @RequestMapping("/quick4")    public void save4(VO vo) throws IOException {        System.out.println(vo);    }}

此时相等于获取POJO类型:所以请求参数名称要与该POJO类(即VO类)中的属性名一致,而在VO类中该属性userList为一个集合,所以不仅要与userList一致,更要进一步与该集合里面存储的对象User的属性名样一致

Step3:以页面为例(post方式提交)web项目核心目录(即webapp)下创建一个form.jsp页面,代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html>    <head>        <title>Title</title>    </head>    <body>        <%--将表单提交到控制器映射地址下的/user/quick4,也就是UserController类下的save4业务方法中--%>        <form action="/SpringMvcThree/user/quick4" method="post">            <%--将第一个请求参数提交到userList集合中第一个元素的name属性中--%>            <input type="text" name="userList[0].name"><br/>            <%--将第一个请求参数提交到userList集合中第二个元素的age属性中--%>            <input type="text" name="userList[0].age"><br/>            <input type="text" name="userList[0].name"><br/>            <input type="text" name="userList[0].age"><br/>            <input type="submit" value="提交">        </form>    </body></html>

注意:因为请求参数名称要与VO类中的集合属性名一致,又因为集合中存储的是POJO类对象(即User),而User类中有两个属性nameage,所以请求参数名称要与集合名.POJO类属性名一致,所以表单中name属性值为userList[0].name,它的含义就是集合中第一个元素的name属性。

运行后截图如下所示:

在这里插入图片描述

方式二:前端使用JSON数据发送集合
注意 当使用ajax提交表单时,可以指定contentType为json形式,然后在对应业务方法的参数位置使用@RequestBody注解就可以直接接收集合数据而不需要使用POJO进行封装
代码示例

Step1:在web项目核心目录(即webapp)下创建js目录,引入jquery源码文件jquery-3.7.1.js(官网自行下载)

Step2:web项目核心目录(即webapp)下创建一个ajax.jsp文件,并在该文件中引入jquery的源码文件,最终代码如下:

<%--  Created by IntelliJ IDEA.  User: 10195  Date: 2024/11/22  Time: 16:04  To change this template use File | Settings | File Templates.--%><%@ page contentType="text/html;charset=UTF-8" language="java" %><html>    <head>        <title>Title</title>    </head>    <body>        <script src="js/jquery-3.7.1.js"></script>        <script>            // 创建核心对象            var userList = new Array();            userList.push({name:"zhangsna", age:15});            userList.push({name:"lisi", age:16});            $.ajax({               type:"POST",               url:"user/quick5",               data:JSON.stringify(userList),               contentType:"application/json;charset=utf-8"            });        </script>    </body></html>

Step3:在springMVC的核心配置文件中添加如下代码:

原因:在SpringMVC中默认会拦截对所有资源的请求,包括静态资源;若不单独配置则会使静态资源请求被误认为是需要交给核心前端控制器处理的业务请求,此时由于前端控制器无法找到与之对应的业务方法,从而导致资源无法正确加载,所以需要在SpringMVC的核心配置文件中对这些资源进行开放访问。

<!--配置静态资源的路径映射,开放某些资源的访问--><mvc:resources mapping="/js/**" location="/js/"/>

springMVC的核心配置文件完整代码如下:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context.xsd       http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc.xsd">    <!--配置Controller层的注解的组件扫描-->    <context:component-scan base-package="at.guigu.controller"></context:component-scan>    <!--等同于    <context:component-scan base-package="at.guigu">        type指定要扫描的内容为注解,expression指定要扫描的对应注解的全限定名        只扫描at.guigu包下有@Controller注解的类        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    </context:component-scan>    -->    <!--配置内部资源视图解析器-->    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <!--将InternalResourceViewResolver类中的前缀属性prefix的值设为/jsp/-->        <property name="prefix" value="/user/"></property>        <!--将InternalResourceViewResolver类中的前缀属性suffix的值设为.jsp-->        <property name="suffix" value=".jsp"></property>    </bean>    <!--mvc的注解驱动-->    <mvc:annotation-driven/>    <!--等同于配置处理器适配器-->    <!--<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">        <property name="messageConverters">            <list>                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>            </list>        </property>    </bean>-->    <mvc:resources mapping="/js/**" location="/js/"/></beans>

Step4:在controller包下的UserController类中添加save5方法,完整代码如下:

package at.guigu.controller;import at.guigu.pojo.User;import at.guigu.pojo.VO;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;import java.util.Arrays;import java.util.List;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取普通POJO类型    @ResponseBody    @RequestMapping("/quick2")    public void save2(User user) throws IOException {        System.out.println(user);    }    // 获取嵌套POJO类型    @ResponseBody    @RequestMapping("/quick22")    public void save22(Brand brand) throws IOException {        System.out.println(brand);    }        // 获取JSON对象的POJO类型    @ResponseBody    @RequestMapping("/quick222")    public void save222(@RequestBody User user) throws IOException {        System.out.println(user);    }    // 获取数组类型    @ResponseBody    @RequestMapping("/quick3")    public void save3(@RequestParam("strs")String[] strs) throws IOException {        // 由于直接打印数组时只会打印出其地址,所以将其转为List集合输出到控制台        System.out.println(Arrays.asList(strs));    }        // 获取普通集合类型    @ResponseBody    @RequestMapping("/quickpre4")    public void savepre4(@RequestParam("strs") List<String> strs) throws IOException {        System.out.println(strs);    }        // 获取JSON集合类型    @ResponseBody    @RequestMapping("/quickpre44")    public void savepre44(@RequestBody List<String> strs) throws IOException {        System.out.println(strs);    }    // 获取对象集合类型方式一    @ResponseBody    @RequestMapping("/quick4")    public void save4(VO vo) throws IOException {        System.out.println(vo);    }    // 获取对象集合类型方式二    @ResponseBody    @RequestMapping("/quick5")    public void save5(@RequestBody List<User> userList) throws IOException {        System.out.println(userList);    }}

在这里插入图片描述

开放静态资源的请求访问(SpringMVC配置文件形式)

注意

在SpringMVC中默认会拦截对所有资源的请求,包括静态资源;若不单独配置则会使静态资源请求被误认为是需要交给核心前端控制器处理的业务请求,此时由于前端控制器无法找到与之对应的业务方法,从而导致资源无法正确加载,所以需要在SpringMVC的核心配置文件中对这些资源进行开放访问。开放静态资源的请求访问时,其路径映射不能与内部资源视图解析器一样,否则会报错

通过<mvc:resources>标签进行资源路径配置标签常用属性如下:

<mvc:resources>标签属性解释
mapping告诉 Spring MVC,当用户请求某些特定 URL 时,这些请求是访问静态资源,而不是交给控制器处理。/js/**代表js后可以是多级url地址
location定义资源在服务器上的实际存放位置

由于每开放一种静态资源就要写一个该标签代码,所以可用如下代码代替

<mvc:default-servlet-handler/>

解释: 在SpringMVC中默认会拦截对所有资源的请求,包括静态资源;若不单独配置则会使静态资源请求被误认为是需要交给核心前端控制器处理的业务请求,此时由于前端控制器无法找到与之对应的业务方法,从而导致资源无法正确加载,此时就会交由Tomcat来找对应的静态资源,从而使得资源正确加载

简要总结: SpringMVC框架无法找到对应资源时就会让原始容器Tomcat去找静态资源

开放对图片、jquery文件等静态资源访问的代码示例如下

<!--配置静态资源的路径映射,开放某些资源的访问--><mvc:resources mapping="/js/**" location="/js/"/><mvc:resources mapping="/img/**" location="/img/"/>

等同于

<mvc:default-servlet-handler/>

开放静态资源的请求访问方式一(SpringMVC类形式)

Step1:在config包下创建一个继承WebMvcConfigurationSupport的子类SpringMvcSupport,并为其加上@Configuration注解,代码如下

package at.guigu.config;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;// 静态资源的路径映射类@Configurationpublic class SpringMvcSupport extends WebMvcConfigurationSupport {    @Override    protected void addResourceHandlers (ResourceHandlerRegistry registry) {        // 当访问/pages/????时候,从/pages目录下查找内容        // 配置静态资源的路径映射,开放某些资源的访问        // 等同于<mvc:resources mapping="/js/**" location="/js/"/>        registry.addResourceHandler("/xxx/**").addResourceLocations("/xxx/");        // 等同于<mvc:resources mapping="/img/**" location="/img/"/>        registry.addResourceHandler("/img/**").addResourceLocations("/img/");    }}

Step2: 在SpringMVC核心配置类中用@ComponentScan注解扫描静态资源的路径映射类所在包,代码如下

package at.guigu.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller"></context:component-scan>// 加载controller对应的bean@ComponentScan({"at.guigu.controller", "at.guigu.config"})// 自动配置 Spring MVC 的各种特性,比如:类型转换器、mvc的注解驱动<mvc:annotation-driven/>、静态资源路径映射@EnableWebMvcpublic class SpringMvcConfiguration {    // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/user/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }}

开放静态资源的请求访问方式二(SpringMVC类形式)

让SpringMVC的核心配置类SpringMvcConfiguration实现WebMvcConfigurer接口,然后重写其中的addResourceHandlers方法即可,代码如下

package at.guigu.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller"></context:component-scan>// 加载controller对应的bean@ComponentScan("at.guigu.controller")// 自动配置 Spring MVC 的各种特性@EnableWebMvcpublic class SpringMvcConfiguration implements WebMvcConfigurer {    // 依赖注入拦截器的bean    @Autowired    private MyInterceptor myInterceptor;    // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/jsp/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }    // 配置静态资源路径    @Override    public void addResourceHandlers(ResourceHandlerRegistry registry) {        // 配置静态资源路径映射        registry.addResourceHandler("/xxx/**").addResourceLocations("/xxx/");    }}

GET请求参数乱码问题

Tomcat8.5版本之前会出现GET请求参数乱码问题,在其之后官方就已经给解决了

若使用的是Tomcat7插件则会出现Get请求参数乱码问题,此时需要在pom.xml文件对应的Tomcat插件中配置UTF-8字符集,插件代码如下

<plugins>    <!-- Tomcat插件 -->    <plugin>        <groupId>org.apache.tomcat.maven</groupId>        <artifactId>tomcat7-maven-plugin</artifactId>        <version>2.2</version>        <configuration>            <port>80</port><!--Tomcat端口号-->            <path>/</path><!--虚拟目录-->            <uriEncoding>UTF-8</uriEncoding><!--指定字符集-->        </configuration>    </plugin></plugins>

POST请求参数乱码问题

SpringMVC配置文件形式

当使用POST请求时,数据会出现乱码问题(可详见使用POJO类进行集合封装的代码示例),解决方法:

在web项目核心目录(即webapp)下的WEB-INF中的web.xml中配置一个全局过滤器来进行编码的过滤,代码如下

注意:<filter>以及<filter-mapping>标签需要写到<linstener>标签前,因为web.xml中有严格的标签顺序

<!--全局过滤器--><filter>    <filter-name>CharacterEncodingFilter</filter-name>    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>    <init-param>        <param-name>encoding</param-name>        <param-value>UTF-8</param-value>    </init-param></filter><filter-mapping>    <filter-name>CharacterEncodingFilter</filter-name>    <url-pattern>/*</url-pattern></filter-mapping>

此时再次运行使用POJO类进行集合封装的代码示例后就不会出现乱码问题了,运行截图如下

在这里插入图片描述

SpringMVC配置类形式

当使用POST请求时,数据会出现乱码问题(可详见使用POJO类进行集合封装的代码示例),解决方法:

在web.xml文件对应的配置类中指定字符过滤器,代码如下

package at.guigu.config;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;import org.springframework.web.filter.CharacterEncodingFilter;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;import javax.servlet.Filter;// 定义一个Servlet容器启动的配置类,来加载Spring的配置public class ServletContainerInitConfiguration extends AbstractAnnotationConfigDispatcherServletInitializer {    // 加载SpringMVC的配置    @Override    protected Class<?>[] getServletConfigClasses() {        return new Class[]{SpringMvcConfiguration.class};    }    // 设置哪些请求归SpringMVC处理    @Override    protected String[] getServletMappings() {        return new String[]{"/"};    }    // 加载非SringMVC的配置,比如:加载Spring的配置    @Override    protected Class<?>[] getRootConfigClasses() {        return new Class[]{SpringConfiguration.class};    }        // POST请求乱码处理:指定字符过滤器    @Override    protected Filter[] getServletFilters() {        CharacterEncodingFilter filter = new CharacterEncodingFilter();        filter.setEncoding("UTF-8");        return new Filter[]{filter};    }}/* 等同于// 定义一个Servlet容器启动的配置类,来加载Spring的配置public class ServletContainerInitConfiguration extends AbstractDispatcherServletInitializer {    // 加载SpringMVC的配置    @Override    protected WebApplicationContext createServletApplicationContext() {        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();        context.register(SpringMvcConfiguration.class);        return context;    }    // 设置哪些请求归SpringMVC处理    @Override    protected String[] getServletMappings() {        // 代表将所有请求都交给前端控制器处理        return new String[]{"/"};    }    // 加载非SringMVC的配置,比如:加载Spring的配置    @Override    protected WebApplicationContext createRootApplicationContext() {        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();        context.register(SpringConfiguration.class);        return context;    }    // POST请求乱码处理:指定字符过滤器    @Override    protected Filter[] getServletFilters() {        CharacterEncodingFilter filter = new CharacterEncodingFilter();        filter.setEncoding("UTF-8");        return new Filter[]{filter};    }}*/

@requestParam注解

@requestParam注解属性解释
value请求参数的名称
required该注解指定的请求参数是否必须存在,默认为true,提交时若该参数不存在则会报错
defaultValue当该注解没有指定请求参数时,则使用指定默认值

定义:当请求参数的名称与Controller控制器中对应的业务方法的参数名称不一致时,将它们显式的绑定到一块

在这里插入图片描述

代码示例如下:

在controller包下的UserController类中添加save6方法,完整代码如下:

package at.guigu.controller;import at.guigu.pojo.User;import at.guigu.pojo.VO;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;import java.util.Arrays;import java.util.List;//将Usercontroller放到Spring容器中@Controller@RequestMapping(value = "/user")public class UserController {    // 获取普通数据类型    @RequestMapping(value = "/quick1")    @ResponseBody    public void save1(@RequestParam("username")String username, @RequestParam("age")int age) throws IOException {        System.out.println(username);        System.out.println(age);    }    // 获取嵌套POJO类型    @ResponseBody    @RequestMapping("/quick22")    public void save22(Brand brand) throws IOException {        System.out.println(brand);    }        // 获取JSON对象的POJO类型    @ResponseBody    @RequestMapping("/quick222")    public void save222(@RequestBody User user) throws IOException {        System.out.println(user);    }    // 获取数组类型    @ResponseBody    @RequestMapping("/quick3")    public void save3(@RequestParam("strs")String[] strs) throws IOException {        // 由于直接打印数组时只会打印出其地址,所以将其转为List集合输出到控制台        System.out.println(Arrays.asList(strs));    }        // 获取普通集合类型    @ResponseBody    @RequestMapping("/quickpre4")    public void savepre4(@RequestParam("strs") List<String> strs) throws IOException {        System.out.println(strs);    }        // 获取JSON集合类型    @ResponseBody    @RequestMapping("/quickpre44")    public void savepre44(@RequestBody List<String> strs) throws IOException {        System.out.println(strs);    }    // 获取对象集合类型方式一    @ResponseBody    @RequestMapping("/quick4")    public void save4(VO vo) throws IOException {        System.out.println(vo);    }    // 获取对象集合类型方式二    @ResponseBody    @RequestMapping("/quick5")    public void save5(@RequestBody List<User> userList) throws IOException {        System.out.println(userList);    }    // 测试@RequestParam注解    @RequestMapping(value = "/quick1")    @ResponseBody    public void save6(@RequestParam(value = "name", required = false, defaultValue = "zhangzhang")String username) throws IOException {        System.out.println(username);    }}

请求参数名与业务方法的参数名不一致

在这里插入图片描述

不写请求参数,则会将指定默认值赋值给业务方法对应的参数

在这里插入图片描述

自定义类型转换器

SpringMVC默认已经提供了一些常用的类型转换器,比如将客户端提交的字符串转换成int类型进行参数设置。但不是所有是数据类型都提供了转换器,比如日期类型的数据,此时就需要自定义转换器

注意

参数若不会自动映射匹配,所以可使用如下两种方法 使用@RequestParam注解在pom.xml文件中添加maven插件以上两种方式代码示例详见获取普通数据类型

日期类型参数传递示例一(SpringMVC配置文件形式)

本代码示例以日期类型为例:将日期封装为yyyy-MM-dd形式

注意:日期默认封装格式为yyyy/MM/dd形式,若请求参数中为yyyy-MM-dd形式则会报错,所以本示例是将其转换为常用的yyyy-MM-dd形式

开发步骤

自定义一个实现Converter接口的转换器类

在SpringMVC的核心配置文件中声明转换器类

在核心配置文件中用<annotation-driven>(即mvc的注解驱动)标签中的conversion-service属性引用转换器类

Step1: 创建一个与三层架构包同级的converter包,然后定义一个实现Converter接口的类DataConverter,并定义该类的泛型为<String, Date>,然后重写其中的convert方法,在该方法中将日期转换成指定格式的日期对象并返回

注意: 该接口是org.springframework.core.convert.converter.Converter包下的<String, Date>中第一个参数String为请求参数字符串;第二个参数Date为要转换到的类型convert方法的参数为客户端的请求参数
package at.guigu.converter;import org.springframework.core.convert.converter.Converter;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class DataConverter implements Converter<String, Date> {    @Override    public Date convert(String source) {        // 将日期字符串转换成指定的日期对象并返回        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");        Date date = null;        try {            date = format.parse(source);        } catch (ParseException e) {            e.printStackTrace();        }        return date;    }}

Step2: 在SpringMVC的核心配置文件中声明转换器类,核心配置文件代码如下

<!--声明转换器类--><bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">    <property name="converters">        <list>            <bean class="at.guigu.converter.DataConverter"></bean>        </list>    </property></bean>

Step3: 在SpringMVC的核心配置文件中用<annotation-driven>标签中的conversion-service属性引用转换器类的id,代码如下

<mvc:annotation-driven conversion-service="conversionService"/>

SpringMVC的核心配置文件代码如下

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context.xsd       http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc.xsd">    <!--配置Controller层的注解的组件扫描-->    <context:component-scan base-package="at.guigu.controller"></context:component-scan>    <!--等同于    <context:component-scan base-package="at.guigu">        type指定要扫描的内容为注解,expression指定要扫描的对应注解的全限定名        只扫描at.guigu包下有@Controller注解的类        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    </context:component-scan>    -->    <!--配置内部资源视图解析器-->    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <!--将InternalResourceViewResolver类中的前缀属性prefix的值设为/jsp/-->        <property name="prefix" value="/user/"></property>        <!--将InternalResourceViewResolver类中的前缀属性suffix的值设为.jsp-->        <property name="suffix" value=".jsp"></property>    </bean>    <!--mvc的注解驱动-->    <!--利用conversion-service来引用转换器类,属性值为转换器对应的id-->    <mvc:annotation-driven conversion-service="conversionService"/>    <!--等同于配置处理器适配器-->    <!--<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">        <property name="messageConverters">            <list>                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>            </list>        </property>    </bean>-->    <!--配置静态资源的路径映射,让 Spring MVC 可以处理静态文件-->    <mvc:default-servlet-handler/>        <!--声明转换器类-->    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">        <property name="converters">            <list>                <bean class="at.guigu.converter.DataConverter"></bean>            </list>        </property>    </bean></beans>

Step4: 在controller包下创建UserControllerThree类并添加save1方法,完整代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;import java.util.Date;@Controller@RequestMapping("/user3")public class UserControllerThree {    @ResponseBody    @RequestMapping("/quickk1")    public void save1(@RequestParam("date") Date date) throws IOException {        System.out.println(date);    }}

运行截图如下

在这里插入图片描述

日期类型参数传递示例二(SpringMVC配置类形式)

日期类型数据基于系统不同格式也不尽相同,所以我们在接收形参时,可以根据不同的日期格式设置不同的接收方式
2088-08-18
2088/08/18
08/18/2088

注意:日期类型参数传递除了使用自定义类型转换器外(详见自定义类型转换器中的内容),还可以使用注解形式,步骤如下

在控制器给对应方法的形参加上@DateTimeFormat(pattern)注解

它内部依赖的是Converter接口

在SpringMVC的核心配置类上加上@EnableWebMvc注解,它可以根据类型匹配对应的类型转换器,代替了SpringMVC配置文件形式中的一下两步:

在SpringMVC的核心配置文件中声明转换器类

在核心配置文件中用<annotation-driven>(即mvc的注解驱动)标签中的conversion-service属性引用转换器类

Step1:MvcReqClaDemo项目示例中的controller包下创建UserControllerThree类并添加save1方法,完整代码如下

package at.guigu.controller;import org.springframework.format.annotation.DateTimeFormat;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;import java.util.Date;// 自定义类型转换器@Controller@RequestMapping("/user3")public class UserControllerThree {    @ResponseBody    @RequestMapping("/quickk1")    public void save1(@RequestParam("date") Date date,                      @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam("date1") Date date1,                      @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam("date2") Date date2) throws IOException {        System.out.println(date);        System.out.println(date1);        System.out.println(date2);    }}

Step2: SpringMVC核心类添加上@EnableWebMvc注解,代码如下

package at.guigu.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller"></context:component-scan>// 加载controller对应的bean@ComponentScan("at.guigu.controller")// 自动配置 Spring MVC 的各种特性,比如:类型转换器、mvc的注解驱动<mvc:annotation-driven/>@EnableWebMvcpublic class SpringMvcConfiguration {    // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/user/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }}

在这里插入图片描述

获取Servlet的相关API

SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用对象有

HttpServletRequestHttpServletResponseHttpSession

获取方式: 只需要将想要的Servlet的API作为控制器对应方法的参数即可

方法一般是谁调用谁传参,因为业务方法是SpringMVC框架调用的,所以SpringMVC会自动根据方法的参数进行注入

测试示例

在controller包下创建UserControllerFour类并添加save1方法,完整代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;@Controller@RequestMapping("/user4")public class UserControllerFour {    @ResponseBody    @RequestMapping("/quick1")    public void save1(HttpServletRequest req, HttpServletResponse res, HttpSession hs) throws Exception{        System.out.println(req);        System.out.println(res);        System.out.println(hs);    }}

在这里插入图片描述

获取请求头信息

利用@RequestHeader注解来获取请求头信息,相当于web阶段所学的request.getHeader(name)方法,可详见会话跟踪技术部分内容

@RequestHeader注解属性解释
value请求头名称
required是否必须携带该请求头,默认为true,即必须携带该请求头才能访问这个资源

测试示例

在controller包下创建UserControllerFive类并添加save1方法,代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller@RequestMapping("/user5")public class UserControllerFive {    @ResponseBody    @RequestMapping("/quick1")    public void save1() throws Exception{    }}

运行后通过开发者工具可看到请求头信息,如图所示

在这里插入图片描述

假设现在获取请求头user-agent的信息,则代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestHeader;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller@RequestMapping("/user5")public class UserControllerFive {    @ResponseBody    @RequestMapping("/quick1")    public void save1(@RequestHeader(value = "User-Agent", required = false) String user_agent) throws Exception{        System.out.println(user_agent);    }}

在这里插入图片描述

获取指定Cookie请求头信息

@RequestHeader只能根据如图所示1中的请求头来获取2中的信息,而有些请求头后的信息有键值对,比如Cookie中又有很多键值对Cookie,此时若想获取Cookie里面的小Cookie的话@RequestHeader注解就会失效。

在这里插入图片描述

Cookie不只有一个,所以属于特殊请求头,如图所示
在这里插入图片描述

@CookieValue获取指定Cookie的值

@CookieValue注解属性解释
valueCookie名称
required是否必须携带该Cookie,默认为true,即必须携带该Cookie才能访问这个资源

代码示例(此处以JSESSIONID这个Cookie为例)

UserControllerFive类中添加save2方法,代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.CookieValue;import org.springframework.web.bind.annotation.RequestHeader;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller@RequestMapping("/user5")public class UserControllerFive {    // 获取普通请求头    @ResponseBody    @RequestMapping("/quick1")    public void save1(@RequestHeader(value = "User-Agent", required = false) String user_agent) throws Exception{        System.out.println(user_agent);    }    // 获取特殊请求头:获取指定Cookie    @ResponseBody    @RequestMapping("/quick2")    public void save2(@CookieValue(value = "JSESSIONID", required = false) String jessionid) throws Exception{        System.out.println(jessionid);    }}

在这里插入图片描述

注意:除以上注解之外,也可使用通用方式(即使用HttpServletRequest接口中的方法)来获取请求头信息,可详见会话跟踪技术部分内容

文件上传(获取文件)

文件上传客户端三要素

表单项type="file"表单提交方式为post表单的enctype属性是多部分表单形式,即enctype="mulipart/form-data"

在这里插入图片描述

注意

当form表单修改为多部分表单时,request.getParameter(String name)会失效,因为该方法只能获取单个参数值

该方法是根据键名来获取参数值,具体解释可详见可见Web&Http&Servlet&Request&Response部分内容

默认情况下,enctype="application/x-www-form-urlencoded",此时form表单的正文内容是key=value&key=value&key=value

enctype="mulipart/form-data"时,请求正文内容就会变成多部分形式,此时能够获取表单的所有数据,如图所示

在这里插入图片描述

文件上传步骤

在pom.xml文件中导入坐标:fileupload和io两个坐标

<!--fileupload坐标--><dependency>    <groupId>commons-fileupload</groupId>    <artifactId>commons-fileupload</artifactId>    <version>1.5</version></dependency><!--io坐标--><dependency>    <groupId>commons-io</groupId>    <artifactId>commons-io</artifactId>    <version>2.17.0</version></dependency>

在SpringMVC的核心配置文件中配置文件上传解析器

编写文件上传代码

单文件上传和多文件上传的公共步骤(后续代码演示不在演示公共步骤)

在pom.xml文件中导入fileupload和io两个坐标,文件完整代码如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.example</groupId>        <artifactId>SpringMvcDemo</artifactId>        <version>1.0-SNAPSHOT</version>    </parent>    <artifactId>SpringMvcThree</artifactId>    <packaging>war</packaging>    <name>SpringMvcThree Maven Webapp</name>    <url>http://maven.apache.org</url>    <dependencies>        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>3.8.1</version>            <scope>test</scope>        </dependency>        <!--===================Spring相关坐标=======================-->        <!--spring坐标-->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-context</artifactId>            <version>6.1.6</version>        </dependency>        <!--spring-web -->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-web</artifactId>            <version>5.2.25.RELEASE</version>        </dependency>        <!--spring-test坐标-->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-test</artifactId>            <version>6.1.6</version>            <scope>test</scope>        </dependency>        <!--Annotation坐标-->        <dependency>            <groupId>javax.annotation</groupId>            <artifactId>javax.annotation-api</artifactId>            <version>1.3.2</version>        </dependency>        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>4.13.2</version>            <scope>test</scope>        </dependency>        <!-- servlet-->        <dependency>            <groupId>javax.servlet</groupId>            <artifactId>javax.servlet-api</artifactId>            <version>4.0.1</version>            <scope>provided</scope>        </dependency>        <!--jsp-->        <dependency>            <groupId>javax.servlet.jsp</groupId>            <artifactId>javax.servlet.jsp-api</artifactId>            <version>2.3.3</version>            <scope>provided</scope>        </dependency>        <!--===================SpringMVC相关坐标=======================-->        <!--spring-webmvc-->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-webmvc</artifactId>            <version>5.2.25.RELEASE</version>        </dependency>        <!--jackson-core-->        <dependency>            <groupId>com.fasterxml.jackson.core</groupId>            <artifactId>jackson-core</artifactId>            <version>2.17.1</version>        </dependency>        <!--jackson-databind-->        <dependency>            <groupId>com.fasterxml.jackson.core</groupId>            <artifactId>jackson-databind</artifactId>            <version>2.17.1</version>        </dependency>        <!--jackson-annotations-->        <dependency>            <groupId>com.fasterxml.jackson.core</groupId>            <artifactId>jackson-annotations</artifactId>            <version>2.17.1</version>        </dependency>        <!--=====================数据库相关坐标=========================-->        <!--mysql坐标-->        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.33</version>        </dependency>        <!--druid坐标-->        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>druid</artifactId>            <version>1.2.18</version>        </dependency>        <!--c3p0坐标-->        <dependency>            <groupId>com.mchange</groupId>            <artifactId>c3p0</artifactId>            <version>0.9.5.5</version>        </dependency>        <!--=====================MyBatis相关坐标=========================-->        <!--spring-jdbc-->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-jdbc</artifactId>            <version>6.1.10</version>        </dependency>        <!--mybatis-spring-->        <dependency>            <groupId>org.mybatis</groupId>            <artifactId>mybatis-spring</artifactId>            <version>3.0.3</version>        </dependency>        <!--MyBatis坐标-->        <dependency>            <groupId>org.mybatis</groupId>            <artifactId>mybatis</artifactId>            <version>3.5.16</version>        </dependency>        <!--=====================文件上传相关坐标=========================-->        <!--fileupload坐标-->        <dependency>            <groupId>commons-fileupload</groupId>            <artifactId>commons-fileupload</artifactId>            <version>1.5</version>        </dependency>        <!--io坐标-->        <dependency>            <groupId>commons-io</groupId>            <artifactId>commons-io</artifactId>            <version>2.17.0</version>        </dependency>    </dependencies>    <build>        <finalName>SpringMvcThree</finalName>        <plugins>            <!-- Tomcat插件 -->            <plugin>                <groupId>org.apache.tomcat.maven</groupId>                <artifactId>tomcat7-maven-plugin</artifactId>                <version>2.2</version>            </plugin>            <!--<plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-compiler-plugin</artifactId>                &lt;!&ndash; maven插件版本 &ndash;&gt;                <version>3.13.0</version>                <configuration>                    &lt;!&ndash; Java版本 &ndash;&gt;                    <source>21</source>                    <compilerArgs>                        <arg>-parameters</arg>                    </compilerArgs>                </configuration>            </plugin>-->        </plugins>    </build></project>

在SpringMVC的核心配置文件中配置文件上传解析器,代码如下

SpringMVC核心配置文件代码如下

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context.xsd       http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc.xsd">    <!--配置Controller层的注解的组件扫描-->    <context:component-scan base-package="at.guigu.controller"></context:component-scan>    <!--等同于    <context:component-scan base-package="at.guigu">        type指定要扫描的内容为注解,expression指定要扫描的对应注解的全限定名        只扫描at.guigu包下有@Controller注解的类        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    </context:component-scan>    -->    <!--配置内部资源视图解析器-->    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <!--将InternalResourceViewResolver类中的前缀属性prefix的值设为/jsp/-->        <property name="prefix" value="/user/"></property>        <!--将InternalResourceViewResolver类中的前缀属性suffix的值设为.jsp-->        <property name="suffix" value=".jsp"></property>    </bean>    <!--mvc的注解驱动-->    <mvc:annotation-driven conversion-service="conversionService"/>    <!--等同于配置处理器适配器-->    <!--<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">        <property name="messageConverters">            <list>                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>            </list>        </property>    </bean>-->    <!--配置静态资源的路径映射,让 Spring MVC 可以处理静态文件-->    <mvc:default-servlet-handler/>        <!--声明转换器类-->    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">        <property name="converters">            <list>                <bean class="at.guigu.converter.DataConverter"></bean>            </list>        </property>    </bean>    <!--配置文件上传解析器-->    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">        <!--所上传文件的编码类型-->        <property name="defaultEncoding" value="UTF-8"/>        <!--所上传的单个文件的大小-->        <property name="maxUploadSizePerFile" value="500000"/>        <!--所上传的总文件的大小-->        <property name="maxUploadSize" value="5000000"/>    </bean></beans>

SpringMVC核心配置文件对应的核心配置类代码如下

package at.guigu.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller"></context:component-scan>// 加载controller对应的bean@ComponentScan("at.guigu.controller")// 自动配置 Spring MVC 的各种特性,比如:类型转换器、mvc的注解驱动<mvc:annotation-driven/>、静态资源路径映射@EnableWebMvc// 引入配置静态资源的路径映射类@Import(SpringMvcSupport.class)public class SpringMvcConfiguration {    // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/user/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }    // 配置文件上传解析器    @Bean    public CommonsMultipartResolver multipartResolver() {        CommonsMultipartResolver resolver = new CommonsMultipartResolver();        resolver.setDefaultEncoding("UTF-8"); // 所上传文件的编码类型        resolver.setMaxUploadSizePerFile(500000);// 所上传的单个文件的大小        resolver.setMaxUploadSize(5000000);// 所上传的总文件的大小        return resolver;    }}

在这里插入图片描述

单文件上传示例

在web项目核心目录(即webapp)下创建文件upload.jsp,文件代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html>    <head>        <title>Title</title>    </head>    <body>        <form action="/SpringMvcThree/user6/quick1" method="post" enctype="multipart/form-data">            名称<input type="text" name = "username"><br/>            文件<input type="file" name = "uploadFile"><br/>            <input type="submit" value="提交"><br/>        </form>    </body></html>

在controller包下创建UserControllerSix类并添加save1方法,完整代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.io.IOException;@Controller@RequestMapping("/user6")public class UserControllerSix {    @ResponseBody    @RequestMapping("/quick1")    public void save1(@RequestParam("username") String username, @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {        System.out.println(username);        System.out.println(uploadFile);        // 获取上传文件名        String originalFilename = uploadFile.getOriginalFilename();        // 保存文件        uploadFile.transferTo(new File("F:\\node\\idea\\test\\" + originalFilename));    }}

在这里插入图片描述

注意

业务方法的参数名要与请求参数名一致,由于表单上传的文件会被SpringMVC封装成一个MultipartFile对象,且对象名为表单中所定义的文件的name属性值,所以对应业务方法中第二个参数的名为uploadFile

单文件上传时,参数不会自动映射匹配,解决方法有两种:

在pom.xml文件中添加maven插件

使用@RequestParam注解

以上两种方式详见获取普通数据类型的代码示例部分

多文件上传示例

方式一:文件name属性的属性名不一样

在web项目核心目录(即webapp)下创建文件upload2.jsp,文件代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html>    <head>        <title>Title</title>    </head>    <body>        <form action="/SpringMvcThree/user6/quick2" method="post" enctype="multipart/form-data">            名称<input type="text" name = "username"><br/>            文件1<input type="file" name = "uploadFile1"><br/>            文件2<input type="file" name = "uploadFile2"><br/>            <input type="submit" value="提交"><br/>        </form>    </body></html>

UserControllerSix类中添加save2方法,完整代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.io.IOException;@Controller@RequestMapping("/user6")public class UserControllerSix {        // 单文件上传    @ResponseBody    @RequestMapping("/quick1")    public void save1(@RequestParam("username") String username, @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {        System.out.println(username);        System.out.println(uploadFile);        // 获取上传文件名        String originalFilename = uploadFile.getOriginalFilename();        // 保存文件        uploadFile.transferTo(new File("F:\\node\\idea\\test\\" + originalFilename));    }        //多文件上传 方式一    @ResponseBody    @RequestMapping("/quick2")    public void save2(@RequestParam("username") String username, @RequestParam("uploadFile1") MultipartFile uploadFile1, @RequestParam("uploadFile2") MultipartFile uploadFile2) throws IOException {        System.out.println(username);        System.out.println(uploadFile1);        System.out.println(uploadFile2);        // 获取上传文件1的文件名        String originalFilename1 = uploadFile1.getOriginalFilename();        // 保存文件1        uploadFile1.transferTo(new File("F:\\node\\idea\\test\\" + originalFilename1));        // 获取上传文件2的文件名        String originalFilename2 = uploadFile2.getOriginalFilename();        // 保存文件2        uploadFile2.transferTo(new File("F:\\node\\idea\\test\\" + originalFilename2));    }}

在这里插入图片描述

方式二:文件name属性的属性名一样,此时用MultipartFile对象数组

在web项目核心目录(即webapp)下创建文件upload3.jsp,文件代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html>    <head>        <title>Title</title>    </head>    <body>        <form action="/SpringMvcThree/user6/quick3" method="post" enctype="multipart/form-data">            名称<input type="text" name = "username"><br/>            文件1<input type="file" name = "uploadFiles"><br/>            文件2<input type="file" name = "uploadFiles"><br/>            <input type="submit" value="提交"><br/>        </form>    </body></html>

UserControllerSix类中添加save2方法,完整代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.io.IOException;@Controller@RequestMapping("/user6")public class UserControllerSix {    // 单文件上传    @ResponseBody    @RequestMapping("/quick1")    public void save1(@RequestParam("username") String username, @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {        System.out.println(username);        System.out.println(uploadFile);        // 获取上传文件名        String originalFilename = uploadFile.getOriginalFilename();        // 保存文件        uploadFile.transferTo(new File("F:\\node\\idea\\test\\" + originalFilename));    }    //多文件上传 方式一    @ResponseBody    @RequestMapping("/quick2")    public void save2(@RequestParam("username") String username, @RequestParam("uploadFile1") MultipartFile uploadFile1, @RequestParam("uploadFile2") MultipartFile uploadFile2) throws IOException {        System.out.println(username);        System.out.println(uploadFile1);        System.out.println(uploadFile2);        // 获取上传文件1的文件名        String originalFilename1 = uploadFile1.getOriginalFilename();        // 保存文件1        uploadFile1.transferTo(new File("F:\\node\\idea\\test\\" + originalFilename1));        // 获取上传文件2的文件名        String originalFilename2 = uploadFile2.getOriginalFilename();        // 保存文件2        uploadFile2.transferTo(new File("F:\\node\\idea\\test\\" + originalFilename2));    }    // 多文件上传 方式二    @ResponseBody    @RequestMapping("/quick3")    public void save3(@RequestParam("username") String username, @RequestParam("uploadFiles") MultipartFile[] uploadFiles) throws IOException {        System.out.println(username);        for (MultipartFile file : uploadFiles) {            System.out.println(file);            // 获取上传文件名            String originalFilename = file.getOriginalFilename();            // 保存文件            file.transferTo(new File("F:\\node\\idea\\test\\" + originalFilename));        }    }}

在这里插入图片描述

获取Rest风格的请求参数

Restful定义

根据REST风格对资源进行访问称为Restful

Rest风格

Rest是一种软件架构风格、设计风格,而不是标准

它只是提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件

基于该风格的软件会更简洁更有层次,更易实现缓存机制等

可以利用Rest风格来省略请求参数名的书写(即简化书写),比如

http://localhost:8080/SpringMvcThree/user/quick6?name=zhangsan

可改写为

http://localhost:8080/SpringMvcThree/user/quick6/zhangsan

隐藏资源的访问行为,无法通过地址得知对资源进行的是什么操作,比如

http://localhost/user/saveUserhttp://localhost/user/deleteUserhttp://localhost/user/updateUser

均可改写为

http://localhost/user

此时会根据请求方式来判断执行的是哪个方法

当使用Rest风格来获取请求参数时,需要在方法的@RequestMapping注解中用占位符指明映射地址后的为请求参数;同时用@PathVariable注解来修饰业务方法中的参数,且该注解的value值要与@RequestMapping注解中的占位符名称一致

在这里插入图片描述

Rest风格请求使用的是url+请求方式来表示一次请求目的,Http协议中有4种操作方式:

GET:用于获取资源 /users/1 GET:获取id=1的user POST:用于新增/保存资源 /users POST:新增/保存user PUT:用于修改/更新资源 /users/1 PUT:修改/更新id=1的user DELETE:用于删除资源 /users/1 DELETE:删除id=1的user 注意: 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts……

用到的主要注解

@RequestMapping(value, method)method用来指定操作方式@PathVatiable@GetMapping(value):设置当前控制器方法Get请求访问路径以及Restful风格的动作,等同于@RequestMapping(value, method=RequestMethod.Get)@PostMapping(value):设置当前控制器方法Post请求访问路径以及Restful风格的动作,等同于@RequestMapping(value, method=RequestMethod.Post)@PutMapping(value):设置当前控制器方法Put请求访问路径以及Restful风格的动作,等同于@RequestMapping(value, method=RequestMethod.Put)@DeleteMapping(value):设置当前控制器方法Delete请求访问路径以及Restful风格的动作,等同于@RequestMapping(value, method=RequestMethod.Delete)以上注解可详见SpringMVC注解解析部分内容

简单代码示例

在controller包下创建UserControllerTwo类,并在该类中添加save1方法,完整代码如下:

package at.guigu.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller@RequestMapping("/user2")public class UserControllerTwo {    // Restful风格的请求参数    @ResponseBody    @RequestMapping("/quickk1/{name}")    public void save1(@PathVariable(value = "name") String userName) {        System.out.println(userName);    }}

在这里插入图片描述

SpringMVC拦截器(interceptor)

定义

SpringMVC拦截器(interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行SpringMVC拦截器(interceptor)类似于Servlet开发中所使用的过滤器(Filter),用于对处理器进行预处理和后处理拦截器是AOP思想的具体体现

作用

在指定方法调用的前后执行预先设定的代码阻止原始方法的执行

在实际应用中,会将拦截器(interceptor)按一定顺序联结成一条链,该链被称为拦截器链(Interceptor Chain)

在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用

拦截器(Interceptor)和过滤器(Filter)的区别

区别过滤器拦截器
适用范围是servlet规范中的一部分,任何JavaWeb工程都可以使用是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
拦截范围在url-pattern中配置了/*后,会对所有要访问的资源进行拦截增强<mvc:mapping path=""/>中配置了/**之后,也会对所有资源进行拦截增强,但是可以通过<mvc:exclude-mapping path=""/>标签排除不需要拦截的资源

步骤

创建实现HandlerInterceptor接口的实现类,并重写它的三个方法在SpringMVC的核心配置文件中配置拦截器测试拦截器的拦截效果

快速入门

配置文件形式

注意:此处仅弄一个简单环境来测试拦截器,所以准备工作均省略,可详见快速入门以及SpringMVC数据响应部分内容,完整框架如图所示

在这里插入图片描述

以上形式还未配置拦截器,所以运行后前面能接收到响应如图所示,现要求进行拦截器配置

在这里插入图片描述

Step1: 创建一个与三层架构包同级的interceptor包并在该包下创建一个实现HandlerInterceptor接口的拦截器类,代码如下

package at.guigu.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class MyInterceptor implements HandlerInterceptor {    // 在原始方法(即目标方法)执行之前执行    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        System.out.println("preHandle Running...");        return false;    }    // 在原始方法(即目标方法)执行之后,视图对象返回之前执行    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        System.out.println("postHandle Running...");    }    // 在整个请求完成之后执行(即在原始方法执行完并且视图对象也已返回之后执行)    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        System.out.println("afterCompletion Running...");    }}

Step2: 在SpringMVC的核心配置文件中配置拦截器

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context.xsd       http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc.xsd">    <!--mvc的注解驱动-->    <mvc:annotation-driven/>    <!--配置Controller层的注解的组件扫描-->    <context:component-scan base-package="at.guigu.controller"></context:component-scan>    <!--等同于    <context:component-scan base-package="at.guigu">        type指定要扫描的内容为注解,expression指定要扫描的对应注解的全限定名        只扫描at.guigu包下有@Controller注解的类        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    </context:component-scan>    -->    <!--配置内部资源视图解析器-->    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <!--将InternalResourceViewResolver类中的视图名称前缀属性prefix的值设为/jsp/-->        <property name="prefix" value="/jsp/"></property>        <!--将InternalResourceViewResolver类中的视图名称后缀属性suffix的值设为.jsp-->        <property name="suffix" value=".jsp"></property>    </bean>    <!--配置静态资源的路径映射,让 Spring MVC 可以处理静态文件-->    <mvc:default-servlet-handler/>        <!--配置拦截器-->    <mvc:interceptors>        <mvc:interceptor>            <!--定义拦截器的作用范围:此处表示拦截所有请求路径-->            <mvc:mapping path="/**"/>            <!--定义拦截器的具体实现类,class属性值为对应实现类的全限定名-->            <bean class="at.guigu.interceptor.MyInterceptor"/>        </mvc:interceptor>    </mvc:interceptors></beans>

配置文件形式运行后截图如下

在这里插入图片描述

Step3: 测试拦截器拦截效果

在以上运行截图中,只执行了preHandle方法是因为当它的返回值为true表示请求可以交给Controller来执行原始方法;反之则请求被拦截,此时只会执行preHandle方法

此时若将其返回值改true,代码如下

package at.guigu.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class MyInterceptor implements HandlerInterceptor {    // 在原始方法(即目标方法)执行之前执行    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        System.out.println("preHandle Running...");        return true;    }    // 在原始方法(即目标方法)执行之后,视图对象返回之前执行    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        System.out.println("postHandle Running...");    }    // 在整个请求完成之后执行(即在原始方法执行完并且视图对象也以返回之后执行)    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        System.out.println("afterCompletion Running...");    }}

此时的运行截图如下

在这里插入图片描述

配置类形式方法一

注意:此处仅弄一个简单环境来测试拦截器,所以准备工作均省略,可详见快速入门以及SpringMVC数据响应部分内容,完整框架如图所示

在这里插入图片描述

以上形式还未配置拦截器,所以运行后前面能接收到响应如图所示,现要求进行拦截器配置

在这里插入图片描述

Step1: 创建一个与三层架构包同级的interceptor包并在该包下创建一个实现HandlerInterceptor接口的拦截器类,代码如下

注意:要给该类加上@Component注解(作用:实例化bean)

package at.guigu.interceptor;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Componentpublic class MyInterceptor implements HandlerInterceptor {    // 在原始方法(即目标方法)执行之前执行    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        System.out.println("preHandle Running...");        return false;    }    // 在原始方法(即目标方法)执行之后,视图对象返回之前执行    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        System.out.println("postHandle Running...");    }    // 在整个请求完成之后执行(即在原始方法执行完并且视图对象也以返回之后执行)    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        System.out.println("afterCompletion Running...");    }}

Step2: 在config包下创建配置静态资源以及拦截器的路径映射类,即继承WebMvcConfigurationSupport类的子类SpringMvcSupport,代码如下:

依赖注入MyInterceptor的bean重写addInterceptors方法
package at.guigu.config;import at.guigu.interceptor.MyInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;// 配置静态资源以及拦截器的路径映射@Configurationpublic class SpringMvcSupport extends WebMvcConfigurationSupport {    @Autowired    private MyInterceptor myInterceptor;    @Override    protected void addResourceHandlers (ResourceHandlerRegistry registry) {        // 配置静态资源路径映射        registry.addResourceHandler("/xxx/**").addResourceLocations("/xxx/");    }    @Override    protected void addInterceptors(InterceptorRegistry registry) {        //addInterceptor定义拦截器的具体实现类        //addPathPatterns的参数代表拦截所有请求路径        registry.addInterceptor(myInterceptor).addPathPatterns("/**");    }}

Step3: 在SpringMVC的核心配置类中利用@ComponentScan注解扫描拦截器类MyInterceptor以及SpringMvcSupport类所在包

package at.guigu.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller"></context:component-scan>// 加载controller对应的bean@ComponentScan({"at.guigu.controller", "at.guigu.interceptor", "at.guigu.config"})// 自动配置 Spring MVC 的各种特性@EnableWebMvcpublic class SpringMvcConfiguration {    // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/jsp/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }}

配置类形式运行后截图如下

在这里插入图片描述

Step4: 测试拦截器拦截效果

在以上运行截图中,只执行了preHandle方法是因为当它的返回值为true表示请求可以交给Controller来执行原始方法;反之则请求被拦截,此时只会执行preHandle方法

此时若将其返回值改true,代码如下

package at.guigu.interceptor;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Componentpublic class MyInterceptor implements HandlerInterceptor {    // 在原始方法(即目标方法)执行之前执行    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        System.out.println("preHandle Running...");        return true;    }    // 在原始方法(即目标方法)执行之后,视图对象返回之前执行    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        System.out.println("postHandle Running...");    }    // 在整个请求完成之后执行(即在原始方法执行完并且视图对象也以返回之后执行)    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        System.out.println("afterCompletion Running...");    }}

此时的运行截图如下

在这里插入图片描述

配置类形式方法二

此处配置类形式方法一中可以将第二、三步合并,即:让SpringMVC的核心配置类SpringMvcConfiguration继承WebMvcConfigurer接口并重写其中的addInterceptors方法来进行拦截器的路径映射配置,步骤如下

Step1: 创建一个与三层架构包同级的interceptor包并在该包下创建一个实现HandlerInterceptor接口的拦截器类,代码略

Step2: 让SpringMVC的核心配置类SpringMvcConfiguration继承WebMvcConfigurer接口并重写其中的addInterceptors方法来进行拦截器的路径映射配置,代码如下:

package at.guigu.config;import at.guigu.interceptor.MyInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller"></context:component-scan>// 加载controller对应的bean@ComponentScan("at.guigu.controller")// 自动配置 Spring MVC 的各种特性@EnableWebMvcpublic class SpringMvcConfiguration implements WebMvcConfigurer {    // 依赖注入拦截器的bean    @Autowired    private MyInterceptor myInterceptor;        // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/jsp/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }    // 配置静态资源路径    @Override    public void addResourceHandlers(ResourceHandlerRegistry registry) {        // 配置静态资源路径映射        registry.addResourceHandler("/xxx/**").addResourceLocations("/xxx/");    }    // 配置拦截器路径    @Override    public void addInterceptors(InterceptorRegistry registry) {        //addInterceptor定义拦截器的具体实现类        //addPathPatterns的参数代表拦截所有请求路径        registry.addInterceptor(myInterceptor).addPathPatterns("/**");    }}

运行截图略

HandlerInterceptor接口详解

HandlerInterceptor接口方法

HandlerInterceptor接口方法解释
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception在请求到达Controller之前执行(即在在原始方法(即目标方法)执行之前执行),用于执行预处理逻辑。
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception在Controller处理完请求之后,视图渲染之前执行(即在原始方法(目标方法)执行之后,视图对象返回之前执行)
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception在整个请求完成之后执行(即在原始方法执行完并且视图对象也已返回之后执行)。注意:即使在Controller或视图渲染过程中抛出异常,该方法也会执行

方法对应参数及返回值详解(相同参数不在重复解释)

preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)——前置处理方法 HttpServletRequest :请求对象HttpServletResponse :响应对象handler :被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装return true :表示请求可以交给Controller来执行原始方法。(当为拦截链环境时,会将请求交给下一个Interceptor的前置处理方法,直到所有前置处理方法均通过后,会交给Controller来执行原始方法)return false :表示请求被拦截,Controller控制器和拦截链上后续的Interceptor都不会继续执行 postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView)——后置处理方法 ModelAndView :如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行跳转调用该方法的前提是前置处理方法的返回值为true afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex)——完成后处理方法 Exception : 如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理调用该方法的前提是前置处理方法的返回值为true无论处理器方法内部是否出现异常,该方法均会执行

preHandle方法经常用于执行预处理逻辑,比如:

验证用户身份或权限。

检查请求参数是否合法。

记录日志或统计请求次数。

当返回值为false时请求被拦截,后续逻辑(如Controller处理或postHandleafterCompletion方法)不会执行(可详见快速入门)。此时可以直接设置响应,例如返回一个错误状态码或转发或重定向到其他页面。示例如下

在web项目核心目录(即webapp)下的jsp文件夹中创建error.jsp页面,代码如下,用于拦截示例

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html>    <head>        <title>Title</title>    </head>    <body>        <h1>Error...</h1>    </body></html>

MyInterceptor类的代码更改如下

package at.guigu.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class MyInterceptor implements HandlerInterceptor {    // 在原始方法(即目标方法)执行之前执行    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        System.out.println("preHandle Running...");        String str = request.getParameter("param");        if ("yes".equals(str)) {            return true;        } else {            request.getRequestDispatcher("/jsp/error.jsp").forward(request, response);            return false;        }    }    // 在原始方法(即目标方法)执行之后,视图对象返回之前执行    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        System.out.println("postHandle Running...");    }    // 在整个请求完成之后执行(即在原始方法执行完并且视图对象也以返回之后执行)    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        System.out.println("afterCompletion Running...");    }}

当param请求参数值不是yes

在这里插入图片描述

当param请求参数值是yes

在这里插入图片描述

postHandle方法用于对ModelAndView进行修改,例如:

添加全局数据到视图中(如页面标题、用户信息)。

根据Controller的处理结果动态调整视图数据。

注意:如果Controller方法没有返回ModelAndView(如使用@ResponseBodyRestController),则此方法可能不会被调用。

MyInterceptor类的代码更改如下

package at.guigu.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class MyInterceptor implements HandlerInterceptor {    // 在原始方法(即目标方法)执行之前执行    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        System.out.println("preHandle Running...");        String str = request.getParameter("param");        if ("yes".equals(str)) {            return true;        } else {            request.getRequestDispatcher("/jsp/error.jsp").forward(request, response);            return false;        }    }    // 在原始方法(即目标方法)执行之后,视图对象返回之前执行    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        modelAndView.addObject("username", "李四");        System.out.println("postHandle Running...");    }    // 在整个请求完成之后执行(即在原始方法执行完并且视图对象也以返回之后执行)    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        System.out.println("afterCompletion Running...");    }}

在这里插入图片描述

拦截器配置详解

在快速入门中配置的是单个拦截器,在SpringMVC的核心配置文件中对应的代码如下

<!--配置拦截器--><mvc:interceptors>    <mvc:interceptor>        <!--定义拦截器的作用范围:此处表示拦截所有请求路径-->        <mvc:mapping path="/**"/>        <!--定义拦截器的具体实现类,class属性值为对应实现类的全限定名-->        <bean class="at.guigu.interceptor.MyInterceptor"/>    </mvc:interceptor></mvc:interceptors>

用到的标签

标签解释
<mvc:interceptors>配置拦截器
<mvc:interceptors>内嵌标签解释
<mvc:interceptor>配置单个拦截器
<mvc:interceptor>内嵌标签解释
<mvc:mapping path/>定义拦截器的作用范围。当path属性为/**时,代表拦截所有请求路径;当为/xxx/**时,代表拦截xxx下的所有资源
<bean class/>定义拦截器的具体实现类,class属性值为对应实现类的全限定名

拦截器链

特点

拦截器链执行顺序以拦截器添加顺序为准当拦截器中出现对原始处理器的拦截时,后续的拦截器均终止运行当拦截器运行中段时,此时仅运行配置在前面的拦截器的afterCompletion(即完成后处理方法)操作

在这里插入图片描述

配置多个拦截器(拦截器链)步骤(配置文件形式)

环境准备等工作可见快速入门,此处以配置两个拦截器为例

Step1:interceptor包下创建第二个实现HandlerInterceptor接口的拦截器类,并重写其中的方法,代码如下

package at.guigu.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class MyInterceptorTwo implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        System.out.println("preHandle Running222...");        return true;    }    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        System.out.println("postHandle Running222...");    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        System.out.println("afterCompletion Running222...");    }}

Step2:

在SpringMVC的核心配置文件中配置拦截器

<!--配置拦截器--><mvc:interceptors>    <!--配置第一个拦截器实现类-->    <mvc:interceptor>        <!--定义拦截器的作用范围:此处表示拦截所有请求路径-->        <mvc:mapping path="/**"/>        <!--定义拦截器的具体实现类,class属性值为对应实现类的全限定名-->        <bean class="at.guigu.interceptor.MyInterceptor"/>    </mvc:interceptor>    <!--配置第二个拦截器实现类-->    <mvc:interceptor>        <!--定义拦截器的作用范围:此处表示拦截所有请求路径-->        <mvc:mapping path="/**"/>        <!--定义拦截器的具体实现类,class属性值为对应实现类的全限定名-->        <bean class="at.guigu.interceptor.MyInterceptorTwo"/>    </mvc:interceptor></mvc:interceptors>

SpringMVC核心配置文件完整代码如下

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context.xsd       http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc.xsd">    <!--mvc的注解驱动-->    <mvc:annotation-driven/>    <!--配置Controller层的注解的组件扫描-->    <context:component-scan base-package="at.guigu.controller"></context:component-scan>    <!--等同于    <context:component-scan base-package="at.guigu">        type指定要扫描的内容为注解,expression指定要扫描的对应注解的全限定名        只扫描at.guigu包下有@Controller注解的类        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    </context:component-scan>    -->    <!--配置内部资源视图解析器-->    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <!--将InternalResourceViewResolver类中的视图名称前缀属性prefix的值设为/jsp/-->        <property name="prefix" value="/jsp/"></property>        <!--将InternalResourceViewResolver类中的视图名称后缀属性suffix的值设为.jsp-->        <property name="suffix" value=".jsp"></property>    </bean>    <!--配置静态资源的路径映射,让 Spring MVC 可以处理静态文件-->    <mvc:default-servlet-handler/>    <!--配置拦截器-->    <mvc:interceptors>        <!--配置第一个拦截器实现类-->        <mvc:interceptor>            <!--定义拦截器的作用范围:此处表示拦截所有请求路径-->            <mvc:mapping path="/**"/>            <!--定义拦截器的具体实现类,class属性值为对应实现类的全限定名-->            <bean class="at.guigu.interceptor.MyInterceptor"/>        </mvc:interceptor>        <!--配置第二个拦截器实现类-->        <mvc:interceptor>            <!--定义拦截器的作用范围:此处表示拦截所有请求路径-->            <mvc:mapping path="/**"/>            <!--定义拦截器的具体实现类,class属性值为对应实现类的全限定名-->            <bean class="at.guigu.interceptor.MyInterceptorTwo"/>        </mvc:interceptor>    </mvc:interceptors></beans>

在这里插入图片描述

配置多个拦截器(拦截器链)步骤(配置类形式)

环境准备等工作可见快速入门,此处以配置两个拦截器为例

Step1:interceptor包下创建第二个实现HandlerInterceptor接口的拦截器类,并重写其中的方法,代码如下

package at.guigu.interceptor;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Componentpublic class MyInterceptorTwo implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        System.out.println("preHandle Running222...");        return true;    }    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        System.out.println("postHandle Running222...");    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        System.out.println("afterCompletion Running222...");    }}

Step2: 在config包下的继承WebMvcConfigurationSupport类的子类SpringMvcSupport中依赖注入MyInterceptorTwo的bean,代码如下:

package at.guigu.config;import at.guigu.interceptor.MyInterceptor;import at.guigu.interceptor.MyInterceptorTwo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;// 配置静态资源以及拦截器的路径映射@Configurationpublic class SpringMvcSupport extends WebMvcConfigurationSupport {    @Autowired    private MyInterceptor myInterceptor;    @Autowired    private MyInterceptorTwo myInterceptorTwo;    // 配置静态资源路径    @Override    protected void addResourceHandlers (ResourceHandlerRegistry registry) {        // 配置静态资源路径映射        registry.addResourceHandler("/xxx/**").addResourceLocations("/xxx/");    }    // 配置拦截器路径    @Override    protected void addInterceptors(InterceptorRegistry registry) {        //addInterceptor定义拦截器的具体实现类        //addPathPatterns的参数代表拦截所有请求路径        registry.addInterceptor(myInterceptor).addPathPatterns("/**");        registry.addInterceptor(myInterceptorTwo).addPathPatterns("/**");    }}

Step3: 在SpringMVC的核心配置类中利用@ComponentScan注解扫描拦截器类MyInterceptor以及SpringMvcSupport类所在包,代码如下

package at.guigu.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller"></context:component-scan>// 加载controller对应的bean@ComponentScan({"at.guigu.controller", "at.guigu.interceptor", "at.guigu.config"})// 自动配置 Spring MVC 的各种特性@EnableWebMvcpublic class SpringMvcConfiguration {    // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/jsp/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }}

在这里插入图片描述

SpringMVC异常处理

异常常见位置与诱因

框架内部抛出的异常:因使用不合规导致数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)

项目异常分类及处理方案

项目异常分类场景处理步骤
业务异常(BusinessException)1.规范的用户行为产生的异常;2.不规范的用户行为操作产生的异常发送对应消息传递给用户,提醒规范操作
系统异常(SystemException)项目运行过程中可预计且无法避免的异常1.记录日志;2.发送固定消息传递给用户,安抚用户;3.发送特定消息给运维人员,提醒维护;
其他异常(Exception)编程人员未预期到的异常1.发送固定消息传递给用户,安抚用户;2.发送特定消息给编程人员,提醒维护(纳入预期范围内)3.记录日志

系统异常分类

系统异常(SystemException)分类解决方式
预期异常通过捕获异常获取异常信息
运行时异常RuntimeException通过规范代码开发、测试等手段减少运行时异常发生

异常处理思路

在ssm框架中,各个层级出现的所有均异常向上抛出到表现层进行处理,持久层抛给业务层,业务层抛给表现层,表现层继续向上抛出给SpringMVC的核心前端控制器,最后由SpringMVC的核心前端控制器交由异常处理器进行异常处理

在这里插入图片描述

异常处理器

底层使用的是AOP的思想,它能够集中统一的处理项目中出现的异常

在这里插入图片描述

异常处理的方式

简单异常处理:使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver自定义异常处理: 方式一:实现SpringMVC的异常处理接口HandlerExceptionResolver来自定义自己的异常处理器方式二:

自定义异常的方式

自定义异常方式使用场景
创建继承RuntimeException(运行时异常)的子类程序内部的错误或不可恢复的错误,不强制要求捕获或声明
创建继承Exception(可检查异常)的子类用于可恢复的错误或外部因素引起的错误,需要强制要求捕获或声明

注意:

​ 1.在本示例中并没有分太清,只是为了演示作用,后续真实项目可根据情况选择;

​ 2.在配置文件形式的示例中创建继承Exception(可检查异常)的子类来创建自定义的异常;

​ 3.在配置类形式的示例中创建继承RuntimeException(运行时异常)的子类来创建自定义的异常。

​ 4.通过创建继承Exception(可检查异常)的子类来创建自定义的异常在示例中只进行了简写,并未完整给出具体操作代码,而在创建继承RuntimeException(运行时异常)的子类来创建自定义的异常中进行了详细的书写,两者是一样的,示例中仅以一个为例进行精写

异常处理的两种方式(配置文件形式)

环境准备

Step1: 导入坐标(略,可详见快速入门)

Step2: 右键源代码配置文件目录(即资源文件resources)→NewXML Configuration FileSpring Config,创建Spring和SpringMVC的核心配置文件,代码截图如下

在这里插入图片描述

Step3: 创建业务层service包、controller包、异常exception包,代码截图如下

注意:在service包中模拟各种异常来进行后续的示例操作

在这里插入图片描述

Step4: 配置web项目核心目录(即webapp)下的WEB-INF中的web.xml,并在webapp目录下创建jsp文件夹,并写入4个页面success.jsp、error.jsp、errorshow1.jsp、errorshow5.jsp,代码截图如下

注意:success.jsp为无异常时前端显示页面;error.jsp为默认错误视图;errorshow1.jsp、errorshow5.jsp分别为业务层show1()show2()方法抛出异常后所映射的视图

在这里插入图片描述

此时运行后前端页面会报对应异常,分别如下图所示

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

初始项目结构如下

在这里插入图片描述

简单异常的处理(配置文件形式)

注意:SpringMVC已经定义好了该类型的转换器,在使用时可根据项目情况在SpringMVC的核心配置文件中进行相应 异常与视图 的映射配置

简单异常处理只需要在SpringMVC核心配置文件中配置异常处理机制即可,代码如下

<!--配置异常处理机制--><bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">    <property name="defaultErrorView" value="error1"/>    <property name="exceptionMappings">        <map>            <entry key="at.guigu.exception.MyException" value="error2"/>            <entry key="java.lang.ClassCastException" value="error3"/>        </map>    </property></bean>

原理: 通过SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver配置异常处理机制。当某个异常抛出时,Spring会根据配置的 exceptionMappings 映射到异常所对应的视图。如果抛出的异常没有在 exceptionMappings 中配置,那么会使用 defaultErrorView 指定的默认错误视图。

示例代码解释: 当抛出的异常为MyException时则会映射到视图error2.jsp;当抛出异常为ClassCastException时则会映射到视图error3.jsp;若抛出的异常没有在 exceptionMappings 中配置,则此时按照defaultErrorView(即默认异常视图)映射到error1.jsp

注意: 在配置异常处理机制的代码中,视图均为带后缀名.jsp,是因为已在SpringMVC的核心配置文件中提前配置了内部资源视图解析器

代码运行示例

未在SpringMVC核心配置文件中配置异常处理机制时,此时由于有异常所以前端会显示异常页面,可详见环境准备中的运行截图

在SpringMVC核心配置文件中配置异常处理机制,完整代码如下

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context.xsd       http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc.xsd">    <!--mvc的注解驱动-->    <mvc:annotation-driven/>    <!--配置Controller层的注解的组件扫描-->    <context:component-scan base-package="at.guigu.controller"></context:component-scan>    <!--等同于    <context:component-scan base-package="at.guigu">        type指定要扫描的内容为注解,expression指定要扫描的对应注解的全限定名        只扫描at.guigu包下有@Controller注解的类        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    </context:component-scan>    -->    <!--配置内部资源视图解析器-->    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <!--将InternalResourceViewResolver类中的视图名称前缀属性prefix的值设为/jsp/-->        <property name="prefix" value="/jsp/"></property>        <!--将InternalResourceViewResolver类中的视图名称后缀属性suffix的值设为.jsp-->        <property name="suffix" value=".jsp"></property>    </bean>    <!--配置静态资源的路径映射,让 Spring MVC 可以处理静态文件-->    <mvc:default-servlet-handler/>    <!--配置异常处理机制-->    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">        <property name="defaultErrorView" value="error"/>        <property name="exceptionMappings">            <map>                <entry key="java.lang.ClassCastException" value="errorShow1"/>                <entry key="at.guigu.exception.MyException" value="errorshow5"/>            </map>        </property>    </bean></beans>

运行截图如下

show1() ClassCastException异常自动映射到errorShow1.jsp页面

在这里插入图片描述

show5() 自定义MyException异常自动映射到errorShow5.jsp页面

在这里插入图片描述

其它异常自动映射到error.jsp页面

在这里插入图片描述

自定义异常的处理方式一(配置文件及配置类形式)

步骤

exception包下创建继承Exception(可检查异常)的子类来创建自定义的异常创建实现HandlerExceptionResolver接口的异常处理器类在SpingMVC的核心配置文件或配置类中配置自定义异常处理机制编写异常映射页面测试异常跳转

代码实现

Step1: 创建继承Exception(可检查异常)的子类来创建自定义的异常

本步骤已在环境准备中完成,此处省略

Step2: 创建一个与三层架构包同级的resolver包,并在该包下创建一个实现HandlerExceptionResolver接口的异常处理器类MyExceptionResolver,并重写其中的resolveException方法,代码如下

package at.guigu.resolver;import at.guigu.exception.MyException;import org.springframework.web.servlet.HandlerExceptionResolver;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class MyExceptionResolver implements HandlerExceptionResolver {    /**     *     * @param httpServletRequest     * @param httpServletResponse     * @param o     * @param e:为抛出的异常对象     * @return :返回ModelAndView对象,为异常映射的视图     */    @Override    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {        ModelAndView modelAndView = new ModelAndView();        // 若是ClassCastException异常则映射到errorShow1.jsp页面        if (e instanceof ClassCastException) {            modelAndView.setViewName("errorShow1");        // 若是自定义MyException异常则映射到errorShow5.jsp页面        } else if (e instanceof MyException) {            modelAndView.setViewName("errorShow5");        // 若是其它异常则映射到error.jsp页面        } else {            modelAndView.setViewName("error");        }        return  modelAndView;    }}

Step3: 在SpingMVC的核心配置文件或配置类中配置自定义异常处理机制,代码如下:

SpingMVC的核心配置文件

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context.xsd       http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc.xsd">    <!--mvc的注解驱动-->    <mvc:annotation-driven/>    <!--配置Controller层的注解的组件扫描-->    <context:component-scan base-package="at.guigu.controller"></context:component-scan>    <!--等同于    <context:component-scan base-package="at.guigu">        type指定要扫描的内容为注解,expression指定要扫描的对应注解的全限定名        只扫描at.guigu包下有@Controller注解的类        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    </context:component-scan>    -->    <!--配置内部资源视图解析器-->    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <!--将InternalResourceViewResolver类中的视图名称前缀属性prefix的值设为/jsp/-->        <property name="prefix" value="/jsp/"></property>        <!--将InternalResourceViewResolver类中的视图名称后缀属性suffix的值设为.jsp-->        <property name="suffix" value=".jsp"></property>    </bean>    <!--配置静态资源的路径映射,让 Spring MVC 可以处理静态文件-->    <mvc:default-servlet-handler/>    <!--    配置异常处理机制    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">        <property name="defaultErrorView" value="error"/>        <property name="exceptionMappings">            <map>                <entry key="java.lang.ClassCastException" value="errorShow1"/>                <entry key="at.guigu.exception.MyException" value="errorShow5"/>            </map>        </property>    </bean>    -->    <!--配置自定义异常处理机制-->    <bean class="at.guigu.resolver.MyExceptionResolver"/></beans>

SpingMVC的核心配置类

package at.guigu.config;import at.guigu.resolver.MyExceptionResolver;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller"></context:component-scan>// 加载controller对应的bean@ComponentScan(basePackages = {"at.guigu.controller"})// 自动配置 Spring MVC 的各种特性,比如:类型转换器、mvc的注解驱动<mvc:annotation-driven/>@EnableWebMvc// 引入配置静态资源类@Import(SpringMvcSupport.class)public class SpringMvcConfiguration {    // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/jsp/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }    // 配置自定义异常处理器    @Bean    public MyExceptionResolver myExceptionResolver() {        return new MyExceptionResolver();    }}

Step4: 编写异常映射页面。(本步骤已在环境准备工作中实现,此处省略)

运行截图如下

show1() ClassCastException异常自动映射到errorShow1.jsp页面

在这里插入图片描述

show5() 自定义MyException异常自动映射到errorShow5.jsp页面

在这里插入图片描述

其它异常自动映射到error.jsp页面

在这里插入图片描述

异常处理的两种方式(配置类形式)

方式一简单异常处理:使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver

方式二自定义异常的处理:实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器

用到的注解如下

类注解解释
@ControllerAdvice定义全局的异常处理、数据绑定、全局模型属性等功能,用于集中处理多个控制器中的异常、数据绑定、模型属性等
@RestControllerAdvice定义全局的异常处理、数据绑定、全局模型属性等功能,用于集中处理多个控制器中的异常、数据绑定、模型属性等
方法注解解释
@ExceptionHandler设置指定异常的处理方案,出现异常后终止原始控制器执行,并转入当前方法执行

环境准备

Step1: 导入坐标(略,可详见快速入门)

Step2: 创建config包,并在该包下创建Spring核心配置类SpringConfiguration,SpringMVC核心配置类SpringMvcConfiguration、配置静态资源的继承WebMvcConfigurationSupport类的子类SpringMvcSupport、代替web.xml文件的继承AbstractAnnotationConfigDispatcherServletInitializer的子类ServletConfiguration,如图所示

在这里插入图片描述

Step3: 创建业务层service包、controller包、异常exception包,代码截图如下

注意:在service包中模拟各种异常来进行后续的示例操作

在这里插入图片描述

Step4: 在webapp目录下创建jsp文件夹,并写入4个页面success.jsp、error.jsp、errorshow1.jsp、errorshow5.jsp,代码截图如下

注意:success.jsp为无异常时前端显示页面;error.jsp为默认错误视图;errorshow1.jsp、errorshow5.jsp分别为业务层show1()show2()方法抛出异常后所映射的视图

在这里插入图片描述

此时运行后前端页面会报对应异常,分别如下图所示

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

简单异常的处理(配置类形式)

Step1: 在controller包下创建ProjectEcepAdvice类,代码如下

Step1-1: 添加@ControllerAdvice注解或@RestControllerAdvice注解 若当前controller包下使用的是@Controller注解则为ProjectEcepAdvice类添加@ControllerAdvice注解若当前controller包下使用的是@RestController注解则为ProjectEcepAdvice类添加@RestControllerAdvice注解 Step1-2: 写一个处理异常的方法doException,并将拦截的异常作为参数传入Step1-3: 给该方法添加一个@ExceptionHandler(value)注解,并设置value属性,以此来指定捕获的异常类型 捕获所有类型异常:@ExceptionHandler(Exception.class)捕获指定类型异常:@ExceptionHandler({FileNotFoundException.class, IOException.class})
package at.guigu.controller;import at.guigu.exception.MyException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.servlet.ModelAndView;@ControllerAdvicepublic class ProjectEcepAdvice {    // 捕获所有异常进行处理    @ExceptionHandler(Exception.class)    public ModelAndView doException(Exception e){        ModelAndView modelAndView = new ModelAndView();        // 若是ClassCastException异常则映射到errorShow1.jsp页面        if (e instanceof ClassCastException) {            modelAndView.setViewName("errorShow1");            // 若是自定义MyException异常则映射到errorShow5.jsp页面        } else if (e instanceof MyException) {            modelAndView.setViewName("errorShow5");            // 若是其它异常则映射到error.jsp页面        } else {            modelAndView.setViewName("error");        }        return  modelAndView;    }}

运行截图如下

show1() ClassCastException异常自动映射到errorShow1.jsp页面

在这里插入图片描述

show5() 自定义MyException异常自动映射到errorShow5.jsp页面

在这里插入图片描述

其它异常自动映射到error.jsp页面

在这里插入图片描述

注意:若ProjectEcepAdvice类并未在controller包下,而是在其它包下,则此时需要在SpringMVC的核心配置类中用@ComponentScan注解引入,比如:

此时我并未在controller包下创建该类,而是创建了一个与三层架构包同级的advice包,并在该包下创建ProjectEcepAdvice类,此时就需要SpringMVC的核心配置类中利用@ComponentScan注解引入该包,让其能够扫描到,代码如下:

package at.guigu.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.view.InternalResourceViewResolver;// 该注解代表该类是SpringMVC的核心配置类@Configuration// 配置注解的组件扫描<context:component-scan base-package="at.guigu.controller, at.guigu.advice"></context:component-scan>// 加载controller对应的bean@ComponentScan(basePackages = {"at.guigu.controller", "at.guigu.advice"})// 自动配置 Spring MVC 的各种特性,比如:类型转换器、mvc的注解驱动<mvc:annotation-driven/>//@EnableWebMvc// 引入配置静态资源类@Import(SpringMvcSupport.class)public class SpringMvcConfiguration {    // 配置视图解析器    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver resolver = new InternalResourceViewResolver();        resolver.setPrefix("/jsp/");  // 设置视图文件路径前缀        resolver.setSuffix(".jsp");   // 设置视图文件后缀        return resolver;    }}

自定义异常的处理(配置类形式)

步骤

exception包下创建继承RuntimeException的子类SystemException来自定义一个异常在SpingMVC的核心配置文件中配置自定义异常处理机制编写异常映射页面测试异常跳转

代码实现

Step1: 创建一个与三层架构包同级的exception包,并在该包下创建继承RuntimeException的子类SystemException自定义一个系统异常 ,代码如下:

设置一个code私有属性来接收异常对应的编号添加构造器(注意:RuntimeException有5个构造器,可根据需要选用一个或多个)添加getset方法
package at.guigu.exception;public class SystemException extends RuntimeException {    // 给异常添加编号    private Integer code;    // 添加构造方法    public SystemException(Integer code, String message) {        super(message);        this.code = code;    }    public SystemException(Integer code, String message, Throwable cause) {        super(message, cause);        this.code = code;    }    public Integer getCode() {        return code;    }    public void setCode(Integer code) {        this.code = code;    }}

Step2: 按照第一步在创建一个BusinessException类来自定义一个业务异常 ,代码如下:

package at.guigu.exception;public class BusinessException extends RuntimeException {    // 给异常添加编号    private Integer code;    // 添加构造方法    public BusinessException(Integer code, String message) {        super(message);        this.code = code;    }    public BusinessException(Integer code, String message, Throwable cause) {        super(message, cause);        this.code = code;    }    public Integer getCode() {        return code;    }    public void setCode(Integer code) {        this.code = code;    }}

Step3: 在controller包下创建Code类,来封装自定义异常的响应码,代码如下

package at.guigu.controller;public class Code {    // 50001代表自定义异常BusinessException    public static final Integer BUSS_ERR = 50001;    // 50002代表自定义异常SystemException    public static final Integer SYST_ERR = 50002;    // 50003代表自定义MyException异常    public static final Integer MYEXECP_ERR = 50003;    // 50004代表除自定义异常外的其它异常    public static final Integer OTHER_ERR = 50004;}

Step4: 将业务层中可能出现的异常进行拦截包装,然年将其转换成自定义的异常,代码如下:

此处只进行部分演示
  package at.guigu.service;    import at.guigu.controller.Code;  import at.guigu.exception.MyException;  import at.guigu.exception.BusinessException;  import at.guigu.exception.SystemException;  import org.springframework.stereotype.Service;    import java.io.FileInputStream;  import java.io.FileNotFoundException;  import java.io.InputStream;    @Service  public class UserService {      public void show1() {          // 将show1()抛出的类型转换异常拦截包装并将其转换成自定义的异常          Object str = "zhangsan";          if (str == "zhangsan") {              throw new BusinessException(Code.BUSS_ERR, "抛出类型转换异常");          }      }      public void show2() {          // 将show2()抛出的除0异常拦截包装并将其转换成自定义的异常          try {              int i = 1/0;          } catch (Exception e) {              throw new SystemException(Code.SYST_ERR, "抛出除0异常,请重新输入除数", e);          }      }      public void show3() throws FileNotFoundException {          System.out.println("抛出文件找不到异常");          InputStream inputStream = new FileInputStream("C:/xxx/xxx/xxx.txt");      }      public void show4() {          System.out.println("抛出空指针异常");          String str = null;          str.length();      }      public void show5() throws MyException {          System.out.println("抛出自定义异常");          throw new MyException();      }  }

Step6: 在controller包下创建ProjectEcepAdvice类,代码如下

Step6-1: 添加@ControllerAdvice注解或@RestControllerAdvice注解 若当前controller包下使用的是@Controller注解则为ProjectEcepAdvice类添加@ControllerAdvice注解若当前controller包下使用的是@RestController注解则为ProjectEcepAdvice类添加@RestControllerAdvice注解 Step6-2: 写一个处理异常的方法doException,并将拦截的异常作为参数传入Step6-3: 给该方法添加一个@ExceptionHandler(value)注解,并设置value属性,以此来指定捕获的异常类型 捕获所有类型异常:@ExceptionHandler(Exception.class)捕获指定类型异常:@ExceptionHandler({FileNotFoundException.class, IOException.class})
package at.guigu.controller;import at.guigu.exception.BusinessException;import at.guigu.exception.MyException;import at.guigu.exception.SystemException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.servlet.ModelAndView;import java.io.FileNotFoundException;@ControllerAdvicepublic class ProjectEcepAdvice {    // 捕获指定的自定义异常BusinessException进行处理    @ExceptionHandler(BusinessException.class)    public ModelAndView doBusinessException(BusinessException e){        ModelAndView modelAndView = new ModelAndView();        modelAndView.setViewName("errorShow1");        // 发送对应消息传递给用户,提醒规范操作        return modelAndView;    }    // 捕获指定的自定义异常SystemException进行处理    @ExceptionHandler(SystemException.class)    public ModelAndView doSystemException(SystemException e){        // 记录日志        // 发送消息给运维        // 发送对应消息传递给用户        // 发送邮件以及异常发送给开发人员        ModelAndView modelAndView = new ModelAndView();        modelAndView.setViewName("error");        return modelAndView;    }    // 捕获除自定义异常MyException进行处理    @ExceptionHandler(MyException.class)    public ModelAndView doMyException(MyException e){        ModelAndView modelAndView = new ModelAndView();        modelAndView.setViewName("errorShow5");        return modelAndView;    }    // 捕获除自定义外的其它异常进行处理    @ExceptionHandler({FileNotFoundException.class, NullPointerException.class})    public ModelAndView doException(Exception e){        // 记录日志        // 发送消息给运维        // 发送对应消息传递给用户        // 发送邮件以及异常发送给开发人员        ModelAndView modelAndView = new ModelAndView();        modelAndView.setViewName("error");        return modelAndView;    }}

运行截图略


点击全文阅读


本文链接:http://zhangshiyu.com/post/198050.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1