3.17.3 @ExceptionHandle注解

org.springframework.web.bind.annotation.ExceptionHandle注解的作用对象为方法,并且在运行时有效,value()可以指定异常类。
@ExceptionHandler注解的源代码如下:

1
2
3
4
5
6
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler{
Class<? extends Throwable>[] value() default {};
}

@ExceptionHandler注解的方法可以支持的参数除了HttpServletRequestHttpServletResponse等对象之外,还支持一个异常参数,包括一般的异常或自定义异常。
如果@ExceptionHandler注解没有指定异常类,会默认进行映射.

3.17.2 @ResponseStatus注解

org.springframework.web.bind.annotation.ExceptionHandle.ResponseStatus注解是处理异常最简单的方式,其可以修饰一个或者一个方法,当修饰一个类的时候,通常修饰的是一个异常类。
使用@ResponseStatus注解可指定如下表所示的属性

属性 类型 是否必要 说明
code HttpStatus http状态码,如Http.CREATEDHttpStatus.OK
value String code属性作用相同
reason HttpStatus 错误信息

使用时,先声明一个自定义异常类,在自定义异常类上面加上@ResponseStatus注解,就表示在系统运行期间,当抛出自定义异常的时候,使用@ResponseStatus注解中声明的value属性和reason属性将异常信息返回给客户端,提高可读性。

示例 @ResponseStatus处理异常

新建一个项目ResponseStatusTest,加入所需的jar文件,示例代码如下:

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>异常处理示例</title>
</head>
<body>
<a href="find">@ResponseStatus异常处理</a>
</body>
</html>

BookException.java

1
2
3
4
5
6
7
8
9
10
11
package org.fkjava.exception;

import org.springframework.web.bind.annotation.ResponseStatus;

// BookException是自定义异常类,使用了@ResponseStatus注解修饰,
// reason表示抛出异常时显示的错误信息,
@ResponseStatus(reason = "查询数据失败")
public class BookException extends RuntimeException
{
private static final long serialVersionUID = 1L;
}

BookException是自定义异常类,使用了@ResponseStatus注解修饰, reason表示抛出异常时显示的错误信息,此处没有使用value属性,表示适用于所有http状态码.

BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.fkit.controller;

import org.fkjava.exception.BookException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class BookController {
@GetMapping("/find")
public String find() throws Exception
{
try
{
@SuppressWarnings("unused")
int i = 5 / 0;
return "success";
} catch (Exception e)
{
throw new BookException();
}
}
}

find方法模拟了一个异常,发生异常时抛出自定义异常BookException
部署ResponseStatusTest这个Web应用,在浏览器中输入如下URL来测试应用,
单击"ResponseStatus异常处理“超链接,发送"find"请求,请求处理方法抛出自定义BookException异常,显示异常信息.

示例 SimpleMappingExceptionResolver处理异常

新建一个项目SimpleMappingExceptionResolverTest,加入所需的jar文件,示例代码如下:

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>异常处理示例</title>
</head>
<body>
<br>
<a href="hello">没有异常处理</a>
<br>
<a href="test">使用简单异常处理器处理异常</a>
<br>
<a href="find">使用简单异常处理器处理特定异常</a>
<br>
</body>
</html>

index.jsp中有3个超链接,分别测试没有异常处理有异常处理特定异常处理3种情况。

TestController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.fkit.controller;

import java.sql.SQLException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class TestController
{
@GetMapping("/hello")
public String hello() throws Exception
{
// 抛出异常
throw new Exception();
}
@GetMapping("/test")
public String test() throws Exception
{
// 模拟异常
@SuppressWarnings("unused")
int i = 5 / 0;
return "success";
}
@GetMapping("/find")
public String find() throws Exception
{
try
{
// 模拟异常
@SuppressWarnings("unused")
int i = 5 / 0;
return "success";
} catch (Exception e)
{
throw new SQLException("查找数据失败!");
}

}
}

TestController中有3个方法,分别对应index.jsp页面的3个请求:

  1. hello方法什么都没做,直接抛出一个异常。
  2. test方法模拟了一个除数不能为0异常。
  3. find方法模拟了一个除数不能为0异常之后,在catch块中抛出了一个SQLException异常。

部署SimpleMappingExceptionResolverTest这个Web应用,在浏览器中输入如下URL来测试应用:

1
http://localhost:8080/SimpleMappingExceptionResolverTest/

单击”没有异常处理“超链接,发送"hello"请求,此时没有异常处理程序,异常被直接抛给了浏览器.
异常被直接抛到浏览器,页面上显示一大堆错误堆栈信息,用户看到这些错误堆栈信息,往往都会一头雾水,抱怨这个设计实在太不友好。而且错误堆栈信息由于暴露了后台方法的调用关系,对应用来说这是存在一定潜在风险的。虽然在web.xml中可以配置处理异常的jsp页面,但这还是远远不够的。 Spring MVC对错误处理提供了更好的解决方案
接下来,在springmvc-config.xml中加入异常处理的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">


<!-- spring可以自动去扫描base-pack下面的包或者子包下面的java文件, -->
<!-- 如果扫描到有Spring的相关注解的类,则把这些类注册为Spring的bean -->
<context:component-scan
base-package="org.fkit.controller" />
<!-- 默认配置方案 -->
<mvc:annotation-driven />
<!-- 静态资源处理 -->
<mvc:default-servlet-handler />

<!-- 视图解析器 p:prefix属性表示前缀 p:suffix 表示后缀 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/content/" p:suffix=".jsp" />

<!-- p:defaultErrorView="error"表示所有没有指定的异常都跳转到异常处理页面error, -->
<!-- p:exceptionAttribute="ex"表示在异常处理页面中可以访问的异常对象变量名是ex。 -->
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"
p:defaultErrorView="error" p:exceptionAttribute="ex">

<!-- 异常映射 exceptionMappings表示映射的异常, -->
<!-- 接受参数是一个Properties key是异常类名,value是处理异常的页面 -->
<property name="exceptionMappings">
<props>
<prop key="IOException">ioerror</prop>
<prop key="SQLException">sqlerror</prop>
</props>
</property>
</bean>

</beans>

重点是异常处理的配置。 SimpleMappingExceptionResolverSpring提供的处理异常的类,所有抛岀的异常都会被该类捕获。

  • p:defaultErrorView="error"属性表示所有没有指定的异常都跳转到异常处理页面error
  • p:exceptionAttribute="ex"属性表示在异常处理页面中可以访问的异常对象变量名是ex
  • 如果需要为一些特定的异常指定异常处理页面,可以使用exceptionMappings属性,该属性接受的参数是一个Properties对象,:
    • key是异常类名或者包名加类名,
    • value是异常处理页面。

例如上面的配置指明,如果是IOException则跳转到ioerror页面,是SQLException则跳转到sqlerror页面,是其他异常则全部跳转到error页面,在所有异常页面中可以通过ex变量访问异常对象Exception

error.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>异常处理示例</title>
</head>
<body>
<h3>异常处理页面</h3>
抛出异常信息:${requestScope.ex.message}
</body>
</html>

sqlerror.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>异常处理示例</title>
</head>
<body>
<h3>特定异常处理页面</h3>
抛出异常信息:${requestScope.ex.message}
</body>
</html>

再次运行SimpleMappingExceptionResolverTest这个Web应用,在浏览器中输入如下URL来测试应用:

1
http://localhost:8080/SimpleMappingExceptionResolverTest/

单击”使用简单异常处理器处理异常“超链接,发送"test"请求抛出的异常被SimpleMappingExceptionResolver捕获,转发到异常处理页面error.jsp
单击”使用简单异常处理器处理特定异常“超链接,发送"find"请求,请求处理方法抛出的是SQLException异常,被SimpleMappingExceptionResolver捕获,转发到异常处理页面sqlerror.jsp

3.17.1 Spring MVC异常处理接口以及实现类

HandlerExceptionResolver接口

HandlerExceptionResolverSpring3.0之后新增的一个重要接口,负责Spring MVC的异常处理.
该接口只有一个方法签名:

1
2
3
4
5
6
ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response,
@Nullable java.lang.Object handler,
java.lang.Exception ex
)

resolveException方法处理程序执行期间被抛出的异常,返回一个模型和视图,视图通常是一个特定的错误处理页面。

AbstractHandlerExceptionResolver抽象类

AbstractHandlerExceptionResolver抽象类实现了HandlerExceptionResolver接口,重写了HandlerExceptionResolver接口的resolveException方法用于处理异常.

AbstractHandlerMethodExceptionResolver抽象类

AbstractHandlerMethodExceptionResolver抽象类继承了AbstractHandlerExceptionResolver抽象类,该类主要就是为HandlerMethod类服务,即handler参数是HandlerMethod类型。

ExceptionHandlerExceptionResolver类

ExceptionHandlerExceptionResolver类继承自AbstractHandlerMethodExceptionResolver,该类主要处理Controller中使用@ExceptionHandler注解的方法和@ControllerAdvice注解定义的类。该类也是<mvc:annotation- driven/>配置中定义的HandlerExceptionResolver实现类之一,大多数异常处理都由该类操作

SimpleMappingExceptionResolver类

SimpleMappingExceptionResolver继承自AbstractHandlerExceptionResolver抽象类,SimpleMappingExceptionResolver是一个根据配置来解析异常的类,包括异常类型、默认的错误视图、默认的响应码及异常映射等配置属性。

两个实现类的用途

因此,在Spring MVC中进行异常处理,

  • 如果选择XML配置,则使用SimpleMappingExceptionResolver类;
  • 如果使用@ExceptionHandler注解和@ControllerAdvice注解,则由ExceptionHandlerExceptionResolver类进行处理。

提示:SimpleMappingExceptionResolverExceptionHandlerExceptionResolver这两种异常处理方式在项目中不能共存,在开发中通常根据实际情况来选择。

3.17 Spring MVC的异常处理

使用try catch处理异常

任何成熟的MVC框架都应该提供异常处理机制,当然可以在Controller的请求处理方法中手动使用try...catch块捕捉异常,当捕捉到特定异常时,返回特定逻辑视图名,但这种处理方式非常烦琐,需要在请求处理方法中书写大量的catch块。最大的缺点还在于异常处理与代码耦合,一旦需要改变异常处理方式,必须修改大量代码!这是一种相当糟糕的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class XxxController
{
@RequestMapping("/hello")
public String hello()
{
try{
...
} catch(异常1 e)
{
return 结果1;
} catch(异常2 e)
{
return 结果2;
}
}
}

Spring MVC提供的异常处理方式

Spring MVC中提供的异常处理方式有两种:
(1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver
(2)使用@ExceptionHandler注解实现局部异常处理或使用@Controlleradvice注解实现统一异常处理。

3.16 @RestController注解

用途

org.springframework.web.bind.annotation.RestController注解本身使用@Controller@ResponseBody注解。使用了@RestController注解的类会被看作一个Controller,并且该类中所有使用@RequestMapping注解的方法都默认使用了@ResponseBody注解。

源码

@RestController注解的源代码如下:

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController
@AliasFor(annotation=Controller.class)
String value() default "";
}

示例 @RestController注解的使用

创建一个RestControllerTest项目,所有文件和配置基本和3.14节的ResponseBodyTest项目一致。

项目结构

展开/折叠
G:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\03\RestControllerTest
├─src\
│ └─org\
│   └─fkit\
│     ├─controller\
│     │ └─BookController.java
│     └─domain\
│       └─Book.java
└─WebContent\
  ├─index.jsp
  ├─js\
  │ ├─jquery-1.11.0.min.js
  │ ├─jquery-migrate-1.2.1.min.js
  │ └─json2.js
  ├─META-INF\
  │ └─MANIFEST.MF
  └─WEB-INF\
    ├─lib\
    │ ├─commons-logging-1.2.jar
    │ ├─jackson-annotations-2.9.2.jar
    │ ├─jackson-core-2.9.2.jar
    │ ├─jackson-databind-2.9.2.jar
    │ ├─spring-aop-5.0.1.RELEASE.jar
    │ ├─spring-aspects-5.0.1.RELEASE.jar
    │ ├─spring-beans-5.0.1.RELEASE.jar
    │ ├─spring-context-5.0.1.RELEASE.jar
    │ ├─spring-context-indexer-5.0.1.RELEASE.jar
    │ ├─spring-context-support-5.0.1.RELEASE.jar
    │ ├─spring-core-5.0.1.RELEASE.jar
    │ ├─spring-expression-5.0.1.RELEASE.jar
    │ ├─spring-instrument-5.0.1.RELEASE.jar
    │ ├─spring-jcl-5.0.1.RELEASE.jar
    │ ├─spring-jdbc-5.0.1.RELEASE.jar
    │ ├─spring-jms-5.0.1.RELEASE.jar
    │ ├─spring-messaging-5.0.1.RELEASE.jar
    │ ├─spring-orm-5.0.1.RELEASE.jar
    │ ├─spring-oxm-5.0.1.RELEASE.jar
    │ ├─spring-test-5.0.1.RELEASE.jar
    │ ├─spring-tx-5.0.1.RELEASE.jar
    │ ├─spring-web-5.0.1.RELEASE.jar
    │ ├─spring-webflux-5.0.1.RELEASE.jar
    │ ├─spring-webmvc-5.0.1.RELEASE.jar
    │ └─spring-websocket-5.0.1.RELEASE.jar
    ├─springmvc-config.xml
    └─web.xml

BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.fkit.controller;

import java.util.ArrayList;
import java.util.List;
import org.fkit.domain.Book;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// 该类会被看成一个Controller
@RestController
@RequestMapping("/json")
public class BookController
{
// 同时该类中所有使用@RequestMapping注解的方法都默认使用了@ResponseBody注解,
// 所以getJson方法会将List集合数据转换成JSON格式并返回客户端。
@RequestMapping(value = "/testRequestBody")
public Object getJson()
{
List<Book> list = new ArrayList<Book>();
list.add(new Book(1, "书名称1", "书的作者1"));
list.add(new Book(2, "书的名称2", "书的作者2"));
return list;
}
}

BookController使用了@RestController注解,该类会被看成一个Controller,同时该类中所有使用@RequestMapping注解的方法都默认使用了@ResponseBody注解, getJson方法会将List集合数据转换成JOSN格式并返回客户端.
测试结果和ResponseBodyTest项目的测试结果一致,此处不再赘述。

示例 接收XML格式的数据

创建一个XmlTest项目,在WebContent目录下创建一个js目录,加入jQueryjson2js文件。

项目结构

展开/折叠
G:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\03\XmlTest
├─src\
│ ├─book.xml
│ └─org\
│   └─fkit\
│     ├─controller\
│     │ └─BookController.java
│     └─domain\
│       └─Book.java
└─WebContent\
  ├─js\
  │ ├─jquery-1.11.0.min.js
  │ ├─jquery-migrate-1.2.1.min.js
  │ └─json2.js
  ├─META-INF\
  │ └─MANIFEST.MF
  ├─readxml.jsp
  ├─sendxml.jsp
  └─WEB-INF\
    ├─lib\
    │ ├─commons-logging-1.2.jar
    │ ├─spring-aop-5.0.1.RELEASE.jar
    │ ├─spring-aspects-5.0.1.RELEASE.jar
    │ ├─spring-beans-5.0.1.RELEASE.jar
    │ ├─spring-context-5.0.1.RELEASE.jar
    │ ├─spring-context-indexer-5.0.1.RELEASE.jar
    │ ├─spring-context-support-5.0.1.RELEASE.jar
    │ ├─spring-core-5.0.1.RELEASE.jar
    │ ├─spring-expression-5.0.1.RELEASE.jar
    │ ├─spring-instrument-5.0.1.RELEASE.jar
    │ ├─spring-jcl-5.0.1.RELEASE.jar
    │ ├─spring-jdbc-5.0.1.RELEASE.jar
    │ ├─spring-jms-5.0.1.RELEASE.jar
    │ ├─spring-messaging-5.0.1.RELEASE.jar
    │ ├─spring-orm-5.0.1.RELEASE.jar
    │ ├─spring-oxm-5.0.1.RELEASE.jar
    │ ├─spring-test-5.0.1.RELEASE.jar
    │ ├─spring-tx-5.0.1.RELEASE.jar
    │ ├─spring-web-5.0.1.RELEASE.jar
    │ ├─spring-webflux-5.0.1.RELEASE.jar
    │ ├─spring-webmvc-5.0.1.RELEASE.jar
    │ └─spring-websocket-5.0.1.RELEASE.jar
    ├─springmvc-config.xml
    └─web.xml

sendxml.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试接收XML格式的数据</title>
<script type="text/javascript" src="js/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript">
$(document).ready(function() {
sendxml();
});
function sendxml() {
var xmlData = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"+
"<book><id>1</id><name>书的名称</name><author>作者</author></book>";
$.ajax("${pageContext.request.contextPath}/sendxml",// 发送请求的URL字符串。
{
type : "POST", // 请求方式 POST或GET
contentType : "application/xml", // 发送信息至服务器时的内容编码类型
// 发送到服务器的数据。
data : xmlData,
async : true, // 默认设置下,所有请求均为异步请求。如果设置为false,则发送同步请求
});
}
</script>
</head>
<body>
</body>
</html>

分析

sendxml.jsp页面代码分析如下:
(1)页面使用jQuery发送JSON数据,在页面的<head>部分,引入了jQueryjson2的js文件
(2)载入页面时调用sendxml函数。
(3)sendxml函数发送异步请求到"sendxml",

  • ajax方法的contentType选项:contentType:"application/xml"表示发送的内容编码格式为XML;
  • ajax方法的data选项表示要发送的数据是XML数据。

Book.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package org.fkit.domain;

import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

// @XmlRootElement表示XML文档的根元素
@XmlRootElement
public class Book implements Serializable
{
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private String author;
public Book()
{
super();
}
public Book(Integer id, String name, String author)
{
super();
this.id = id;
this.name = name;
this.author = author;
}
public Integer getId()
{
return id;
}
// 该属性作为xml的element
@XmlElement
public void setId(Integer id)
{
this.id = id;
}

public String getName()
{
return name;
}
@XmlElement
public void setName(String name)
{
this.name = name;
}
public String getAuthor()
{
return author;
}
@XmlElement
public void setAuthor(String author)
{
this.author = author;
}
@Override
public String toString()
{
return "Book [id=" + id + ", name=" + name + ", author=" + author + "]";
}
}

分析

Book类中定义了3个属性:idnaneauthor,分别对应XML的元素。 toString方法用来输出获取的数据对象信息。

BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.fkit.controller;

import java.io.InputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import org.fkit.domain.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class BookController
{
// @RequestBody Book book会将传递的xml数据自动绑定到Book对象
@PostMapping(value = "/sendxml")
public void sendxml(@RequestBody Book book)
{
System.out.println("接收XML数据成功");
System.out.println(book);
}
// @ResponseBody 会将Book自动转成XML数据返回
@PostMapping(value = "/readxml")
// @ResponseBody注解把方法返回的Book对象转换成XML并发给客户端.
@ResponseBody
public Book readXml() throws Exception
{
// 通过JAXBContext的newInstance方法,传递一个class就可以获得一个上下文
JAXBContext context = JAXBContext.newInstance(Book.class);
// 创建一个Unmarshall对象
Unmarshaller unmar = context.createUnmarshaller();
// 读取资源目录下的文件
InputStream is = this.getClass().getResourceAsStream("/book.xml");
// Unmarshall对象的unmarshal方法可以进行xml到Java对象的转换
Book book = (Book) unmar.unmarshal(is);
System.out.println(book);
return book;
}
}

分析

sendxml方法中的第一个参数@RequestBody Book book表示,使用@RequestBody注解获取到XML数据后,将XML数据设置到Book对象的对应属性中。为了测试接收数据,将接收到XML数据的Book对象打印在控制台上。
springmvc-config.xml文件和JsonRequestTest项目的一致,重点在于<mvc:annotation-driven />,该配置默认装配了Jaxb2RootElementHttpMessageConverter来处理XML数据的转换。

测试

部署XmlTest这个Web应用,在浏览器中输入如下URL来测试应用:

1
http://localhost:8080/XmlTest/sendxml.jsp

载入sendxml.jsp页面时会发送Ajax请求,传递XML数据。BookController接收到请求后,@RequestBody注解会将XML数据设置到Book参数对应的属性中。控制台输出如下:

1
2
接收XML数据成功
Book [id=1, name=书的名称, author=作者]

可以看到,XML数据传递的idnameauthor元素被赋值到了Book对象对应的属性当中。

示例 返回XML格式的数据

readxml.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试返回XML格式的数据</title>
<script type="text/javascript" src="js/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript">
$(document).ready(function() {
readxml();
});
function readxml() {
$.ajax("${pageContext.request.contextPath}/readxml",// 发送请求的URL字符串。
{
dataType : "text", // 预期服务器返回的数据类型。
type : "POST", // 请求方式 POST或GET
async : true, // 默认设置下,所有请求均为异步请求。如果设置为false,则发送同步请求
// 请求成功后的回调函数。
success : function(xml) {
// 获得xml数据的id,name,author
var id = $("id", xml).text();
var name = $("name", xml).text();
var author = $("author", xml).text();
var tr = $("<tr align='center'/>");
$("<td/>").html(id).appendTo(tr);
$("<td/>").html(name).appendTo(tr);
$("<td/>").html(author).appendTo(tr);
$("#booktable").append(tr);
},
// 请求出错时调用的函数
error : function() {
alert("数据接收失败");
}
});
}
</script>
</head>
<body>
<table id="booktable" border="1" style="border-collapse: collapse;">
<tr align="center">
<th>编号</th>
<th>书名</th>
<th>作者</th>
</tr>
</table>
</body>
</html>

分析

readxml.jsp页面代码分析如下:
(1)页面使用jQuery发送M数据,在页面的<head>部分,引入了jQueryjson2的js文件。
(2)载入页面时调用readxml函数。
(3) readxml函数发送异步请求到readxml,请求成功将返回一个XML数据,接到返回的数据后将XML数据中的元素读取出来并将其设置到页面的<span>中。
BookControllerreadxml方法使用JAXB读取一个XML文件的数据并生成一个Book对象返回。@ResponseBody会将Book对象转换成XML数据返回到前台JSP页面。

测试

在浏览器中输入如下URL来测试应用:

1
http://localhost:8080/XmlTest/readxml.jsp

浏览器显示内容如下:

1
2
编号     书名     作者
1 书的名称 书的作者

这表示Spring MVC成功将XML数据返回到客户端。

3.15 转换XML数据

处理XML格式的HttpMessageConverter

Spring MVC提供了处理XML格式请求/响应的HttpMessageConverter,如:
Jaxb2RootElementHttpMessageConverter通过JAXB2读写XML消息,并将请求消息转换到注解@XmlRootElement@XmlType作用的类中。

配置步骤

因此只需要

  • Spring Web容器中为RequestMappingHandlerAdapter装配处理XMLHttpMessageConverter,
  • 并在交互过程中通过请求的Accept指定MIME类型,

Spring MVC就可以使服务端的处理方法和客户端通过XML格式的消息进行通信了。开发者几乎不用关心通信层数据格式的问题,可以将精力集中到业务处理上面。

JAXB

Spring的官方文档说明中,Spring MVC默认使用Jaxb2RootElementHttpMessageConverter转换XML格式的数据,JAXB(Java Architecture for XML Binding)可以很方便地生成XML格式数据,也能够很方便地生成JSON格式数据,这样一来可以更好地在XMLJSON之间进行转换。
JAXB是一个业界的标准,是一项可以根据XML Schema产生Java类的技术。在该过程中,JAXB提供了将XML实例文档反向生成Java对象的方法,并能将Java对象的内容重新写到XML实例文档中,从而使得Java开发者在Java应用程序中能够很方便地处理XML数据。
JAXB常用的注解包括:@XmlRootElement@XmlElement,等等.

示例 自定义HttpMessageConverter返回JSON格式的数据

接下来,使用Fastjson来返回JSON数据。
创建一个Fastjson2Test项目,在WebContent目录下创建一个js目录,加入jQueryjson2js文件,在WEB-INF/lib目录中加入Fastjsonjar文件。
Fastjson2Test项目的所有JSPJava文件和ResponseBodyTest一致,只是在springmvc-config.xml中使用了FastjsonFastJsonHttpMessageConverter。读者可参考配套资源文件中对应的代码,测试结果和ResponseBodyTest项目一致,此处不再赘述.

项目结构

展开/折叠
G:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\03\Fastjson2Test
├─src\
│ └─org\
│   └─fkit\
│     ├─controller\
│     │ └─BookController.java
│     └─domain\
│       └─Book.java
└─WebContent\
  ├─index.jsp
  ├─js\
  │ ├─jquery-1.11.0.min.js
  │ ├─jquery-migrate-1.2.1.min.js
  │ └─json2.js
  ├─META-INF\
  │ └─MANIFEST.MF
  └─WEB-INF\
    ├─lib\
    │ ├─commons-logging-1.2.jar
    │ ├─fastjson-1.2.9.jar
    │ ├─spring-aop-5.0.1.RELEASE.jar
    │ ├─spring-aspects-5.0.1.RELEASE.jar
    │ ├─spring-beans-5.0.1.RELEASE.jar
    │ ├─spring-context-5.0.1.RELEASE.jar
    │ ├─spring-context-indexer-5.0.1.RELEASE.jar
    │ ├─spring-context-support-5.0.1.RELEASE.jar
    │ ├─spring-core-5.0.1.RELEASE.jar
    │ ├─spring-expression-5.0.1.RELEASE.jar
    │ ├─spring-instrument-5.0.1.RELEASE.jar
    │ ├─spring-jcl-5.0.1.RELEASE.jar
    │ ├─spring-jdbc-5.0.1.RELEASE.jar
    │ ├─spring-jms-5.0.1.RELEASE.jar
    │ ├─spring-messaging-5.0.1.RELEASE.jar
    │ ├─spring-orm-5.0.1.RELEASE.jar
    │ ├─spring-oxm-5.0.1.RELEASE.jar
    │ ├─spring-test-5.0.1.RELEASE.jar
    │ ├─spring-tx-5.0.1.RELEASE.jar
    │ ├─spring-web-5.0.1.RELEASE.jar
    │ ├─spring-webflux-5.0.1.RELEASE.jar
    │ ├─spring-webmvc-5.0.1.RELEASE.jar
    │ └─spring-websocket-5.0.1.RELEASE.jar
    ├─springmvc-config.xml
    └─web.xml

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试返回JSON格式的数据</title>
<script type="text/javascript" src="js/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript">
$(document).ready(function() {
testResponseBody();
});
function testResponseBody() {
$.post("${pageContext.request.contextPath}/json/testRequestBody", null,
function(data) {
$.each(data, function() {
var tr = $("<tr align='center'/>");
$("<td/>").html(this.id).appendTo(tr);
$("<td/>").html(this.name).appendTo(tr);
$("<td/>").html(this.author).appendTo(tr);
$("#booktable").append(tr);
})
}, "json");
}
</script>
</head>
<body>
<table id="booktable" border="1" style="border-collapse: collapse;">
<tr align="center">
<th>编号</th>
<th>书名</th>
<th>作者</th>
</tr>
</table>
</body>
</html>

BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.fkit.controller;

import java.util.ArrayList;
import java.util.List;
import org.fkit.domain.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping(
"/json"
)
public class BookController
{
@RequestMapping(
value = "/testRequestBody"
)
// @ResponseBody注解会自动将集合数据转换json格式并返回给客户端
@ResponseBody
public Object getJson()
{
List<Book> list = new ArrayList<Book>();
list.add(new Book(1, "Spring+MyBatis企业应用实战", "肖文吉"));
list.add(new Book(2, "轻量级JavaEE企业应用实战", "李刚"));
return list;
}
}

springmvc-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?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:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- spring可以自动去扫描base-pack下面的包或者子包下面的java文件, -->
<!-- 如果扫描到有Spring的相关注解的类,则把这些类注册为Spring的bean -->
<context:component-scan
base-package="org.fkit.controller" />
<!-- 静态资源处理(因为用到js,css文件) -->
<mvc:default-servlet-handler />
<!-- 设置配置方案 -->
<mvc:annotation-driven>
<!-- 设置不使用默认的消息转换器 -->
<mvc:message-converters
register-defaults="false">
<!-- 配置Spring的转换器 -->
<bean
class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean
class="org.springframework.http.converter.xml.SourceHttpMessageConverter" />
<bean
class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
<bean
class="org.springframework.http.converter.BufferedImageHttpMessageConverter" />
<!-- 配置fastjson中实现HttpMessageConverter接口的转换器 -->
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!-- 加入支持的媒体类型:返回contentType -->
<property name="supportedMediaTypes">
<list>
<!-- 这里顺序不能反,一定先写text/html,不然ie下会出现下载提示 -->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

<!-- 视图解析器 p:prefix属性表示前缀 p:suffix 表示后缀 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/content/" p:suffix=".jsp" />

</beans>

3.14 @ResponseBody注解

用途

org.springframework.web.bind.annotation.ResponseBody注解用于Controller的请求处理方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区

适用范围

当返回的数据不是HTML标签的页面,而是其他某种格式的数据时(如JSONXML等)使用它

示例 @ResponseBody返回JSON格式的数据

创建一个ResponseBodyTest项目,在WebContent目录下创建一个js目录,加入jQueryjson2js文件,在WEB-INF/lib目录中加入Jacksonjar文件。

项目结构

展开/折叠
D:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\03\ResponseBodyTest
├─src\
│ └─org\
│   └─fkit\
│     ├─controller\
│     │ └─BookController.java
│     └─domain\
│       └─Book.java
└─WebContent\
  ├─index.jsp
  ├─js\
  │ ├─jquery-1.11.0.min.js
  │ ├─jquery-migrate-1.2.1.min.js
  │ └─json2.js
  ├─META-INF\
  │ └─MANIFEST.MF
  └─WEB-INF\
    ├─lib\
    │ ├─commons-logging-1.2.jar
    │ ├─jackson-annotations-2.9.2.jar
    │ ├─jackson-core-2.9.2.jar
    │ ├─jackson-databind-2.9.2.jar
    │ ├─spring-aop-5.0.1.RELEASE.jar
    │ ├─spring-aspects-5.0.1.RELEASE.jar
    │ ├─spring-beans-5.0.1.RELEASE.jar
    │ ├─spring-context-5.0.1.RELEASE.jar
    │ ├─spring-context-indexer-5.0.1.RELEASE.jar
    │ ├─spring-context-support-5.0.1.RELEASE.jar
    │ ├─spring-core-5.0.1.RELEASE.jar
    │ ├─spring-expression-5.0.1.RELEASE.jar
    │ ├─spring-instrument-5.0.1.RELEASE.jar
    │ ├─spring-jcl-5.0.1.RELEASE.jar
    │ ├─spring-jdbc-5.0.1.RELEASE.jar
    │ ├─spring-jms-5.0.1.RELEASE.jar
    │ ├─spring-messaging-5.0.1.RELEASE.jar
    │ ├─spring-orm-5.0.1.RELEASE.jar
    │ ├─spring-oxm-5.0.1.RELEASE.jar
    │ ├─spring-test-5.0.1.RELEASE.jar
    │ ├─spring-tx-5.0.1.RELEASE.jar
    │ ├─spring-web-5.0.1.RELEASE.jar
    │ ├─spring-webflux-5.0.1.RELEASE.jar
    │ ├─spring-webmvc-5.0.1.RELEASE.jar
    │ └─spring-websocket-5.0.1.RELEASE.jar
    ├─springmvc-config.xml
    └─web.xml

BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.fkit.controller;

import java.util.ArrayList;
import java.util.List;
import org.fkit.domain.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/json")
public class BookController
{
@RequestMapping(value = "/testRequestBody")
// @ResponseBody注解会自动将集合数据转换json格式并返回给客户端.
@ResponseBody
public Object getJson()
{
List<Book> list = new ArrayList<Book>();
list.add(new Book(1, "书名字1", "作者1"));
list.add(new Book(2, "书名字2", "作者2"));
return list;
}
}

getJson方法会将List集合数据转换成JSON格式,然后将其返回到客户端。

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试返回JSON格式的数据</title>
<script type="text/javascript" src="js/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript">
$(document).ready(function() {
testResponseBody();
});
function testResponseBody() {
//提交post请求
$.post("${pageContext.request.contextPath}/json/testRequestBody", null,
//请求成功后调用
function(data) {
$.each(data, function() {
//把获取到的数据封装成表格的一行
var tr = $("<tr align='center'/>");
$("<td/>").html(this.id).appendTo(tr);
$("<td/>").html(this.name).appendTo(tr);
$("<td/>").html(this.author).appendTo(tr);
//把这行追加到表格之中
$("#booktable").append(tr);
})
},
//表示请求的是json数据
"json");
}
</script>
</head>
<body>
<table id="booktable" border="1" style="border-collapse: collapse;">
<tr align="center">
<th>编号</th>
<th>书名</th>
<th>作者</th>
</tr>
</table>
</body>
</html>

分析

index.jsp页面代码分析如下:
(1)页面使用jQuery发送请求,在页面的<head>部分,引入了jQueryjson2的js文件。
(2)载入页面时调用testResponseBody函数。
(3) testResponseBody函数发送异步请求到"json/testRequestBody",请求成功将返回一个JSON数据,该数据包含多个书籍信息。接到返回的数据后使用jQuery将数据设置到页面的<table>表单中。

测试

部署ResponseBodyTest这个Web应用,在浏览器中输入如下URL来测试应用:

1
http://localhost:8080/ResponseBodyTest/

载入index.jsp页面时会发送Ajax请求, getJson方法创建多个Book对象并将其封装到List集合中返回,方法上的@ResponseBody注解会将集合数据转换为JSON格式数据并将其返回客户端。
此时客户端显示内容如下:

1
2
3
编号     书名     作者
1 书名字1 作者1
2 书名字2 作者2

这表示Spring MVC成功将包含Book对象的集合数据被转换成JSON格式并被成功写回客户端。