Spring MVC JSON 数据交互(熟悉)
Spring MVC 在传递数据时,通常都需要对数据的类型和格式进行转换。而这些数据不仅可以常见的 String 类型,还可以是 JSON 等其他类型。
JSON 是近些年一种比较流行的数据格式,它与 XML 相似,也是用来存储数据的。但相较于 XML,JSON 数据占用的空间更小,解析速度更快。因此,使用 JSON 数据进行前后台的数据交互也是一种十分常见的手段。
JSON 概述
JSON(JavaScript Object Notation,JS 对象标记)是一种轻量级的数据交互格式。与 XML 一样,JSON 也是一种基于纯文本的数据格式。通过它,我们不仅能够传递 String、Number、Boolean 等简单类型的数据,还可以传递数组、Object 对象等复杂类型的数据。
JSON 支持 2 种数据结构,它们分别是对象结构和数组结构。
1. 对象结构
JSON 的对象结构以“{”开始,以“}”结束,中间则由 0 个或多个以英文的逗号(即“,”)分隔的 key/value 对构成。
对象结构的语法结构如下。
{ key1:value1, key2:value2, ... }
其中,key 必须为 String 类型,value 可以是 String、Number、Object、Array 等数据类型。
例如,一个 person 对象包含姓名、密码、年龄等信息,使用 JSON 的表示形式如下:
{ "pname":"张三", "password":"123456", "page":40 }
2. 数组结构
JSON 的数组结构以“[”开始、以“]”结束,中间部分由 0 个或多个以英文的逗号(即“,”)分隔的值列表组成。
数组结构的语法结构如下:
{ value1, value2, ... }
例如,一个数组中包含了 String、Number、Boolean、null 多种类型的数据,使用 JSON 的数组结构表示形式如下。
[ "c语言中文网", 123456789, true, null ]
上述两种(对象、数组)数据结构也可以分别组合构成更加复杂的数据结构。
例如,一个 student 对象包含 sno、sname、hobby 和 college 等多个属性,其 JSON 表示形式如下。
{ "sno":"201802228888", "sname":"张三", "hobby":[ "篮球", "足球" ], "college":{ "cname":"清华大学", "city":"北京", "code":100000 } }
JSON 数据转换
为了实现浏览器与控制器类之间的 JSON 数据交互,Spring MVC 提供了一个默认的 MappingJackson2HttpMessageConverter 类,来处理 JSON 格式请求和响应。通过它,我们既可以将 Java 对象转换为 JSON 数据,也可以将 JSON 数据转换为 Java 对象。
引入依赖包
想要使用 MappingJackson2HttpMessageConverter 对 JSON 数据进行转换,第一步就是要将 Jackson 的依赖包引入到 Spring MVC 项目中。
jackson-annotations-x.x.x.jar:JSON 转换的注解包
jackson-core-x.x.x.jar:JSON 转换的核心包
jackson-databind-x.x.x.jar:JSON 转换的数据绑定包
以上这些 Jackson 的开源依赖包都可以通过“https://mvnrepository.com/artifact/com.fasterxml.jackson.core”下载得到。
JSON 转换注解
Spring MVC 为我们提供了两个十分重要的与 JSON 格式转换相关的注解,它们分别是 @RequestBody 和 @ResponseBody。
注解 |
位置 |
说明 |
@RequestBody |
方法的形参上 |
注解实现接收http请求json数据,将json转换为java对象。 |
@ResponseBody |
方法上 |
将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。 |
@ResponseBody注解的作用和原理
作用
将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。
当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。
当方法上面写ResponseBody,如果返回值是字符串,那么直接将字符串写到客户端;
当方法上面写ResponseBody,如果是一个对象,会将对象转化为json串,然后写到客户端。
这里需要注意的是,如果返回对象,按utf-8编码。
如果返回String,默认按iso8859-1编码,页面可能出现乱码。因此在注解中我们可以手动修改编码格式,例如@RequestMapping(value="/cat/query",produces="text/html;charset=utf-8"),前面是请求的路径,后面是编码格式。
原理
其实是通过HttpMessageConverter中的方法实现的,它本是一个接口,在其实现类完成转换。【Converter是转换器】
如果是bean对象,会调用对象的getXXX()方法获取属性值并且以键值对的形式进行封装,进而转化为json串。
代码演示:http://c.biancheng.net/spring_mvc/9679.html
Spring MVC 拦截器 (熟悉)
截器(Interceptor)是 Spring MVC 提供的一种强大的功能组件。它可以对用户请求进行拦截,并在请求进入控制器(Controller)之前、控制器处理完请求后、甚至是渲染视图后,执行一些指定的操作。
在 Spring MVC 中,拦截器的作用与 Servlet 中的过滤器类似,它主要用于拦截用户请求并做相应的处理,例如通过拦截器,我们可以执行权限验证、记录请求信息日志、判断用户是否已登录等操作。
Spring MVC 拦截器使用的是可插拔式的设计,如果我们需要某一拦截器,只需在配置文件中启用该拦截器即可;如果不需要这个拦截器,则只要在配置文件中取消应用该拦截器即可。
定义拦截器
想要在 Spring MVC 项目中使用拦截器,第一步就是要对拦截器类进行定义。
Spring MVC 在 org.springframework.web.servlet 包中提供了一个 HandlerInterceptor 接口,该接口包含 3 个方法,如下表。
我们可以通过实现 HandlerInterceptor 接口,重写其方法,来实现对拦截器类的定义,示例代码如下。
package net.biancheng.c.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 执行"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle 执行"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion 执行"); } }
配置拦截器
在定义完拦截器后,我们还需要在 Spring MVC 的配置文件中使用 <mvc:interceptors> 标签及其子标签对拦截器进行配置,这样这个拦截器才会生效。
在 Spring MVC 的配置文件中,<mvc:interceptors> 标签用于定义一组拦截器,其包含多个常用的子标签,具体说明如下表。
在了解完 <mvc:interceptors> 标签及其子标签的含义后,接下来我们就来讲解如何通过它们来对拦截器进行定义。
1. 通过 <bean> 子标签配置全局拦截器
我们可以在 Spring MVC 的配置文件中,通过 <mvc:interceptors> 标签及其子标签 <bean> ,将我们自定义的拦截器配置成了一个全局拦截器。该拦截器会对项目内所有的请求进行拦截,配置代码如下。
<!--配置拦截器--> <mvc:interceptors> <bean class="net.biancheng.c.interceptor.MyInterceptor"></bean> </mvc:interceptors>
2. 通过 <ref> 子标签配置全局拦截器
除了 <bean> 标签外,我们还可以在 <mvc:interceptors> 标签中通过子标签 <ref> 定义一个全局拦截器引用,对所有的请求进行拦截。
<!--将自定义的拦截器放到 ioc 容器中--> <bean id="interceptor" class="net.biancheng.c.interceptor.MyInterceptor"></bean> <!--配置拦截器--> <mvc:interceptors> <!--通过 ref 配置全局拦截器--> <ref bean="interceptor"></ref> </mvc:interceptors>
注意:<mvc:interceptors> 标签的 <ref> 子标签不能单独使用,它需要与 <bean> 标签(<mvc:interceptors> 标签内或<mvc:interceptors>标签外)或 @Component 等注解配合使用,以保证 <ref> 标签配置的拦截器是Spring IOC 容器中的组件。
3. 通过<mvc:interceptor>子标签对拦截路径进行配置
我们还可以在 Spring MVC 的配置文件中通过 <mvc:interceptors> 标签的子标签 <mvc:interceptor>,对拦截器拦截的请求路径进行配置,示例配置如下。
<!--配置拦截器--> <mvc:interceptors> <!--拦截器 1--> <mvc:interceptor> <!--配置拦截器拦截的请求路径--> <mvc:mapping path="/**"/> <!--配置拦截器不需要拦截的请求路径--> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/"/> <!--定义在 <mvc:interceptors> 下,表示拦截器只对指定路径的请求进行拦截--> <bean class="net.biancheng.c.interceptor.MyInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
需要注意的是,在 <mvc:interceptor> 中,子元素必须按照上述代码的配置顺序进行编写,即 <mvc:mapping> → <mvc:exclude-mapping> → <bean> 的顺序,否则就会报错。其次,以上这三种配置拦截器的方式,我们可以根据自身的需求以任意的组合方式进行配置,以实现在 <mvc:interceptors> 标签中定义多个拦截器的目的。
拦截器的执行流程
拦截器的执行流程如下图所示。
图1:单个拦截器处理流程
拦截器处理流程的步骤如下:
1.当请求的路径与拦截器拦截的路径相匹配时,程序会先执行拦截器类(MyInterceptor)的 preHandle() 方法。若该方法返回值为 true,则继续向下执行 Controller(控制器)中的方法,否则将不再向下执行;
2.控制器方法对请求进行处理;
3.调用拦截器的 postHandle() 方法,此时我们可以对请求域中的模型(Model)数据和视图做出进一步的修改;
4.通过 DispatcherServlet 的 render() 方法对视图进行渲染;
5.调用拦截器的 afterCompletion () 方法,完成资源清理、日志记录等工作。
*多个拦截器的执行流程
在大型的企业级项目中,通常都不会只有一个拦截器,开发人员可能会定义许多不同的拦截器来实现不同的功能。在程序运行期间,拦截器的执行是有一定的顺序的,该顺序与拦截器在配置文件中定义的顺序有关。
假设一个项目中包含两个不同的拦截器:Interceptor1 和 Interceptor2,它们在配置文件中定义的顺序为:Interceptor1 → Interceptor2。下面我们就通过一个拦截器流程图来描述下多个拦截器的执行流程。
图3:多个拦截器的执行流程
从上面的执行流程图可以看出,当存在多个拦截器同时工作时,它们的 preHandle() 方法会按照拦截器在配置文件中的配置顺序执行,但它们的 PostHandle() 和 afterCompletion() 方法则会按照配置顺序的反序执行。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--开启组件扫描--> <context:component-scan base-package="net.biancheng.c"></context:component-scan> <!-- 配置 Thymeleaf 视图解析器 --> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="order" value="1"/> <property name="characterEncoding" value="UTF-8"/> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀 --> <property name="prefix" value="/WEB-INF/templates/"/> <!-- 视图后缀 --> <property name="suffix" value=".html"/> <property name="templateMode" value="HTML5"/> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> <!-- <!– view-name:设置请求地址所对应的视图名称–>--> <mvc:view-controller path="/hello" view-name="success"></mvc:view-controller> <!-- 当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签--> <mvc:annotation-driven></mvc:annotation-driven> <!-- <!–静态资源映射–>--> <mvc:default-servlet-handler/> <!--配置拦截器--> <mvc:interceptors> <!--拦截器 1--> <mvc:interceptor> <!--配置拦截器拦截的请求路径--> <mvc:mapping path="/**"/> <!--配置拦截器不需要拦截的请求路径--> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/"/> <!--定义在 <mvc:interceptors> 下,表示拦截器只对指定路径的请求进行拦截--> <bean class="net.biancheng.c.interceptor.MyInterceptor"></bean> </mvc:interceptor> <!--拦截器 MyInterceptor2--> <mvc:interceptor> <!--配置拦截器拦截的请求路径--> <mvc:mapping path="/**"/> <!--配置拦截器不需要拦截的请求路径--> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/"/> <!--定义在 <mvc:interceptors> 下,表示拦截器只对指定路径的请求进行拦截--> <bean class="net.biancheng.c.interceptor.MyInterceptor2"></bean> </mvc:interceptor> <!--拦截器 MyInterceptor3--> <mvc:interceptor> <!--配置拦截器拦截的请求路径--> <mvc:mapping path="/**"/> <!--配置拦截器不需要拦截的请求路径--> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/"/> <!--定义在 <mvc:interceptors> 下,表示拦截器只对指定路径的请求进行拦截--> <bean class="net.biancheng.c.interceptor.MyInterceptor3"></bean> </mvc:interceptor> </mvc:interceptors> </beans>
从控制台输出的内容可知:
由于这三个拦截器在 Spring MVC 的配置文件中是按照 MyInterceptor → MyInterceptor2 → MyInterceptor3 的顺序配置的,因此这三个拦截器的执行顺序也是 MyInterceptor → MyInterceptor2 → MyInterceptor3;
由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此 MyInterceptor3 和它之前的拦截器 MyInterceptor 和 MyInterceptor2 中的 preHandle() 都会执行。
由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此 MyInterceptor、MyInterceptor2 和 MyInterceptor3 的 postHandle() 方法都不会执行。
由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此只有 MyInterceptor 和 MyInterceptor2 的 afterCompletion() 方法执行了。
Spring MVC 数据校验 (了解)
数据校验是每个项目中必不可少的模块,Spring MVC 提供了两种数据校验的组件:
- 基于 Validator 接口进行校验
- 使用 Annotation JSR-303 标准校验
使用基于 Validator 接口进行校验会复杂一些,具体的数据校验的规则需要开发者手动设置。而使用 Annotation JSR-303 标准会相对简单一些,开发者不需要编写校验规则,直接通过注解的形式给每一条数据添加校验规则,具体操作是直接在实体类的属性上添加对应的校验注解即可。
好文参考:https://blog.csdn.net/dingd1234/article/details/122793424
Spring MVC 异常处理 (熟悉)
在实际的应用开发中,经常会不可避免地遇到各种可预知的、不可预知的异常,此时我们就需要对这些异常处理,以保证程序正常运行。
Spring MVC 提供了一个名为 HandlerExceptionResolver 的异常处理器接口,它可以对控制器方法执行过程中出现的各种异常进行处理。
Srping MVC 为 HandlerExceptionResolver 接口提供了多个不同的实现类,其中最常用的实现类如下。
DefaultHandlerExceptionResolver
ResponseStatusExceptionResolver
ExceptionHandlerExceptionResolver
SimpleMappingExceptionResolver
其中,ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver 和 DefaultHandlerExceptionResolver 是 Spring MVC 的默认异常处理器。
如果程序发生异常,Spring MVC 会按照 ExceptionHandlerExceptionResolver → ResponseStatusExceptionResolver → DefaultHandlerExceptionResolver 的顺序,依次使用这三个异常处理器对异常进行解析,直到完成对异常的解析工作为止。
DefaultHandlerExceptionResolver
DefaultHandlerExceptionResolver 是 HandlerExceptionResolver 接口的常用实现类之一,更是 Spring MVC 提供的默认异常处理器之一,Spring MVC 默认通过它对控制器处理请求时出现的异常进行处理。
DefaultHandlerExceptionResolver 提供了一个 doResolveException() 方法,其返回类型为 ModelAndView。该方法会在控制器方法出现指定异常时,生成一个新的包含了异常信息的 ModelAndView 对象替换控制器方法的 ModelAndView 对象,以达到跳转到指定的错误页面,展示异常信息的目的,其部分源码如下。
@Nullable protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { if (ex instanceof HttpRequestMethodNotSupportedException) { return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler); } if (ex instanceof HttpMediaTypeNotSupportedException) { return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler); } if (ex instanceof HttpMediaTypeNotAcceptableException) { return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler); } …… } catch (Exception var6) { if (this.logger.isWarnEnabled()) { this.logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", var6); } } return null; }
从上面的代码可以看出,DefaultHandlerExceptionResolver 的 doResolveException() 方法可以将 Spring MVC 产生的各种异常转换为合适的状态码(code)。通过这些状态码,我们就可以进一步的确定发生异常的原因,以便于找到对应的问题。
下表中列举了 Spring MVC 中一些常见异常的默认状态码
ResponseStatusExceptionResolver
ResponseStatusExceptionResolver 也是 HandlerExceptionResolver 的实现类之一。与 DefaultHandlerExceptionResolver 一样,ResponseStatusExceptionResolver 也是 Spring MVC 提供的默认异常处理器之一,它被用来解析 @ResponseStatus 注解标注的自定义异常,并把异常的状态信息返回给客户端展示。
@ResponseStatus 注解
@ResponseStatus 注解主要用来标注在自定义的异常类上,示例代码如下。
package net.biancheng.c.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "自定义异常") public class UserNotExistException extends RuntimeException { }
如果程序运行时发生了这个自定义的异常,Spring MVC 就会通过 ResponseStatusExceptionResolver 对该异常进行解析,并将异常信息展示到错误页上。
@ResponseStatus 注解包含了三个属性,如下表。