使用IntelliJ IDEA开发SpringMVC网站(七)添加REST功能

在上一篇《使用IntelliJ IDEA开发SpringMVC网站(六)利用mybatis-generator自动生成代码》文章中讲解了如何利用mybatis-generator自动生成代码,本文讲解一下添加REST功能。

rest(一种软件架构风格)

  REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。

  目前在三种主流的Web服务实现方案中,因为REST模式的Web服务与复杂的SOAP和XML-RPC对比来讲明显的更加简洁,越来越多的web服务开始采用REST风格设计和实现。

  为了帮助Spring开发人员使用REST架构模式,Spring3.0封装了对REST的良好支持。

REST基本原理

(以下内容摘自《Spring 实战 第3版》)
  当谈论REST时,有一种常见的错误就是将其视为“基于URL的Web服务”———将REST作为另一种类型的远程过程调用(Remote Procedure Call, RPC)机制,就像SOAP一样,只不过是通过简单的HTTP URL而不是SOAP的大量XML命名空间来触发。

  恰好相反,REST与RPC几乎没有任何关系。RPC是面向服务的,并关注于行为和动作;而REST是面向资源的,强调描述应用程序的事务和名词。

  此外,尽管URL在REST中起了关键作用,但他们仅仅是整体的一部分而已。为了理解REST是什么,我们将它的首字母缩写拆分为不同的组成部分。

  • 表述性(Representational)——REST资源实际上可以用各种形式来进行表述,包括XML、JSON(JavaScript Object Notation)甚至HTML-最适合资源使用者的任意形式。
  • 状态(State)——当使用REST的时候,我们更关注资源的状态而不是对资源采取的行为。
  • 转移(Transfer)——REST涉及转移资源数据,它以某一种表述性形式从一个应用转移到了一个应用。

  更简洁地讲,REST就是将资源的状态以最合适的形式从服务器端转移到客户端(或者反之)。

Spring是如何支持REST的

  Spring3对Spring MVC的一些增强功能为REST提供了良好的支持。现在,Spring支持以下方式来开发REST资源。

  • 控制器可以处理所有的HTTP方法,包含4个主要的REST方法:GET、PUT、DELETE以及POST。
  • 新的@PathVariable注解使得控制器能够处理参数化的URL(将变量输入作为URL的一部分)
  • Spring表单绑定JSP标签库的form:form标签以及新的HiddenHttpMethodFilter,使得通过HTML表单提交PUT和DELETE请求成为可能,即便在某些浏览器中不支持这些HTTP方法。
  • 通过使用Spring的视图和视图解析器,资源可以以各种形式进行表述,包括将模型数据表现为XML、JSON、Atom和RSS的新视图实现。
  • 可以使用新的ContentNegotiatingViewResolver来选择最适合客户端的表述。
  • 基于视图的渲染可以使用新的@ResponseBody注解和各种HTTPMethodConverter实现来达到。
  • 类似地,新的@ResponseBody注解以及HttpMethodConverter实现可以将传入的HTTP数据转化为传入控制器处理方法的Java对象。
  • RestTemplate简化了客户端对REST资源的使用。

编写面向资源的控制器

  编写Spring MVC控制器类的模型是相当灵活的。几乎所有签名的方法都可以用来处理Web请求。但是这种灵活性的一个副作用据说SPring MVC允许你开发出不符合RESTful资源的控制器。编写出的控制器很容易是RESTless的。

剖析RESTless的控制器

  我们先看一下RESTless控制器究竟长什么样子,这有助于我们理解RESTfult控制器。DisplayUserController就是一个RESTless的控制器,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
@Controller
@RequestMapping("/displayUser.htm")
public class DisplayUserController {
@Autowired
private UserService userService;

@RequestMapping(method = RequestMethod.GET)
public String displayUser(@RequestParam(required = false, defaultValue = "1") int id, Model model) {
model.addAttribute(userService.findById(id));
return "user/view";
}
}

  首先要关注的第一件事是这个类的名字。确实,它只是一个名字而已。但是,它准确表达了这个控制器是做什么的。第一个单词是Display——一个动词。这表明这个控制器是面向行为的,而不是面向资源的。

  注意类级别的@RequestMapping注解。它说明这个控制器会处理”displayUser.htm”的请求,这似乎表明这个控制器关注于展现User(这里通过类名可以得到确认)的特殊用例。另外,扩展名说明它只能以HTML的形式来展现列表。

  DisplayUserController的编写方式并没有严重的错误。但是,它并不是一个RESTful的控制器。它是面向行为的并关注于一个特殊的用例:以HTML的形式展现一个User对象的详细信息。

处理RESTful URL

  当开始使用REST的时候,很多人想到的第一件事通常是URL。毕竟,在REST中所有的事情都是通过URL完成的。
URL是统一资源定位符(uniform resource locator)的缩写,按照这个名字,URL本意是用于定位资源的。此外,所有的URL同时也都是URI统一资源标识符(uniform resource identifier)。如果是这样的话,我们可以认为任何给定的URL不仅可以定位一个资源还可以用于标识一个资源。
不同于RESTless URL(http://localhost:8080/SpringMVCDemo/displayUser.htm?id=2), RESTful完成承认HTTP用于表示资源的。 例如:http://localhost:8080/SpringMVCDemo/users/2

添加REST功能

关于REST需要讲解的知识很多,这里就不一一详细介绍了,下面我们直接用一个例子来看看Spring MVC对REST的支持吧。

例子达到的效果是,根据不同的URL返回不同的View:

http://localhost:8080/users/2 返回默认的视图(FreeMarker)

http://localhost:8080/users/2.json 返回JSON

http://localhost:8080/users/2.xml 返回XML

1.添加依赖
在pom.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
<properties>
...
<jackson.version>2.7.5</jackson.version>
<jackson_mapper.version>1.9.13</jackson_mapper.version>
</properties>

<dependencies>
...
<!-- Needed for JSON View -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson_mapper.version}</version>
</dependency>

<!-- Needed for XML View (with JAXB2) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>

使用JAXB2.0实现OXM

OXM是Object XML Mapping的缩写,它是一个O/M-mapper,将java对象映射成XML数据,或者将XML数据映射成java对象。我们把对象与关系数据库之间的映射称为ORM, 所以也把对象与XML之间的映射称为OXM。

JAXB(Java Architecture for XML Binding)是一种特殊的序列化/反序列化工具。它可以使XML数据以Java Objects的形式直接应用于Java程序之中,使Java Objects与XML数据之间的转换成为可能。在JAXB中将Java Objects到XML数据的转换称为marshal;XML数据到Java Objects的转换称为unmarshal。原来JAXB是Java EE的一部分,在JDK6中,SUN将其放到了Java SE中,这也是SUN的一贯做法。JDK6中自带的这个JAXB版本是2.0, 比起1.0(JSR 31)来,JAXB2(JSR 222)用JDK5的新特性Annotation来标识要作绑定的类和属性等,这就极大简化了开发的工作量。

更多介绍参考:http://my.oschina.net/zzx0421/blog/98186

2.Model
修改User,使用@XmlRootElement JAXB注解,@XmlRootElement 将一个Java类映射为一段XML的根节点,之后我们可以使用这个Model展示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
package com.springmvcdemo.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class User {

private int id;
private String age;
private String userName;
public User(){
super();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public User(int id, String age, String userName) {
super();
this.id = id;
this.age = age;
this.userName = userName;
}
}

3.JSON和XML视图

若要输出 JSON 和 XML 视图,你不需要做任何额外的工作,Spring MVC 将自动处理转换.

4.Freemarker View
一个Freemarker View用来展示查询的User数据.
File: /WEB-INF/views/user/view.ftl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html> <!-- 使用 HTML5 doctype,不区分大小写 -->
<html lang="zh-cmn-Hans"> <!-- 更加标准的 lang 属性写法 -->
<head>
<!-- 声明文档使用的字符编码 -->
<meta charset='utf-8'>
<!-- 优先使用 IE 最新版本和 Chrome -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>

<title></title>
</head>

<body>
<h1>Spring @MVC ContentNegotiatingViewResolver</h1>

<b>User ID :</b> ${user.id} <br/>
<b>User Age :</b> ${user.age} <br />
<b>User Name :</b> ${user.userName}


</body>

5.Controller

新建一个UserController,如下所示:

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
package com.springmvcdemo.controller;


import com.springmvcdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;


@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public String getUser(@PathVariable("id") int id, Model model) {
model.addAttribute(userService.findById(id));
return "user/view";
}

/*
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void putUser(@PathVariable("id") int id, User user) {
userService.update(user);
}

@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable("id") int id) {
userService.deleteById(id);
}

@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public @ResponseBody User createUser(User user, HttpServletRequest result, HttpServletResponse response) {
userService.insert(user);
response.setHeader("Location", "/users/" + user.getId());
return user;
}
*/
}

6.ContentNegotiatingViewResolver

在面向人类访问的Web应用程序中,选择的视图通常来讲都会渲染为HTML。视图解决方案是个简单的活动,如果根据视图名匹配上了视图,那这就是我们要用的视图了,当要讲视图名解析为能够产生资源表述的视图时,我们就需要另外一个维度需要考虑了,视图不仅要匹配视图名,而且选择的视图要适合客户端。如果客户端想要JSON,那么渲染HTML的视图就不行了——尽管视图名可能匹配。

Spring的ContentNegotiatingViewResolver是一个特殊的视图解析器,它考虑到了客户端所需要的内容类型。就像其他的视图解析器一样,它要作为一个bean配置在Spring应用上下文里。

在spring-mvc.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
<!-- ContentNegotiatingViewResolver -->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="contentNegotiationManager">
<bean class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="defaultContentType" value="text/html"/>
<property name="mediaTypes">
<map>
<entry key="json" value="application/json"/>
<entry key="xml" value="application/xml"/>
<entry key="html" value="text/html"/>
</map>
</property>
</bean>
</property>

<property name="defaultViews">
<list>
<!-- JSON View -->
<bean
class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
</bean>

<!-- JAXB XML View -->
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.springmvcdemo.model.User</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
</list>
</property>
</bean>

7.Demo

启动服务,

http://localhost:8080/users/2.xml 返回xml视图:

image

http://localhost:8080/users/2.json 返回json视图:

image

http://localhost:8080/users/2 返回默认视图(html):

image

(代码已提交到GitHub:https://github.com/jasonli822/MySpringMvcFrame)