Spring MVC(10):REST 支持 & Ajax+Spring MVC 实例
Spring 对于 REST 的支持
REST
REST(Respresentational State Transfer) 是一种面向资源,强调描述应用程序远程调用的开发方式,并不特指某种技术和框架,简洁来讲:就是将资源的状态以最适合客户端或服务端的形式从服务器客户端转移到客户端的过程;这个与
RPC(remote procedure call)面向服务,关注行为和动作不同;
REST的内涵可以分为以下几点:
-
表述性(Respresesntational):REST资源实际上可以使用各种形式来进行表述,包括 XML、JSON、HTML 等;
-
状态(State):当使用 REST 时候,我们更加关注资源的状态,而非对资源采取的行为;
- 转移(Transfer):REST 涉及资源数据转移时,它以某种表述形式从一个应用程序转移到另一个应用程序;
在 REST 中,资源通过 URL 进行识别和定位,对于资源的行为,是通过 HTTP 方法来进行定义的,即 GET、POST、PUT、DELETE、PATCH 等,这些 HTTP 方法通常会匹配以下的 CRUD 动作:
- Create:POST
- Read:GET
- Update:PUT、PATCH
-
Delete:DELETE
但是这种映射并不是严格规定的,实际上 POST 由于具有请求非幂等性(non-idempotent),使用十分灵活,可以处理任何无法适应 HTTP 方法语义定义的操作;
REST 在不同的语言框架可以分别有自己的实现,Spring
从 3.0 开始对 REST 提供支持,在 Spring 4.0 开始,支持以下的方式创建 REST 资源:
- 控制器可以处理所有 HTTP 方法(包括 POST、GET、PUT、DELETE);
-
控制器借助 @PathVariable 注解,可以处理参数化的 URL;
-
借助 Spring 的视图和视图解析器,资源可以以多种方式进行表述,包括将模型数据渲染为 XML、JSON、Atom、RSS 等;
-
借助 @ResponseBody 注解,各种 HttpMethodConverter,可以替换基于视图的渲染方式;
-
借助 @ResquestBody 注解,各种 HttpMethodConvterter ,可以实现将传输的 HTTP 数据转化为传入控制器处理方法的 POJO;
- 借助 RestTemplate,Spring 应用可以很方便地处理和使用 REST 资源;
HTTP数据转化器 HttpMessageConverter<T>
HttpMessageConverter
的实现类如下:
Spring 默认装配了 RequestMappingHandlerAdapter
作为 HandlerAdapter 的组件实现类,HttpMessageConverter 由 RequestMappingHandlerAdapter 使用;
默认 RequestMappingHandlerAdapter 已经装配了以下的
HTTP 转换器:
-
StringHttpMessageConverter;
-
ByteArrayHttpMessageConverter;
-
SourceHttpMessageConverter;
-
AllEncompassingFormHttpMessageConverter;
如果需要装载其他的转换器,可以在 spring-mvc 配置文件中添加以下:
<!--装载 HttpMessageConverter 适配器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"
p:messageConverters-ref="messageConverters"/>
<!--转换器列表-->
<util:list id="messageConverters">
<bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter" />
<bean class="org.springframework.http.converter.FormHttpMessageConverter" />
</util:list>
<!--自动注册默认MessageConverter-->
<mvc:annotation-driven/>
服务端使用 HTTP 转换器
Spring MVC 提供以下 2 种方式用于使用 HttpMessageConverter 将其请求信息转换并绑定到处理方法入参中:
1)@RequestBody / @ResponseBody
@RequestBody :用于在将客户端的请求正文经过 HttpMessageConverter 转化后的对象,标识到控制器处理方法的入参中;
@ResponseBody :用于标识控制器处理方法的输出对象,此时 HttpMessageConverter 会根据该返回方式的类型在已经注册的转换器中选择合适的转换器,将该响应对象转换为相应的响应正文;
简单的使用示例如下:
public class UserController {
private static final Logger log = LogManager.getLogger();
//接收客户端表单信息,使用 @RequestBody 将该请求正文转换后标识到方法入参
value="/handleForm",method= RequestMethod.POST) (
public String handleFrom( String requestBody){
log.debug(requestBody);
return "success";
}
//向客户端发送一张imageId相关的图片的响应流,使用 @ResponseBody 将输出对象转换为相应的响应正文
"/getImg/{imageId}") (
public byte[] getImage( ("imageId") String imageId) throws IOException {
log.debug("imageId: " + imageId);
Resource res = new ClassPathResource("testimg"+imageId+".jpg");
byte[] fileData = FileCopyUtils.copyToByteArray(res.getFile());
return fileData;
}
}
2)HttpEntity<T> / ResponseEntity<T>
HttpEntity<T> / ResponseEntity<T> 除了能提供以上的 @RequestBody / @ResponseBody 的功能之外,还能为响应、请求绑定响应头、请求头;
以上示例使用 HttpEntity<T> / ResponseEntity<T> 改写如下:
//同 handleForm 方法
"/handleForm2") (
public String handleForm2( HttpEntity<String> httpEntity){
log.debug(httpEntity.getHeaders());
log.debug(httpEntity.getBody());
return "success";
}
//同 getImage 方法
"/getImg2/{imageId}") (
public ResponseEntity<byte[]> getImage2( ("imageId") String imageId) throws IOException {
log.debug("imageId: " + imageId);
Resource res = new ClassPathResource("testimg"+imageId+".jpg");
byte[] fileData = FileCopyUtils.copyToByteArray(res.getFile());
//创建响应对象,将图片字节流加入响应正文
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(fileData, HttpStatus.OK);
return responseEntity;
}
客户端使用HTTP转换器
对于在客户端发送和接收 REST HTTP 请求,Spring
MVC 提供了 RestTemplate
模板类,用于在客户端中调用服务端的的
REST 风格 URL 接口的服务;
该模板类有以下常用的成员方法:
getForObject / getForEntity | 使用 GET 方法发送请求,获取服务器响应 |
postForObejct / postForEntity / postForLocation | 使用 POST 方法发送请求,获取服务器响应 |
put、patchForObject / patchForEntity | 使用 PUT、PATCH 方法发送请求,获取服务器响应 |
delete | 使用 DELETE 方法发送请求,获取服务器响应 |
exchange | 使用自定义方法发送请求,获取服务器响应 |
如同
RequestMappingHandlerAdapter 一样,RestTemplate 也拥有一张 HttpMessageConverter 的注册表,默认已经注册了以下的 HttpMessageConverter:
- StringHttpMessageConverter;
- ByteArrayHttpMessageConverter;
- SourceHttpMessageConverter;
- ResourceHttpMessageConverter;
- AllEncompassingFormHttpMessageConverter;
以下是对应上面示例代码中服务器接口的客户端方法:
//调用服务端 UserController#handleForm 服务接口
public void testHandleForm(){
//创建RestTemplate
RestTemplate restTemplate = new RestTemplate();
//构建表单对象
MultiValueMap<String,String> form = new LinkedMultiValueMap<>();
form.add("userId","1");
form.add("userName","name");
form.add("userAge","20");
//发送请求
restTemplate.postForLocation("http://127.0.0.1:8080/project/handleForm",form);
}
//调用服务端 UserController#getImage 服务接口
public void testGetImage() throws Exception{
//创建RestTemplate
RestTemplate restTemplate = new RestTemplate();
//发送请求,接收服务接口的响应,将响应正文转换为 byte[] 对象
byte[] response = restTemplate.postForObject("http://127.0.0.1:8080/project/getImg/{itemId}",null,byte[].class,"001");
Resource res = new PathResource("./img_copy.jpg");
FileCopyUtils.copy(response,res.getFile());
}
//调用服务端 UserController#handleForm2 服务接口
public void testHandleForm2(){
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String,String> form = new LinkedMultiValueMap<>();
form.add("userId","1");
form.add("userName","name");
form.add("userAge","18");
restTemplate.postForLocation("http://127.0.0.1:8080/project/handleForm2",form);
}
//调用服务端 UserController#getImage2 服务接口
public void testGetImage2() throws Exception{
RestTemplate restTemplate = new RestTemplate();
byte[] response = restTemplate.postForObject("http://127.0.0.1:8080/project/getImg2/{itemId}",null,byte[].class,"001");
Resource res = new PathResource("./img_copy.jpg");
FileCopyUtils.copy(response,res.getFile());
}
使用 HTTP 转换器处理 XML、JSON 数据
在 REST 方式的前后端中最常见的数据交换格式为 XML、JSON 格式数据,使用 HTTPMessageConverter 可以很方便地实现自动将对象 与 相应的XML、JSON格式请求正文/响应正文 之间进行相应的转换;
Spring MVC 提供了以下的转化器用于处理 XML、JSON 格式的请求/响应信息的 HttpMessageConverter;
-
MarshallingHttpMessageConverter:处理 XML请求/响应
-
Jaxb2RootElementHttpMessageConverter:处理XML请求/响应,底层使用JAXB
-
MappingJackson2HttpMessageConverter:处理JSON请求/响应
需要导入的额外依赖库:
com.thoughtworks.xstream:xstream(xStream xml 处理驱动)
com.fasterxml.jackson.core:jackson-core(Jackson json 处理驱动)
com.fasterxml.jackson.core:jackson-databind
服务端
spring-mvc 上线文中配置HTTP消息转换器如下:
<!--装载 HttpMessageConverter 适配器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"
p:messageConverters-ref="messageConverters"/>
<!--转换器列表-->
<util:list id="messageConverters">
<!--XML消息转换器-->
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
p:marshaller-ref="xmlMarshaller"
p:unmarshaller-ref="xmlMarshaller"/>
<!--JSON 消息转换器-->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</util:list>
<!--自动注册默认MessageConverter-->
<mvc:annotation-driven/>
<!--装载Marshaller,使用XStream处理XML-->
<bean id="xmlMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="streamDriver"> <!--声明xml处理驱动-->
<bean class="com.thoughtworks.xstream.io.xml.StaxDriver" /> <!--使用STAX对消息进行处理-->
</property>
<property name="annotatedClasses"> <!--声明使用XStream注解进行XML转换规则的类-->
<list>
<value>site.assad.model.User</value>
</list>
</property>
</bean>
模型类中 User
使用 XStream 注解标记节点:
package site.assad.model;
"user") (
public class User implements Serializable{
"id") (
private int userId;
"name") (
private String userName;
"age1") (
private int age;
//省略 getter,setter
}
示例 UserController 的控制器服务接口如下:
//输出 User 对象 XML / JSON 格式正文的响应
value="/getUser/{userId}",method=RequestMethod.GET) (
public ResponseEntity<User> handleUser( ("userId") int userId){
User user = userService.getUser(userId);
ResponseEntity<User> responseEntity = new ResponseEntity<>(user,HttpStatus.OK);
log.debug(responseEntity.getHeaders());
log.debug(responseEntity.getBody());
return responseEntity;
}
//从请求中获取 XML / JSON 正文,并转化为 User 对象
value="/putUser",method=RequestMethod.POST) (
public String getUser( HttpEntity<User> requestEntity ){
User user = requestEntity.getBody();
log.debug(user);
return "server get User:" + user;
}
可以看到 XML 和 JSON 消息控制器的处理方法是一样的,这些处理方法会根据客户端的请求头相关参数决定将对象转换为 JSON 或 XML 文本;
客户端
客户端接收服务端的 XML/JSON 响应正文,并将其转化为 User,对应UserController#handlerUser
//接收服务器 XML 响应正文,并转化为 User
public void testHanldeUserXML(){
RestTemplate restTemplate = buildRestTemplate();
//构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML)); //请求头说明接收的响应正文类型为XML格式
//构建请求对象
HttpEntity<String> requestEntity = new HttpEntity<>(null,headers);
//发送请求,获取响应对象
ResponseEntity<User> responseEntity = restTemplate.exchange(
"http://127.0.0.1:8080/project/getUser/{userId}",HttpMethod.GET,requestEntity,User.class,"1");
User user = responseEntity.getBody();
log.debug(responseEntity.getHeaders() + "\n" + user);
}
//接收服务器 JSON 响应正文,并转化为 User
public void testHanldeUserJSON(){
RestTemplate restTemplate = buildRestTemplate();
//构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); //请求头说明接收的响应正文类型为JSON 格式
//构建请求对象
HttpEntity<String> requestEntity = new HttpEntity<>(null,headers);
//发送请求,获取响应对象
ResponseEntity<User> responseEntity = restTemplate.exchange(
"http://127.0.0.1:8080/project/getUser/{userId}",HttpMethod.GET,requestEntity,User.class,"1");
User user = responseEntity.getBody();
log.debug(responseEntity.getHeaders() + "\n" + user);
}
//创建包含 XML,JSON 转换器的 RestTemplate
private RestTemplate buildRestTemplate(){
RestTemplate restTemplate = new RestTemplate();
XStreamMarshaller xStreamMarshaller = new XStreamMarshaller();
xStreamMarshaller.setStreamDriver(new StaxDriver());
xStreamMarshaller.setAnnotatedClasses(new Class[]{User.class});
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
xmlConverter.setMarshaller(xStreamMarshaller);
xmlConverter.setUnmarshaller(xStreamMarshaller);
restTemplate.getMessageConverters().add(xmlConverter); //添加 XML 转换器
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); //添加 JSON 转换器
return restTemplate;
}
客户端向服务端发送 XML/JSON 格式的请求正文,对应UserController#getUser
//向服务端发送 User 的 XML 请求正文
public void testGetUserXML(){
RestTemplate restTemplate = buildRestTemplate();
User user = new User(4,"Tim",30);
//构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/xml;UTF-8")); //请求头说明请求正文数据类型为xml
//构建请求对象
HttpEntity<User> requestEntity = new HttpEntity<>(user,headers);
String fallback = restTemplate.postForObject("http://127.0.0.1:8080/project/putUser",requestEntity,String.class);
log.debug(fallback);
}
//向服务端发送 User 的 JSON 请求正文
public void testGetUserJSON(){
RestTemplate restTemplate = buildRestTemplate();
User user = new User(5,"Tim",30);
//构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/json;UTF-8")); //请求头说明请求正文数据类型json
//构建请求对象
HttpEntity<User> requestEntity = new HttpEntity<>(user,headers);
String fallback = restTemplate.postForObject("http://127.0.0.1:8080/project/putUser",requestEntity,String.class);
log.debug(fallback);
}
@RestController 和 AsyncRestTemplate
@ResrController
Spring 4.0 开始,为了方便 REST 程序的开发,Spring 引入了一个 @RestController 注解,用于标注一个控制器为 REST 控制器,如下:
public class UserController {
.....
}
//等同于
public class UserController {
.....
}
AsyncRestTemplate
Spring 4.0
为 RestTemplate 添加了一个实现类 AsyncRestTemplate,用于支持以异步无阻塞的方式进行服务访问;
以下是一个示例的服务端接口:
//演示客户端 AsyncRestTemplate 异步访问服务接口的的服务端接口
value="/getUserAsync",method=RequestMethod.GET) (
public Callable<ResponseEntity<User>> handleUserLongTime(){
return new Callable<ResponseEntity<User>>() {
public ResponseEntity<User> call() throws Exception {
User user = new User(1,"assad",12);
ResponseEntity<User> responseEntity = new ResponseEntity<User>(user,HttpStatus.OK);
Thread.sleep(1000 * 10); //模拟处理时间
return responseEntity;
}
};
}
客户端代码:
//演示客户端 AsyncRestTemplate 异步访问服务接口
public void testGetUserList() throws InterruptedException {
AsyncRestTemplate restTemplate = new AsyncRestTemplate();
System.out.println("client start");
//调用服务接口后,立即返回
ListenableFuture<ResponseEntity<User>> future =
restTemplate.getForEntity("http://127.0.0.1:8080/project/getUserAsync",User.class);
//处理服务响应的异步回调方法
future.addCallback(new ListenableFutureCallback<ResponseEntity<User>>() {
public void onFailure(Throwable ex) {
log.debug("client failure: "+ ex);
}
public void onSuccess(ResponseEntity<User> result) {
User user = result.getBody();
System.out.println("client get result: " + user);
}
});
System.out.println("continue, no wait");
Thread.sleep(1000 * 20);
}
AJAX 实例
运用 Spring MVC 对于 REST 的支持,和使用 HttpMessageConveter 对于 XML、JSON 请求/响应 的处理,可以很方便地编写 Web Ajax 应用的后台;
以下是一个实例的 Ajax + Spring MVC 应用:
前端输入 userId,通过 Ajax 到后台控制器,后台返回符合该 userId 的 User 对象的 JSON 响应正文,再由前台渲染 JSON 数据,应用截图如下:
由于 JSON 作为数据交换格式,后端用到 MappingJackson2HttpMessageConverter
转化器,需要导入以下依赖:
com.fasterxml.jackson.core:jackson-core
com.fasterxml.jackson.core:jackson-databind
后端部分:
web.xml 添加 Spring 容器监听器
<!--加载 Spring WebApplicationContext: Service 层和 Dao 层的配置文件加载到 Spring 容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--启动 Spring 容器监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--声明 DispatcherServlet-->
<servlet>
<servlet-name>assad</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!--配置 DispatcherServlet 的匹配 URL 模式-->
<servlet-mapping>
<servlet-name>assad</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Spring 上下文配置文件 applicationContext.xml 扫描 service bean
<context:component-scan base-package="site.assad.service" />
spring-mvc 上下文配置文件 assad-servlet.xml 扫描 controller bean,添加转化器等
<!--装载 controller bean -->
<context:component-scan base-package="site.assad.web" />
<!--配置视图解析器,映射逻辑视图名的解析-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/views/" p:suffix=".jsp" />
<!--静态资源请求转发-->
<mvc:default-servlet-handler />
<!--装载 HttpMessageConverter 适配器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"
p:messageConverters-ref="messageConverters"/>
<!--额外转换器列表-->
<util:list id="messageConverters">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</util:list>
<!--自动注册默认MessageConverter-->
<mvc:annotation-driven/>
后端的控制器 AjaxController
package site.assad.web;
public class AjaxController {
private UserService userService;
//转发入口url到jsp视图
"/ajax") (
public String toAjaxPage(){
return "ajaxTest";
}
//处理ajaxTest.jsp的Ajax请求,根据获取到的 userId,返回相应的User对象,
//具体转化为JSON,XML或其他格式的响应正文,由请求头的 Accept 决定
value="/getUserAjax",method=RequestMethod.GET) (
public ResponseEntity<User> handleUserAjax( ("userId") int userId){
User user = userService.getUser(userId);
ResponseEntity<User> responseEntity = new ResponseEntity<>(user, HttpStatus.OK);
return responseEntity;
}
}
前端部分:
ajaxTest.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Ajax 测试页面</title>
<script src="<c:url value="/resources/js/jquery-3.1.min.js"/>" ></script>
<script type="text/javascript">
$(document).ready(function(){
//监听userId输入框添的更改时间,加入Ajax方法
$("#userIdInput").change(function(){
var userId = $(this).val().trim();
if(userId !== "" && userId !== null && userId !== undefined ){
$.ajax({
url : "<c:url value="/getUserAjax"/>",
type : "GET",
headers: {
Accept : "application/json;charset=utf-8" //设置响应正文格式类型为 json
},
data : {"userId": userId},
success : function(resData){
if(resData.userId !== undefined){
$("#message").text(
"User Id :" + resData.userId+"\n"
+ "User Name: " + resData.userName + "\n"
+ "User Age: "+ resData.age + "\n"
);
}else{
$("#message").text("不存在该用户");
}
},
error : function(resData,status){
$("message").text("Ajax 请求错误");
}
});
}
});
});
</script>
</head>
<body>
<h1> 实时显示用户信息 </h1>
<label>Input User Id </label><input id="userIdInput" type="text" name="userId" />
<pre id="message"></pre>
</body>
</html>