[Spring] SpringMVC 简介(三)

目录

[九、SpringMVC 中的 AJAX 请求](#九、SpringMVC 中的 AJAX 请求)

1、简单示例

2、@RequestBody(重点关注"赋值形式")

3、@ResponseBody(经常用)

[4、为什么不用手动接收 JSON 字符串、转换 JSON 字符串](#4、为什么不用手动接收 JSON 字符串、转换 JSON 字符串)

5、@RestController

十、文件上传与下载

1、ResponseEntity

2、文件下载

3、文件上传

4、解决文件重名导致内容覆盖的问题

十一、拦截器

1、拦截器的三个方法

2、拦截器的简单示例

3、拦截器的配置方法

4、多个拦截器的执行顺序

[5、preHandle 返回 false 的情况](#5、preHandle 返回 false 的情况)

十二、控制器方法异常处理

1、异常处理解析器

[2、使用 xml 配置](#2、使用 xml 配置)

3、显示域对象的数据

4、使用注解配置


九、SpringMVC 中的 AJAX 请求

Ajax 请求的前端发送有很多种方式,可以使用 Vue,也可以使用 JQuery。

1、简单示例

下面是一个服务器获取请求头中的信息,并响应一个字符串的简单 Ajax 示例。

(1)index.html

使用 Ajax 请求传递两个请求参数,并接收服务器端的响应数据,格式为 text。

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <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 即可。

java 复制代码
@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;
}

2、@RequestBody(重点关注"赋值形式")

@RequestBody 可以获取请求体,在控制器方法设置一个形参,使用 @RequestBody 进行标识,当前请求的请求体就会为当前注解所标识的形参赋值。

需要注意的是,使用了 @RequestBody 后,Ajax 就要发送 POST 请求,而不能发送 GET 请求 。可见:https://juejin.cn/post/7222833868503236667

(1)导入依赖 jackson

Spring 默认的 json 解析器就是 jackson,其实没必要导入,但是可以写上。

XML 复制代码
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

(2)@RequestBody 的赋值形式

当 @RequestBody 修饰一个方法参数时,会将所有的请求参数都赋值给方法参数。什么意思呢?

假设请求体中的请求参数username : "admin", password : "123456", age : 12

  • 若方法参数为 @RequestBody String user
  • 若方法参数为 @RequestBody User user
  • 若方法参数为 @RequestBody Map<String, Object> map

关于 @RequestBody 与 @RequestParam 的区别 以及 是否添加 @RequestBody 注解,这两个问题也很重要。

可以参考:https://blog.csdn.net/weixin_43606226/article/details/106545024

(3)使用注意

仅对使用 JQuery 的情况而言,其他方法(比如:vue)不需要注意。

当我们传递 JSON 格式的数据时:

  • 需要添加 contentType: "application/json";
  • 需要使用 JSON.stringify(data);
html 复制代码
<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>

3、@ResponseBody(经常用)

@ResponseBody 用于标识一个控制器方法,可以将该方法的返回值(String、实体类)直接作为响应体响应到浏览器。

一般情况下,就是将一个 JSON 格式的 json 字符串,响应到客户端,再由客户端浏览器将其解析成 json 对象。而使用了 @ResponseBody 和 @RequestBody 之后,就不是这样的了,具体后面解释。

(1)返回普通字符串的情况

java 复制代码
@RequestMapping(value = "/testResponseBody")
@ResponseBody
public String responseBody() {
    return "success";
}

访问 /testResponseBody,那么就会出现如下结果:

而这显然不是我们之前所编写的 success.html 页面,而仅仅是一个字符串数据。

(2)返回实体类的情况

将 user 的信息发送给服务器,服务器修改 username 后,响应新的 user 信息。

(2-1)index.html

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 代码

java 复制代码
@RequestMapping(value = "/testResponseBody")
@ResponseBody
public User responseBody(@RequestBody User user) {
    System.out.println(user);
    user.setUsername("wyt");
    return user;
}

(3)常见的 Java 对象转换为 Json 之后的类型

  • 实体类:Json 对象;
  • Map:Json 对象;
  • List:Json 数组;

4、为什么不用手动接收 JSON 字符串、转换 JSON 字符串

在 SpringMVC 的核心配置文件中开启 mvc 的注解驱动:

XML 复制代码
<mvc:annotation-driven/>

此时在 HandlerAdaptor 中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,它是由 JackSon 这个 JSON 解析器提供的,其作用有:

  • 既可以将浏览器传递的请求参数构成的 JSON 字符串转换为 JSON 对象
  • 也可以将响应到浏览器的 Java 对象转换为 JSON 格式的字符串

5、@RestController

实际开发中,@ResponseBody 使用的情况非常多,而我们只需要给控制类写上一个 @RestController,就可以同时起到 @Controller 和 @ResponseBody 的作用。

十、文件上传与下载

文件上传与下载的方式有很多,不一定要用 ResponseEntity 这种方法。

1、ResponseEntity

ResponseEntity 是一个类型,用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。

有关文件上传与下载的步骤,可以查看:https://blog.csdn.net/joyride_run/article/details/132814877

2、文件下载

在此最奉上喜欢的图片(R-C.jpg):

(1)Java 代码

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<String, String> 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

html 复制代码
<a th:href="@{/fileDownload}">下载R-C.jpg文件</a>

(3)效果

3、文件上传

(1)如何获取上传的文件信息

  • SpringMVC 将上传的文件封装到 MultipartFile 对象中,通过此对象可以获取文件相关信息
  • 使用 MultipartFile 对象需要配置文件上传解析器
  • 使用 MultipartFile 对象需要引入依赖 commons-fileupload

由于 MultipartResolver 是一个接口,我们需要配置它的实现类 CommonsMulitipartResolver。

XML 复制代码
<!-- 配置文件上传解析器
    id 必须为 multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

</bean>
XML 复制代码
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>

(2)输出文件名

  • 使用 MultipartFile 的类方法 getOriginalFilename(),可以获取上传文件的文件名。

(2-1)Java 代码

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

  • 这里有一个很重要的点,就是表单上传文件的 name 的属性值必须要与 MultipartFile 的参数名相同。
html 复制代码
<form th:action="@{/fileUpload}" method="post" enctype="multipart/form-data">
    头像:<input type="file" name="multipartFile"/> <br/>
    <input type="submit" value="上传"/>
</form>

(3)保存文件

  • 保存文件使用 SpringMVC 封装的 transferTo() 方法,则不需要再使用 IO 流进行持久化存储;
  • 因为我们不知道目录之间的分隔符是 / 还是 \,所以可以使用 File.separator;
java 复制代码
@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";
}

4、解决文件重名导致内容覆盖的问题

解决方法:

  • 使用时间戳;
  • 使用 UUID;

(1)使用 UUID

java 复制代码
String filename = multipartFile.getOriginalFilename();
String extensionName = filename.substring(filename.lastIndexOf("."));
filename = UUID.randomUUID().toString() + extensionName;

十一、拦截器

SpringMVC 中的拦截器用于拦截控制器方法的执行。作用类似于 Filter,但不是完全相同。

默认情况下,未指定对哪些资源进行拦截,则会拦截所有交给 DispatcherServlet 的请求,比如:对 index 的访问,对 controller 的访问。

1、拦截器的三个方法

SpringMVC 中的拦截器有三个抽象方法:

  • preHandle:控制器方法执行之前执行,其返回值表示对控制器方法的放行(true)或拦截(false);
  • postHandle:控制器方法执行之后执行;
  • afterComplation:处理完视图和模型数据,渲染视图完毕之后执行;

2、拦截器的简单示例

SpringMVC 中的拦截器需要实现 HandlerInterceptor,并且在 SpringMVC 的配置文件中进行配置:

(1)SpringMVC 配置文件

  • 由 SpringMVC 加载,不需要写 id
XML 复制代码
<!-- 配置拦截器 -->
<mvc:interceptors>
    <bean class="com.demo.interceptor.FirstInterceptor"></bean>
</mvc:interceptors>

(2)FirstInterceptor.java

  • 重写三个方法,其中 preHandle 的返回 false
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,观察页面响应结果和控制台输出:

  • 首页无法访问,以及如下输出

3、拦截器的配置方法

(1)直接嵌套 <bean> 标签

  • 在 SpringMVC 的配置文件中直接写上:
XML 复制代码
<mvc:interceptors>
    <bean class="com.demo.interceptor.FirstInterceptor"></bean>
</mvc:interceptors>

(2)链接外部 <bean>

  • <ref> 链接 <mvc:interceptors> 外部的 <bean>
XML 复制代码
<bean id="firstInterceptor" class="com.demo.interceptor.FirstInterceptor"/>
<!-- 配置拦截器 -->
<mvc:interceptors>
    <ref bean="firstInterceptor"/>
</mvc:interceptors>

(3)扫描注解方式

  • 在 FirstInterceptor 上添加 @Component,然后直接 <ref>
java 复制代码
@Component(value = "firstInterceptor")
public class FirstInterceptor implements HandlerInterceptor {

}
XML 复制代码
<mvc:interceptors>
    <ref bean="firstInterceptor"/>
</mvc:interceptors>

(4)<mvc:interceptor>

它有两个重要的属性:

  • mvc:mapping path ="":表示需要拦截的请求;
  • mvc:exclude-mapping path="":表示不需要拦截的请求;
  • <ref> 和 <bean> 要写在这两个属性之后;

设置了这两个请求之后,就会按照设置的内容进行拦截,而不是去拦截 DispatcherServlet。

XML 复制代码
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/*"/>
        <mvc:exclude-mapping path="/static"/>
        <ref bean="firstInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

还需要注意,path = " /* ",表示的是,拦截 / 后的一层目录的请求,也就是说 /abc/* 这类请求是无法拦截的。

想要拦截对所有资源的请求,又要求使用 mvc:mapping,那么可以写 path = " /** "。

4、多个拦截器的执行顺序

我们设置两个拦截器:FirstInterceptor、SecondInterceptor。将他们的 preHandler 都设置为放行(true)。

(1)在 SpringMVC 中作如下顺序

  • 先配置 FirstInterceptor,后配置 SecondInterceptor。
XML 复制代码
<mvc:interceptors>
    <ref bean="firstInterceptor"/>
    <ref bean="secondInterceptor"/>
</mvc:interceptors>

(2)访问 index.html,观察输出

显然可以看出,执行 preHandle() 方法,是按照配置顺序来执行;执行另外 2 个方法,是按照配置的逆序来执行。

5、preHandle 返回 false 的情况

当拦截器 C 的 preHandle() 返回了 false:

  • C 以及 C 之前的拦截器的 preHandle() 都会执行;
  • 所有的 postHandle() 都不执行;
  • C 之前的拦截器的 afterComplation() 都会执行;

十二、控制器方法异常处理

1、异常处理解析器

对于异常处理解析器,我们可以配置,也可以不配置,SpringMVC 已经默认使用了解析器。

  • SpringMVC 提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver。
  • HandlerExceptionResolver 接口的实现类有:DefaultHandlerExceptionResolver(SpringMVC 默认使用)和 SimpleMappingExceptionResolver(用户自定义使用)。

在他们的底层实现中,会返回一个 ModelAndView,这代表着我们可以利用 Model 将错误信息保存到 Request 域,还可以利用 View 进行页面跳转。比如:跳转至 error404.html 页面,并显示错误信息。

2、使用 xml 配置

(1)Java 代码

  • 手动添加一个 1 / 0 的异常。
java 复制代码
@RequestMapping(value = "/testException")
public String testException() {
    int i = 1 / 0;
    return "success";
}

(2)index.html 以及 error666.html

  • 访问控制器方法
html 复制代码
<body>
    <a th:href="@{/testException}">测试异常处理</a>
</body>
  • 展示异常页面
html 复制代码
<body>
    <h1>error666 页面</h1>
</body>

(3)配置异常解析器的 View

  • prop 的 key 表示处理器方法执行过程中出现的异常;
  • prop的 value 表示若出现指定异常时,设置一个逻辑视图,跳转到指定页面;
XML 复制代码
<!-- 配置异常解析器 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.ArithmeticException">error666</prop>
            <!--
                .... 其他异常
            -->
        </props>
    </property>
</bean>

(4)运行结果

  • 由于 1 / 0 出现了异常,因此没有转发到 success.html 页面,而是来到了 error666.html 页面。

3、显示域对象的数据

刚才的示例中,只有 View 的功能,现在我们要添加 Model 的功能。

<property> 除了前面的 exceptionMappings,还有一个 exceptionAttribute,他们就是分别表示 View 和 Model:

  • 通过 exceptionAttribute 的 value 设置一个域数据的属性名,、即可通过这个属性名访问异常信息。

(1)SpringMVC 配置文件

  • 添加了 <property name="exceptionAttribute" value="exceptionInfo"/>
XML 复制代码
<!-- 配置异常解析器 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.ArithmeticException">error666</prop>
            <!--
                .... 其他异常
            -->
        </props>
    </property>

    <property name="exceptionAttribute" value="exceptionInfo"/>

</bean>

(2)error666.html

  • 获取域对象,显示异常信息
html 复制代码
<body>
    <h1>error666 页面</h1>
    <p th:text="${exceptionInfo}"></p>
</body>

(3)输出结果

4、使用注解配置

(1)@ControllerAdvice

  • @ControllerAdvice 将当前类标识为异常处理的 Controller。

(2)@ExceptionHandler

  • @ExceptionHandler 有一个 value 属性,需要继承自 Throwable 的异常类的 Class 数组作为属性值,其中传入我们需要处理的异常类的 class 属性。

(3)返回值

  • 如果使用 String,那么返回值可以设置成错误页面的逻辑视图;
  • 如果使用 ModelAndView,就使用 setView 方法设置逻辑视图;

(4)Controller

  • 产生一个 math 异常
java 复制代码
@RequestMapping(value = "/testException")
public String testException() {
    int i = 1 / 0;
    return "success";
}

(5)ExceptionHandler

  • 由于 Controller 有 math 方面的异常,因此用 ArithmeticException
java 复制代码
@ControllerAdvice
public class ExceptionController {
    @ExceptionHandler(value = {ArithmeticException.class})
    public String handleException(Model model, Throwable exception) {
        model.addAttribute("exceptionInfo", exception);
        return "error666";
    }
}

(6)运行结果

相关推荐
学习路漫长6 分钟前
Spring事务和事务传播机制
java·数据库·spring
nbsaas-boot23 分钟前
微服务下功能权限与数据权限的设计与实现
java·微服务·架构
丑到想整容...26 分钟前
微服务-nacos
java·微服务·架构
coderbu36 分钟前
学生请假管理系统
java
liuliuliuliuyujie1 小时前
Java 日志
java
拾木2001 小时前
同步io和异步io
java·开发语言·python
老马啸西风1 小时前
cross-plateform 跨平台应用程序-04-React Native 介绍
java
时差freebright1 小时前
【Visual Studio 报错】vs 在使用二进制写入文件时弹窗报错:使用简体中文 gb2312 编码加载文件
android·java·visual studio
东离与糖宝1 小时前
Rust 所有权 简介
开发语言·后端·rust
我的K84091 小时前
如何将java文件导入idea中运行和推送到Gitee仓库问题
java·idea