目录
4、为什么不用手动接收 JSON 字符串、转换 JSON 字符串
Ajax 请求的前端发送有很多种方式,可以使用 Vue,也可以使用 JQuery。
下面是一个服务器获取请求头中的信息,并响应一个字符串的简单 Ajax 示例。
(1)index.html
使用 Ajax 请求传递两个请求参数,并接收服务器端的响应数据,格式为 text。
- html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>Titletitle>
- <script type="text/javascript" th:src="@{/static/js/JQuery-3.7.0.js}">script>
- <script type="text/javascript">
- $(function() {
- $("#ajaxButton").click(function() {
- $.ajax({
- url:"/Demo_Rest/testAjax",
- data: { // 在请求头
- "username": "admin",
- "password": "123456",
- },
- type: "get",
- dataType: "text",
- success: function(data) {
- alert(data);
- }
- });
- });
- });
- script>
- head>
- <body>
- <h1>index 页面h1>
- <input id="ajaxButton" type="button" value="发起 ajax 请求"/>
- body>
- html>
(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,其实没必要导入,但是可以写上。
- <dependency>
- <groupId>com.fasterxml.jackson.coregroupId>
- <artifactId>jackson-databindartifactId>
- <version>2.15.2version>
- dependency>
(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 格式的数据时:
- <script type="text/javascript">
- $(function() {
- $("#ajaxButton").click(function() {
- var user = {
- username: "admin",
- password: "123456",
- age: 12,
- };
- $.ajax({
- url:"/Demo_Rest/testAjax",
- data: JSON.stringify(user),
- contentType: "application/json",
- type: "post",
- dataType: "text", /* 返回数据类型 */
- success: function(data) {
- alert(data);
- }
- });
- });
- });
- script>
@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
- <script type="text/javascript">
- $(function() {
- $("#ajaxButton").click(function() {
- var user = {
- username: "admin",
- password: "123456",
- age: 12,
- };
- $.ajax({
- url:"/Demo_Rest/testResponseBody",
- data: JSON.stringify(user),
- contentType: "application/json",
- type: "post",
- dataType: "json", /* 返回数据类型 */
- success: function(data) {
- alert(data.username);
- }
- });
- });
- });
- script>
(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 的注解驱动:
<mvc:annotation-driven/>
此时在 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<byte[]> 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<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
- // 10.关闭输入流
- inputStream.close();
- return responseEntity;
- }
(2)index.html
<a th:href="@{/fileDownload}">下载R-C.jpg文件a>
(3)效果

(1)如何获取上传的文件信息
由于 MultipartResolver 是一个接口,我们需要配置它的实现类 CommonsMulitipartResolver。
- <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
-
- bean>
- <dependency>
- <groupId>commons-fileuploadgroupId>
- <artifactId>commons-fileuploadartifactId>
- <version>1.5version>
- dependency>
(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
- <form th:action="@{/fileUpload}" method="post" enctype="multipart/form-data">
- 头像:<input type="file" name="multipartFile"/> <br/>
- <input type="submit" value="上传"/>
- form>
(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 配置文件
- <mvc:interceptors>
- <bean class="com.demo.interceptor.FirstInterceptor">bean>
- mvc:interceptors>
(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)直接嵌套
- <mvc:interceptors>
- <bean class="com.demo.interceptor.FirstInterceptor">bean>
- mvc:interceptors>
(2)链接外部
- <bean id="firstInterceptor" class="com.demo.interceptor.FirstInterceptor"/>
- <mvc:interceptors>
- <ref bean="firstInterceptor"/>
- mvc:interceptors>
(3)扫描注解方式
- @Component(value = "firstInterceptor")
- public class FirstInterceptor implements HandlerInterceptor {
-
- }
- <mvc:interceptors>
- <ref bean="firstInterceptor"/>
- mvc:interceptors>
(4)
它有两个重要的属性:
设置了这两个请求之后,就会按照设置的内容进行拦截,而不是去拦截 DispatcherServlet。
- <mvc:interceptors>
- <mvc:interceptor>
- <mvc:mapping path="/*"/>
- <mvc:exclude-mapping path="/static"/>
- <ref bean="firstInterceptor"/>
- mvc:interceptor>
- mvc:interceptors>
还需要注意,path = " /* ",表示的是,拦截 / 后的一层目录的请求,也就是说 /abc/* 这类请求是无法拦截的。
想要拦截对所有资源的请求,又要求使用 mvc:mapping,那么可以写 path = " /** "。
我们设置两个拦截器:FirstInterceptor、SecondInterceptor。将他们的 preHandler 都设置为放行(true)。
(1)在 SpringMVC 中作如下顺序
- <mvc:interceptors>
- <ref bean="firstInterceptor"/>
- <ref bean="secondInterceptor"/>
- mvc:interceptors>
(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
- <body>
- <a th:href="@{/testException}">测试异常处理a>
- body>
- <body>
- <h1>error666 页面h1>
- body>
(3)配置异常解析器的 View
- <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
- <property name="exceptionMappings">
- <props>
- <prop key="java.lang.ArithmeticException">error666prop>
-
- props>
- property>
- bean>
(4)运行结果
刚才的示例中,只有 View 的功能,现在我们要添加 Model 的功能。
(1)SpringMVC 配置文件
- <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
- <property name="exceptionMappings">
- <props>
- <prop key="java.lang.ArithmeticException">error666prop>
-
- props>
- property>
-
- <property name="exceptionAttribute" value="exceptionInfo"/>
-
- bean>
(2)error666.html
- <body>
- <h1>error666 页面h1>
- <p th:text="${exceptionInfo}">p>
- body>
(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)运行结果
