目录
九、SpringMVC 中的 AJAX 请求
1、简单示例
2、@RequestBody(重点关注“赋值形式”)
3、@ResponseBody(经常用)
4、为什么不用手动接收 JSON 字符串、转换 JSON 字符串
5、@RestController
十、文件上传与下载
1、ResponseEntity
2、文件下载
3、文件上传
4、解决文件重名导致内容覆盖的问题
十一、拦截器
1、拦截器的三个方法
2、拦截器的简单示例
3、拦截器的配置方法
4、多个拦截器的执行顺序
5、preHandle 返回 false 的情况
十二、控制器方法异常处理
1、异常处理解析器
2、使用 xml 配置
3、显示域对象的数据
4、使用注解配置
Ajax 请求的前端发送有很多种方式,可以使用 Vue,也可以使用 JQuery。
下面是一个服务器获取请求头中的信息,并响应一个字符串的简单 Ajax 示例。
(1)index.html
使用 Ajax 请求传递两个请求参数,并接收服务器端的响应数据,格式为 text。
Title
index 页面
(2)java 代码
注意,因为 Ajax 请求是做局部更新,因此不需要进行转发,也不需要重定向,返回类型是用 void 即可。
@RequestMapping(value = "/testAjax")
public void testAjax(String username, String password, HttpServletResponse resp) throws IOException {
System.out.println("username: " + username + ", password: " + password);
resp.getWriter().write("testAjax");
return;
}
@RequestBody 可以获取请求体,在控制器方法设置一个形参,使用 @RequestBody 进行标识,当前请求的请求体就会为当前注解所标识的形参赋值。
需要注意的是,使用了 @RequestBody 后,Ajax 就要发送 POST 请求,而不能发送 GET 请求。可见:https://juejin.cn/post/7222833868503236667
(1)导入依赖 jackson
Spring 默认的 json 解析器就是 jackson,其实没必要导入,但是可以写上。
com.fasterxml.jackson.core
jackson-databind
2.15.2
(2)@RequestBody 的赋值形式
当 @RequestBody 修饰一个方法参数时,会将所有的请求参数都赋值给方法参数。什么意思呢?
假设请求体中的请求参数为 username : "admin", password : "123456", age : 12:
关于 @RequestBody 与 @RequestParam 的区别 以及 是否添加 @RequestBody 注解,这两个问题也很重要。
可以参考:https://blog.csdn.net/weixin_43606226/article/details/106545024
(3)使用注意
仅对使用 JQuery 的情况而言,其他方法(比如:vue)不需要注意。
当我们传递 JSON 格式的数据时:
@ResponseBody 用于标识一个控制器方法,可以将该方法的返回值(String、实体类)直接作为响应体响应到浏览器。
一般情况下,就是将一个 JSON 格式的 json 字符串,响应到客户端,再由客户端浏览器将其解析成 json 对象。而使用了 @ResponseBody 和 @RequestBody 之后,就不是这样的了,具体后面解释。
(1)返回普通字符串的情况
@RequestMapping(value = "/testResponseBody")
@ResponseBody
public String responseBody() {
return "success";
}
访问 /testResponseBody,那么就会出现如下结果:
而这显然不是我们之前所编写的 success.html 页面,而仅仅是一个字符串数据。
(2)返回实体类的情况
将 user 的信息发送给服务器,服务器修改 username 后,响应新的 user 信息。
(2-1)index.html
(2-2)Java 代码
@RequestMapping(value = "/testResponseBody")
@ResponseBody
public User responseBody(@RequestBody User user) {
System.out.println(user);
user.setUsername("wyt");
return user;
}
(3)常见的 Java 对象转换为 Json 之后的类型
在 SpringMVC 的核心配置文件中开启 mvc 的注解驱动:
此时在 HandlerAdaptor 中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,它是由 JackSon 这个 JSON 解析器提供的,其作用有:
实际开发中,@ResponseBody 使用的情况非常多,而我们只需要给控制类写上一个 @RestController,就可以同时起到 @Controller 和 @ResponseBody 的作用。
文件上传与下载的方式有很多,不一定要用 ResponseEntity 这种方法。
ResponseEntity 是一个类型,用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。
有关文件上传与下载的步骤,可以查看:https://blog.csdn.net/joyride_run/article/details/132814877
在此最奉上喜欢的图片(R-C.jpg):
(1)Java 代码
@RequestMapping(value = "/fileDownload", method = RequestMethod.GET)
public ResponseEntity testResponseEntity(HttpSession session) throws IOException {
// 1.获取要下载的文件名
String downloadFilename = "R-C.jpg";
// 2.获取ServletContext对象
ServletContext servletContext = session.getServletContext();
// 3.获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/" + downloadFilename);
// 4.创建输入流
InputStream inputStream = new FileInputStream(realPath);
// 5.创建字节数组
int count = 0;
while (count == 0) count = inputStream.available();// 防止数据未送达,导致count=0,但是本地读文件一般不会有这种错误
byte[] bytes = new byte[count];
// 6.将流读到字节数组中
inputStream.read(bytes);
// 7.创建HttpHeaders对象设置响应头信息
MultiValueMap headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=" + downloadFilename);
// 8.设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
// 9.创建ResponseEntity对象
ResponseEntity responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
// 10.关闭输入流
inputStream.close();
return responseEntity;
}
(2)index.html
下载R-C.jpg文件
(3)效果
(1)如何获取上传的文件信息
由于 MultipartResolver 是一个接口,我们需要配置它的实现类 CommonsMulitipartResolver。
commons-fileupload
commons-fileupload
1.5
(2)输出文件名
(2-1)Java 代码
@RequestMapping(value = "/fileUpload", method = RequestMethod.POST)
public String upload(MultipartFile multipartFile) {
String filename = multipartFile.getOriginalFilename();
System.out.println(filename);
return "success";
}
(2-2)index.html
(3)保存文件
@RequestMapping(value = "/fileUpload", method = RequestMethod.POST)
public String upload(MultipartFile multipartFile, HttpSession session) throws IOException {
// 1.获取上传的文件名
String filename = multipartFile.getOriginalFilename();
// 2.获取文件保存的路径
ServletContext servletContext = session.getServletContext();
String dir = servletContext.getRealPath("/static/img/upload");
// 3.创建文件对象
File file = new File(dir);
if (!file.exists()) {
file.mkdirs(); // mkdir 只能创建下一级目录
}
// 4.保存文件
String savePath = dir + File.separator + filename;
System.out.println(savePath);
multipartFile.transferTo(new File(savePath));
return "success";
}
解决方法:
(1)使用 UUID
String filename = multipartFile.getOriginalFilename();
String extensionName = filename.substring(filename.lastIndexOf("."));
filename = UUID.randomUUID().toString() + extensionName;
SpringMVC 中的拦截器用于拦截控制器方法的执行。作用类似于 Filter,但不是完全相同。
默认情况下,未指定对哪些资源进行拦截,则会拦截所有交给 DispatcherServlet 的请求,比如:对 index 的访问,对 controller 的访问。
SpringMVC 中的拦截器有三个抽象方法:
SpringMVC 中的拦截器需要实现 HandlerInterceptor,并且在 SpringMVC 的配置文件中进行配置:
(1)SpringMVC 配置文件
(2)FirstInterceptor.java
package com.demo.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 FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor -- preHandle");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor -- postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor -- afterCompletion");
}
}
(3)运行结果
访问 index.html,观察页面响应结果和控制台输出:
(1)直接嵌套
(2)链接外部
(3)扫描注解方式
@Component(value = "firstInterceptor")
public class FirstInterceptor implements HandlerInterceptor {
}
(4)
它有两个重要的属性:
设置了这两个请求之后,就会按照设置的内容进行拦截,而不是去拦截 DispatcherServlet。
还需要注意,path = " /* ",表示的是,拦截 / 后的一层目录的请求,也就是说 /abc/* 这类请求是无法拦截的。
想要拦截对所有资源的请求,又要求使用 mvc:mapping,那么可以写 path = " /** "。
我们设置两个拦截器:FirstInterceptor、SecondInterceptor。将他们的 preHandler 都设置为放行(true)。
(1)在 SpringMVC 中作如下顺序
(2)访问 index.html,观察输出
显然可以看出,执行 preHandle() 方法,是按照配置顺序来执行;执行另外 2 个方法,是按照配置的逆序来执行。
当拦截器 C 的 preHandle() 返回了 false:
对于异常处理解析器,我们可以配置,也可以不配置,SpringMVC 已经默认使用了解析器。
在他们的底层实现中,会返回一个 ModelAndView,这代表着我们可以利用 Model 将错误信息保存到 Request 域,还可以利用 View 进行页面跳转。比如:跳转至 error404.html 页面,并显示错误信息。
(1)Java 代码
@RequestMapping(value = "/testException")
public String testException() {
int i = 1 / 0;
return "success";
}
(2)index.html 以及 error666.html
测试异常处理
error666 页面
(3)配置异常解析器的 View
error666
(4)运行结果
刚才的示例中,只有 View 的功能,现在我们要添加 Model 的功能。
(1)SpringMVC 配置文件
error666
(2)error666.html
error666 页面
(3)输出结果
(1)@ControllerAdvice
(2)@ExceptionHandler
(3)返回值
(4)Controller
@RequestMapping(value = "/testException")
public String testException() {
int i = 1 / 0;
return "success";
}
(5)ExceptionHandler
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = {ArithmeticException.class})
public String handleException(Model model, Throwable exception) {
model.addAttribute("exceptionInfo", exception);
return "error666";
}
}
(6)运行结果