代表报文信息转化器,可以将请求报文转换为Java对象,也可以将Java对象转换为响应报文
请求报文分为三部分:请求头、请求空行和请求体
概述:这部分我们将介绍一些关于报文的注解和类型的使用
HttpMessageConverter 为我们提供了两个注解和两个类型:
接下来我们对各部分展开论述,并演示对应的用法
用在控制器方法的形参前,我们一般用于字符串类型的变量接收请求体,是以键值对的形式存在的
编写我们的发起请求的表单,表单收集到的属性值和属性名就是我们请求体中的内容
<!--测试获得请求体-->
<form th:action="@{/testRequestBody}" method="post">
<input type="text" name="username" >
<input type="text" name="password" >
<input type="submit" value="测试@RequestBody" >
</form>
编写对应的控制器方法
// 获取请求体
@RequestMapping(value = "/testRequestBody", method = RequestMethod.POST)
public String testRequestBody(@RequestBody String requestBody){
System.out.println(requestBody);
return "success";
}
类似于我们用User类来封装网页表单传过来的一个User对象的数据
RequestEntity 是一种请求报文的类型,作为形参使用 【我们得到的是当前的整个请求报文】
编写我们的请求表单:
<!--测试请求报文-->
<form th:action="@{/testRequestEntity}" method="post">
<input type="text" name="username" >
<input type="text" name="password" >
<input type="submit" value="测试RequestEntity" >
</form>
编写对应的控制器方法:
// 封装整个请求报文
@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
// 打印一下请求头和请求体
System.out.println(requestEntity.getHeaders());
System.out.println(requestEntity.getBody());
return "success";
}
<a th:href="@{/testResponseBody}">通过@ResponseBody响应浏览器数据</a><br>
// 通过注解响应浏览器数据
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "通过注解响应到浏览器中的数据";
}
是否还有其他方式响应浏览器数据呢?
<a th:href="@{/testResponse}">通过ServletAPI响应浏览器数据</a><br>
@RequestMapping("/testResponse")
public void testResponse(HttpServletResponse response) throws IOException {
response.getWriter().println("hello, response");
}
上面我们是将一个字符串作为响应报文的数据输出到浏览器页面,如果是一个我们封装的Java对象可以吗?
首先我们需要准备一个 pojo 类,此处我们以 User 类为例
package com.atguigu.mvc.bean;
/**
* @author Bonbons
* @version 1.0
*/
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String sex;
public User() {
}
public User(Integer id, String username, String password, Integer age, String sex) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
this.sex = sex;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
准备一个发起请求的超链接
<a th:href="@{/testResponseUser}">通过@ResponseBody响应浏览器User对象</a><br>
编写控制器处理方法
// 测试返回的是一个对象
@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
return new User(1, "LiHua", "10086", 28, "男");
}
运行 tomcat 测试一下
❌ 我们发现不能直接输出复杂类型,那么我们可以将其转化为 json 对象,然后进一步输出
那么我们应该怎么做才能将 Java 对象转换为 json 对象呢?
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
重新运行,再次点击超链接测试【因为我使用了CSDN的插件】
json 是一种数据交互格式,分为 json 对象和 json 数组两种
对于 mvc 注解驱动有很多作用,当前我们接触到的有如下这些:
SpringMVC 为我们提供了一个复合注解 @RestController 注解,用在控制器类名上
编写我们的请求代码
<!--发送一个ajax请求-->
<div id="app">
<!--通过@click绑定点击事件-->
<a @click="testAxios" th:href="@{/testAxios}">SpringMVC处理Ajax</a>
</div>
<!--引入我们的js文件-->
<script type="text/javascript" th:src="@{/static/js/vue.js}"/>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"/>
<script type="text/javascript">
new Vue({
el:"#app", // 对应我们连接的id
methods:{
testAxios:function (event){ // event 代表我们当前的事件
axios({
// 指定请求方式
method : "post",
// 请求地址
url : event.target.href,
// 请求参数
params: {
username : "admin",
password : "123456"
}
}).then(function (response){
alert(response.data);
})
// 取消超链接的默认行为
event.preventDefault();
}
}
});
</script>
编写我们的控制器方法
// Ajax 测试
@RequestMapping("/testAxios")
@ResponseBody
public String testAxios(String username, String password){
System.out.println(username + ", " + password);
return "hello, Ajax";
}
运行测试
文件下载:从服务器端下载到浏览器端;文件上传:从浏览器端上传到我们服务器端 [在底层都是一个文件复制的过程]
在 SpringMVC 中,我们通过 ResponseEntity 作为控制器方法的返回值,来下载文件
在 SpringMVC 中,我们想要上传文件需要配置对应依赖,表单发起post请求,编码方式得设置成二进制
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
有个小细节,springMVC 是根据 id 去查找Bean的,,我们声明的这个类是 MultipartResolver 接口的一个实现类
接下来演示如何实现在 Web 项目中进行文件的上传与下载:
我们准备一个单独的页面,只用来发起文件上传和下载的请求,先通过一个超链接点击发起下载文件的请求
<a th:href="@{/testDown}">下载我们服务器端的1.png</a>
新建一个控制器类:FileUpAndDownController,提供两个控制器方法,先给出下载文件的方法
// 文件下载
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象 [当前的整个工程]
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/1.png");
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组 [available方法是可以得到输入流中的字节数]
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字 [attachment代表以附件的方式下载]
headers.add("Content-Disposition", "attachment;filename=1.png");
//设置响应状态码 [OK 代表200]
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象 [响应体、响应头、响应状态码]
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
无论是文件上传还是下载,都要先获取到 Session,然后进一步获取到 ServletContext
既然要下载文件,肯定需要找到文件的位置,通过 getRealPath 方法将我们文件的路径传进去
我们要把文件中的内容都转化成字节发送到客户端,所以根据我们的路径创建输入流,然后将流读入字符数组中
因为我们传递响应报文不是只有响应体,还需要有响应头和响应状态码
最后将响应报文封装到 ResponseEntity 的对象中,通过 return 响应到浏览器中 【状态栏会有下载成功的提示】
测试一下,因为我使用特殊的下载工具,所以提示不一样
这部分代码可以当做模板直接使用,我们只需要修改一下文件名和请求提示就可以了
对于文件上传,我们需要配置需要的依赖
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
上传文件要求发起的必须是 post 请求,而且需要指定为二进制编码
<!--文件上传只能是post请求方式-->
<!--enctype属性规定了表单数据发送到服务器之前如何进行编码-->
<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
头像 <input type="file" name="photo"><br>
<input type="submit" value="上传">
</form>
在 SpringMVC 的核心配置文件中
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
接着我们看一下文件上传的控制器方法应该怎么写 ?
// 文件上传
@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
// 我们上传文件的文件名
String fileName = photo.getOriginalFilename();
// 获取文件名的后缀
String suffixName = fileName.substring(fileName.lastIndexOf("."));
// 上传之后的文件,我们使用UUID作为其文件名[通用唯一标识码,解决了文件重名覆盖问题]
String uuid = UUID.randomUUID().toString().replace("-", "");
// 将uuid和后缀拼接到一起,就是服务器中的文件名
fileName = uuid + suffixName;
// 通过session获取整个应用
ServletContext servletContext = session.getServletContext();
// 获取我们保存资源的路径
String photoPath = servletContext.getRealPath("photo");
// 获取整个路径下的文件 [主要我们用来判断是否有这个文件夹]
File file = new File(photoPath);
// 判断photoPath这个路径是否存在
if(!file.exists()){
// 不存在就去创建对应的文件夹
file.mkdir();
}
// 创建我们最终的文件完整路径
String realPath = photoPath + File.separator + fileName;
// 将浏览器中的资源转移到我们的服务器中 [会有一个IO流的异常,我们直接抛出]
photo.transferTo(new File(realPath));
return "success";
}
返回值类型为String而且没使用@ResponseBody注解、返回值无前缀,所以我们返回的是视图名【成功上传跳转页面】
两个参数,第一个用来完成携带数据的封装,需要特定的配置(上面在核心文件中的配置),第二个获取 Session
此处我们给出的是直接解决了文件重名的问题,采用 UUID 代替我们上传的文件名,当然文件后缀不能变
需要注意,如果文件夹不存在我们需要创建一个文件夹,Maven工程文件实际保存到target目录下了
最后,将浏览器中的资源转移到我们指定的路径中
什么是拦截器?
过滤器也可以拦截请求,那么拦截器有什么特别的地方吗?
接下来我们编写一个拦截器,演示拦截器具体是如何配置的?
☀️ 编写一个拦截器类 FirstInterceptor
package com.atguigu.mvc.interceptors;
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;
/**
* @author Bonbons
* @version 1.0
*/
@Component
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor --> preHandle");
// return HandlerInterceptor.super.preHandle(request, response, handler);
// 返回true代表放行,返回false代表拦截
// return false;
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor --> postHandle");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor --> afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
☀️ 拦截器要想被我们SpringMVC识别到,需要进行配置
<!--配置拦截器-->
<mvc:interceptors>
<!--通过bean说明某一个类型的对象就是一个拦截器-->
<bean class="com.atguigu.mvc.interceptors.FirstInterceptor"/>
<ref bean="firstInterceptor"/>
<mvc:interceptor>
<!--代表对当前上下文路径上的请求进行拦截-->
<!--如果想拦截所有请求,那么使用 /** -->
/*
代表当前上下文路径下的所有请求/**
代表所有请求☀️ 配置好拦截器我们可以运行测试一下 【此时测试preHandler选择的是false >> 拦截方法】
上面我们演示的是一个拦截器,如果存在多个拦截器,那么执行起来的效果是怎么样的呢?
首先,我们需要再准备一个拦截器 SecondInterceptor
package com.atguigu.mvc.interceptors;
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;
/**
* @author Bonbons
* @version 1.0
*/
@Component
public class SecondInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("SecondInterceptor --> preHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("SecondInterceptor --> postHandle");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("SecondInterceptor --> afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
接着在核心配置文件中配置一下拦截器,为了方便我们不设置拦截规则,采用 ref 声明拦截器
<mvc:interceptors>
<ref bean="firstInterceptor"/>
<ref bean="secondInterceptor" />
</mvc:interceptors>
运行测试一下:
我们对照源码,看一下为什么是这样的一个输出顺序?
因为我们上面这两个拦截器都是放行状态,如果将第二个拦截器设置为拦截状态,那么会输出哪些内容呢?
实际上SpringMVC中还有一个默认的拦截器
存在多个拦截器,只要有一个拦截器的 preHandle 为 false,那么
异常处理器是SpringMVC中为我们提供的一种异常处理机制,我们可以自定义异常处理
主要分为两种方式进行配置:通过配置文件配置、通过注解配置
异常处理调用的是doResolveException方法,返回值为ModelAndView
捕获到我们预处理的异常后,就会根据我们的配置跳转到指定的页面,同时可以将错误信息添加到请求域中,在页面显示
就是我们在SpringMVC的核心配置文件中配置异常处理解析器,来对配置我们如何处理异常
需要我们准备一个发生异常后的跳转页面,我们就输出一句话和打印异常报告
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
出现异常
<p th:text="${ex}"/>
</body>
</html>
设置一个发起异常测试请求的超链接
<a th:href="@{/testException}">测试异常处理</a>
我们模拟一个数学运算异常,在对应的控制器方法中添加异常
// 测试数学运算异常
@RequestMapping("/testException")
public String testException(){
System.out.println(1/0);
return "success";
}
在SpringMVC的核心配置文件中,配置异常解析器
<!--配置异常解析器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--key为异常的全限定类名-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--将异常信息添加到request请求域中-->
<property name="exceptionAttribute" value="ex" />
</bean>
运行测试一下
我们在额外定义一个控制器,专门完成各种异常处理 ExceptionController
在控制类上面,我们使用的不是 @Controller而是@ControllerAdvice注解,这个注解有三个作用
编写我们的控制器和要测试的异常处理方法
package com.atguigu.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* @author Bonbons
* @version 1.0
*/
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = {
// 我们要处理的异常
ArithmeticException.class,
NullPointerException.class
})
/**
* Exception ex 参数代表当前这个异常
* 使用Model model 形参为了将异常信息添加到请求域中
*/
public String testException(Exception ex, Model model){
model.addAttribute("ex", ex);
return "error";
}
}
此时我们就可以直接进行测试了,我们在一个控制器方法模拟一下空指针异常
String s = null;
s.length();
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口 HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有: