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
类,代码如下
//将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类中有两个属性name
和age
,所以请求参数名称要与集合名.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对象作为控制器方法的参数进行注入,常用对象有
HttpServletRequest
HttpServletResponse
HttpSession
获取方式: 只需要将想要的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 注解属性 | 解释 |
---|---|
value | Cookie名称 |
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)
会失效,因为该方法只能获取单个参数值
默认情况下,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> <!– maven插件版本 –> <version>3.13.0</version> <configuration> <!– Java版本 –> <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风格对资源进行访问称为RestfulRest风格
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处理或postHandle
、afterCompletion
方法)不会执行(可详见快速入门)。此时可以直接设置响应,例如返回一个错误状态码或转发或重定向到其他页面。示例如下
在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
(如使用@ResponseBody
或RestController
),则此方法可能不会被调用。
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
)→New
→XML Configuration File
→Spring 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,代码截图如下
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
类,代码如下
@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
来自定义一个系统异常 ,代码如下:
RuntimeException
有5个构造器,可根据需要选用一个或多个)添加get
、set
方法 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
类,代码如下
@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; }}
运行截图略