相关文章推荐
留胡子的鼠标  ·  使用 Migrate.exe - EF6 ...·  1 月前    · 
礼貌的香菇  ·  How to replace ...·  1 年前    · 
稳重的八宝粥  ·  visual studio ...·  1 年前    · 

1. JSON 数据交互

Spring MVC 在数据绑定的过程中需要对传递数据的格式和类型进行转换,它既可以转换 String 等类型的数据,也可以转换 JSON 等其他类型的数据。

1) JSON 概述

JSON(JavaScript Object Notation, JS 对象标记)是一种轻量级的数据交换格式。与 XML 一样,JSON 也是基于纯文本的数据格式。它有对象结构和数组结构两种数据结构。

(1) 对象结构

{ key: value, key2: value2, ... }

其中,key 必须为 String 类型,value 可以是 String、Number、Object、Array 等数据类型。

(2) 数组结构


[ value, value2, ... ]

上述两种(对象、数组)数据结构也可以相互内嵌,组合构成更加复杂的数据结构。

2) JSON 数据转换

为实现浏览器与控制器类之间的 JSON 数据交互,Spring MVC 提供了 MappingJackson2HttpMessageConverter 实现类默认处理 JSON 格式请求响应。该实现类利用 Jackson 开源包读写 JSON 数据,将 Java 对象转换为 JSON 对象和 XML 文档,同时也可以将 JSON 对象和 XML 文档转换为 Java 对象。

在使用注解开发时需要用到两个重要的 JSON 格式转换注解,分别是 @RequestBody 和 @ResponseBody。

@RequestBody:用于将请求体中的数据绑定到方法的形参中,该注解应用在方法的形参上。
@ResponseBody:用于直接返回 return 对象,该注解应用在方法上。

需要注意的是,在该处理方法上,除了通过 @RequestMapping 指定请求的 URL,还有一个 @ResponseBody 注解。该注解的作用是将标注该注解的处理方法的返回结果直接写入 HTTP Response Body(Response 对象的 body 数据区)中。

一般情况下,@ResponseBody 都会在异步获取数据时使用,被其标注的处理方法返回的数据都将输出到响应流中,客户端获取并显示数据。

@ResponseBody 和 @Controller 配合使用,或者使用 @RestController,@RestController 相当于 @Controller + @ResponseBody。

3) 各种 JSON 技术比较

早期 JSON 的组装和解析都是通过手动编写代码来实现的,这种方式效率不高,所以后来有许多的关于组装和解析 JSON 格式信息的工具类出现,如 json-lib、Jackson、Gson 和 FastJson 等,可以解决 JSON 交互的开发效率。

(1) json-lib

json-lib 最早也是应用广泛的 JSON 解析工具,缺点是依赖很多的第三方包,如 commons-beanutils.jar、commons-collections-3.2.jar、commons-lang-2.6.jar、commons-logging-1.1.1.jar、ezmorph-1.0.6.jar 等。

对于复杂类型的转换,json-lib 在将 JSON 转换成 Bean 时还有缺陷,比如一个类里包含另一个类的 List 或者 Map 集合,json-lib 从 JSON 到 Bean 的转换就会出现问题。

所以 json-lib 在功能和性能上面都不能满足现在互联网化的需求。

(2) 开源的 Jackson

开源的 Jackson 是 Spring MVC 内置的 JSON 转换工具。相比 json-lib 框架,Jackson 所依赖 jar 文件较少,简单易用并且性能也要相对高些。并且 Jackson 社区相对比较活跃,更新速度也比较快。

但是 Jackson 对于复杂类型的 JSON 转换 Bean 会出现问题,一些集合 Map、List 的转换出现问题。而 Jackson 对于复杂类型的 Bean 转换 JSON,转换的 JSON 格式不是标准的 JSON 格式。

(3) Google 的 Gson

Gson 是目前功能最全的 JSON 解析神器,Gson 当初是应 Google 公司内部需求由 Google 自行研发。自从在 2008 年 5 月公开发布第一版后,Gson 就已经被许多公司或用户应用。

Gson 主要提供了 toJson 与 fromJson 两个转换函数,不需要依赖其它的 jar 文件,就能直接在 JDK 上运行。在使用这两个函数转换之前,需要先创建好对象的类型以及其成员才能成功的将 JSON 字符串转换为相对应的对象。

类里面只要有 get 和 set 方法,Gson 完全可以将复杂类型的 JSON 到 Bean 或 Bean 到 JSON 的转换,是 JSON 解析的神器。Gson 在功能上面无可挑剔,但性能比 FastJson 有所差距。

(4) 阿里巴巴的 FastJson

FastJson 是用 Java 语言编写的高性能 JSON 处理器,由阿里巴巴公司开发。

FastJson 不需要依赖其它的 jar 文件,就能直接在 JDK 上运行。

FastJson 在复杂类型的 Bean 转换 JSON 上会出现一些问题,可能会出现引用的类型,导致 JSON 转换出错,需要制定引用。

FastJson 采用独创的算法,将 parse 的速度提升到极致,超过所有 JSON 库。

综上 4 种 JSON 技术的比较,在项目选型的时候可以使用 Google 的 Gson 和阿里巴巴的 FastJson 两种并行使用,如果只是功能要求,没有性能要求,可以使用Google 的 Gson。如果有性能上面的要求可以使用 Gson 将 Bean 转换 JSON 确保数据的正确,使用 FastJson 将 JSON 转换 Bean。

示例

在 “ Spring基础知识(12)- Spring MVC (二) ” 的示例里,更新过 springmvc-beans.xml 的 SpringmvcBasic 项目基础上,修改如下。

(1) 导入 jackson、fastjson 依赖包

访问 http://www.mvnrepository.com/,查询 jackson、fastjson

修改 pom.xml:

 1                 <project ... >
 2                     ...
 4                     <dependencies>
 6                         ...
 8                         <!-- jackson -->
 9                         <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
10                         <dependency>
11                             <groupId>com.fasterxml.jackson.core</groupId>
12                             <artifactId>jackson-databind</artifactId>
13                             <version>2.7.2</version>
14                         </dependency>
15                         <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
16                         <dependency>
17                             <groupId>com.fasterxml.jackson.core</groupId>
18                             <artifactId>jackson-core</artifactId>
19                             <version>2.7.2</version>
20                         </dependency>
21                         <!-- fastjson -->
22                         <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
23                         <dependency>
24                             <groupId>com.alibaba</groupId>
25                             <artifactId>fastjson</artifactId>
26                             <version>1.2.79</version>
27                         </dependency>
29                         ...
31                     </dependencies>
33                     ...    
35                 </project>


在IDE中项目列表 -> SrpingmvcBasic -> 点击鼠标右键 -> Maven -> Reload Project

(2) 创建 src/main/java/com/example/entity/User.java 文件

 1             package com.example.entity;
 3             public class User {
 4                 private int id;
 5                 private String username;
 6                 private String password;
 7                 private String email;
 8                 private Date createDate;
10                 public User() {
12                 }
14                 public int getId() {
15                     return id;
16                 }
18                 public void setId(int id) {
19                     this.id = id;
20                 }
22                 public String getUsername() {
23                     return this.username;
24                 }
26                 public void setUsername(String username) {
27                     this.username = username;
28                 }
30                 public String getPassword() {
31                     return password;
32                 }
34                 public void setPassword(String password) {
35                     this.password = password;
36                 }
38                 public String getEmail() {
39                     return email;
40                 }
42                 public void setEmail(String email) {
43                     this.email = email;
44                 }               
46                 public Date getCreateDate() {
47                     return createDate;
48                 }
50                 public void setCreateDate(Date createDate) {
51                     this.createDate = createDate;
52                 }


        (3) 创建 src/main/java/com/example/controller/JsonController.java 文件

 1             package com.example.controller;
 3             import java.util.Date;
 4             import java.util.List;
 5             import java.util.Map;
 6             import java.util.HashMap;
 7             import java.util.ArrayList;
 9             import org.springframework.http.MediaType;
10             import org.springframework.stereotype.Controller;
11             import org.springframework.web.bind.annotation.RequestMapping;
12             import org.springframework.web.bind.annotation.RequestMethod;
13             import org.springframework.web.bind.annotation.ResponseBody;
15             import com.example.entity.User;
17             @Controller
18             @RequestMapping("/json")
19             public class JsonController {
21                 @RequestMapping(value="/text",
22                                 method = RequestMethod.GET)
23                 @ResponseBody
24                 public String getText() {
25                     return "JSON text/html";
26                 }
28                 @RequestMapping(value="/object",
29                                 method = RequestMethod.GET,
30                                 produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
31                 @ResponseBody
32                 public User getObject() {
33                     User user = new User();
34                     user.setUsername("json-object");
35                     user.setPassword("123456");
36                     user.setEmail("test@spring.com");
37                     user.setCreateDate(new Date());
38                     return user;
39                 }
41                 @RequestMapping(value="/map",
42                                 method = RequestMethod.GET,
43                                 produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
44                 @ResponseBody
45                 public Map<String, Object> getMap() {
46                     Map<String, Object> map = new HashMap<>();
47                     map.put("username", "json-map");
48                     map.put("password", "789123");
49                     map.put("email", "test@spring.com");
50                     map.put("createDate", new Date());
51                     return map;
52                 }
54                 @RequestMapping(value="/array",
55                                 method = RequestMethod.GET,
56                                 produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
57                 @ResponseBody
58                 public User[] getArray() {
59                     User user1 = new User();
60                     user1.setUsername("json-array1");
61                     user1.setPassword("123456");
62                     user1.setEmail("test1@spring.com");
63                     user1.setCreateDate(new Date());
65                     User user2 = new User();
66                     user2.setUsername("json-array2");
67                     user2.setPassword("456789");
68                     user2.setEmail("test2@spring.com");
69                     user2.setCreateDate(new Date());
71                     return new User[] {user1, user2};
72                 }
74                 @RequestMapping(value="/list",
75                                 method = RequestMethod.GET,
76                                 produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
77                 @ResponseBody
78                 public List<User> getList() {
79                     List<User> userList = new ArrayList<>();
81                     User user1 = new User();
82                     user1.setUsername("json-list1");
83                     user1.setPassword("123456");
84                     user1.setEmail("test1@spring.com");
85                     user1.setCreateDate(new Date());
86                     userList.add(user1);
88                     User user2 = new User();
89                     user2.setUsername("json-list2");
90                     user2.setPassword("456789");
91                     user2.setEmail("test2@spring.com");
92                     user2.setCreateDate(new Date());
93                     userList.add(user2);
95                     return userList;
96                 }


            或使用 @RestController

 1                 package com.example.controller;
 3                 ...
 5                 import org.springframework.web.bind.annotation.RestController;
 7                 @RestController  // 相当于 @Controller + @ResponseBody
 8                 @RequestMapping("/json")
 9                 public class JsonController {
11                     @RequestMapping(value="/text",
12                                     method = RequestMethod.GET)
13                     public String getText() {
14                         ...
15                     }
17                     @RequestMapping(value="/object",
18                                     method = RequestMethod.GET,
19                                     produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
20                     //@ResponseBody
21                     public User getObject() {
22                         ...
23                     }
25                     @RequestMapping(value="/map",
26                                     method = RequestMethod.GET,
27                                     produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
28                     //@ResponseBody
29                     public Map<String, Object> getMap() {
30                         ...
31                     }
33                     @RequestMapping(value="/array",
34                                     method = RequestMethod.GET,
35                                     produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
36                     //@ResponseBody
37                     public User[] getArray() {
38                         ...
39                     }
41                     @RequestMapping(value="/list",
42                                     method = RequestMethod.GET,
43                                     produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
44                     public List<User> getList() {
45                         ...
46                     }


            默认情况下 @ResponseBody 由 jackson 解析,如果要切换到 fastjson 解析,需要修改 springmvc-beans.xml 文件。
            
            配置 @ResponseBody 由 fastjson 解析,修改 springmvc-beans.xml 文件如下:

1                 <mvc:annotation-driven>
2                     <!-- 配置 @ResponseBody 由 fastjson 解析 -->
3                     <mvc:message-converters>
4                         <bean class="org.springframework.http.converter.StringHttpMessageConverter">
5                             <property name="defaultCharset" value="UTF-8" />
6                         </bean>
7                         <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4" />
8                     </mvc:message-converters>
9                 </mvc:annotation-driven>


        访问 http://localhost:9090/json/xxxx


2. 异常处理

    在 Spring MVC 应用的开发中,不管是操作底层数据库,还是业务层或控制层,都会不可避免地遇到各种可预知的、不可预知的异常。我们需要捕捉处理异常,才能保证程序不被终止。

    Spring MVC 有以下 3 种处理异常的方式:

        (1) 使用 @ExceptionHandler 注解实现异常处理;
        (2) 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver;
        (3) 实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器;


    1) @ExceptionHandler

        局部异常处理仅能处理指定 Controller 中的异常。

        示例
        
            在 “Spring基础知识(12)- Spring MVC (二)” 的示例里,更新过 springmvc-beans.xml 的 SpringmvcBasic 项目基础上,修改如下。

            (1) 创建 src/main/webapp/WEB-INF/jsp/error.jsp 文件

 1                 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
 2                 <html>
 3                 <head>
 4                     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                     <title>Error</title>
 6                 </head>
 7                 <body>
 8                     <h3>Error Page</h3>
 9                     <p>&nbsp;</p>
10                     <p>Error: ${errorMsg}</p>
11                 </body>
12                 </html>


            (2) 创建 src/main/java/com/example/controller/ExceptionController.java 文件

 1                 package com.example.controller;
 3                 import org.springframework.stereotype.Controller;
 4                 import org.springframework.ui.Model;
 5                 import org.springframework.web.bind.annotation.RequestMapping;
 6                 import org.springframework.web.bind.annotation.RequestParam;
 7                 import org.springframework.web.bind.annotation.ExceptionHandler;
 9                 @Controller
10                 @RequestMapping("/exception")
11                 public class ExceptionController {
13                     @RequestMapping("/test")
14                     public String test(@RequestParam("i") Integer i, Model model) {
15                         // 当 i=0 时会产生算术运算异常
16                         double d = 1 / i;
17                         model.addAttribute("message", "d = " + d);
18                         return "success";
19                     }
21                     // 注意:该注解不是加在产生异常的方法上,而是加在处理异常的方法上。
22                     @ExceptionHandler({ ArithmeticException.class })
23                     public String testArithmeticException(Exception e, Model model) {
24                         System.out.println("ExceptionController -> testArithmeticException() : e = " + e);
26                         model.addAttribute("errorMsg", e.getMessage());
27                         return "error";
28                     }

      
            访问 http://localhost:9090/exception/test?i=0

            页面显示:

                Error Page

                Error: / by zero

            控制台显示:

                ExceptionController -> testArithmeticException(): e = java.lang.ArithmeticException: / by zero
        
        使用局部异常处理,仅能处理某个 Controller 中的异常,若需要对所有异常进行统一处理,可使用以下两种方法。


    2) SimpleMappingExceptionResolver

        全局异常处理可使用 SimpleMappingExceptionResolver 来实现。它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。

        示例
        
            在 “Spring基础知识(12)- Spring MVC (二)” 的示例里,更新过 springmvc-beans.xml 的 SpringmvcBasic 项目基础上,修改如下。

            (1) 创建 src/main/webapp/WEB-INF/jsp/error2.jsp 文件

 1                 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
 2                 <html>
 3                 <head>
 4                     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                     <title>Error2</title>
 6                 </head>
 7                 <body>
 8                     <h3>Error2 Page</h3>
 9                     <p>&nbsp;</p>
10                     <p>Error: ${err.getMessage()}</p>
11                 </body>
12                 </html>


            (2) 在 springmvc-beans.xml 中配置全局异常,代码如下。

 1                 <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
 2                     <!-- 定义默认的异常处理页面,当该异常类型注册时使用 -->
 3                     <property name="defaultErrorView" value="error2"></property>
 5                     <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为 exception -->
 6                     <property name="exceptionAttribute" value="err"></property>
 8                     <!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常页名作为值 -->
 9                     <property name="exceptionMappings">
10                         <props>
11                             <prop key="ArithmeticException">error2</prop>
13                             <!-- 其它异常类型的处理 -->
14                                 ...
15                         </props>
16                     </property>
17                 </bean>


            访问 http://localhost:9090/exception/test?i=0

                Error2 Page
                
                Error: / by zero


    3) HandlerExceptionResolver

        实现 HandlerExceptionResolver 接口,自定义自己的异常处理器来处理全局异常。

        Spring MVC 通过 HandlerExceptionResolver 处理程序异常,包括处理器异常、数据绑定异常以及控制器执行时发生的异常。HandlerExceptionResolver 仅有一个接口方法,源码如下。

            public interface HandlerExceptionResolver {
                @Nullable
                ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
            }

        发生异常时,Spring MVC 会调用 resolveException() 方法,并转到 ModelAndView 对应的视图中,返回一个异常报告页面反馈给用户。

        示例


        在 “Spring基础知识(12)- Spring MVC (二)” 的示例里,更新过 springmvc-beans.xml 的 SpringmvcBasic 项目基础上,修改如下。


            (1) 创建 src/main/webapp/WEB-INF/jsp/error3.jsp 文件

 1                 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
 2                 <html>
 3                 <head>
 4                     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                     <title>Error3</title>
 6                 </head>
 7                 <body>
 8                     <h3>Error3 Page</h3>
 9                     <p>&nbsp;</p>
10                     <p>Error: ${errorMsg}</p>
11                 </body>
12                 </html>


            (2) 创建 src/main/java/com/example/controller/CustomHandlerExceptionResolver.java 文件

 1                 package com.example.exception;
 3                 import java.util.HashMap;
 4                 import java.util.Map;
 5                 import javax.servlet.http.HttpServletRequest;
 6                 import javax.servlet.http.HttpServletResponse;
 7                 import org.springframework.web.servlet.HandlerExceptionResolver;
 8                 import org.springframework.web.servlet.ModelAndView;
10                 public class CustomHandlerExceptionResolver implements HandlerExceptionResolver {
11                     @Override
12                     public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
13                                                         Object object, Exception e) {
14                         Map<String, Object> model = new HashMap<String, Object>();
16                         if (e instanceof ArithmeticException) {
17                             model.put("errorMsg", e.getMessage());
18                             return new ModelAndView("error3", model);
19                         }
21                         return new ModelAndView("error", model);
22                     }


            (3) 在 springmvc-beans.xml 文件中添加以下代码

                <!-- 配置 HandlerExceptionResolver  -->
                <bean class="com.example.exception.CustomHandlerExceptionResolver"/>    

            访问 http://localhost:9090/exception/test?i=0

            页面显示:

                Error3 Page
                
                Error: / by zero