RESTful in Java
Table of Contents
1. REST 简介
REST stands for REpresentational State Transfer.
REST 是一种 Web 架构风格。这种架构有下面 6 个约束:
(1) 客户-服务器(Client-Server):通信只能由客户端单方面发起,表现为请求-响应的形式。
(2) 无状态(Stateless):通信的会话状态(Session State)应该全部由客户端负责维护。
(3) 缓存(Cache):响应内容可以在通信链的某处被缓存,以改善网络效率。
(4) 统一接口(Uniform Interface):通信链的组件之间通过统一的接口(如 HTTP 方法 GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS)相互通信,以提高交互的可见性。
(5) 分层系统(Layered System):通过限制组件的行为(即,每个组件只能“看到”与其交互的紧邻层),将架构分解为若干等级的层。
(6) 按需代码(Code-On-Demand,可选):支持通过下载并执行一些代码(例如 Java Applet、Flash 或 JavaScript),对客户端的功能进行扩展。
To the extent that systems conform to the constraints of REST they can be called RESTful.
参考:http://www.infoq.com/cn/articles/understanding-restful-style
REST 架构的好处:
- Performance - component interactions can be the dominant factor in user-perceived performance and network efficiency.
- Scalability to support large numbers of components and interactions among components.
- Simplicity of interfaces.
- Modifiability of components to meet changing needs (even while the application is running).
- Visibility of communication between components by service agents.
- Portability of components by moving program code with the data.
- Reliability is the resistance to failure at the system level in the presence of failures within components, connectors, or data.
参考:https://en.wikipedia.org/wiki/Representational_state_transfer
2. Java 实例:RESTful Web Services
2.1. Eclipse 中演示 Jersey 实例
下面通过一个简单的例子来演示在 Eclipse 集成开发环境中用 Java 实现 RESTful Web Services。例子中 JAX-RS(Java API for RESTful Web Services)的实现采用的是 Jersey.
这个例子是一个简单的用户管理应用,为了简单起见直接使用文件保存用户数据,没有使用数据库。
说明:由于 JAX-RS 标准中并不包含 JSON 的相关接口,要使用 JSON,还需要其它库及其依赖,这个例子使用的是 XML 格式,而没有使用 JSON。
参考:http://www.tutorialspoint.com/restful/restful_quick_guide.htm
2.1.1. 环境准备
首先,要安装 JDK 1.7, Apache Tomcat(或其它应用服务器),步骤略。
然后,从 https://jersey.java.net/download.html 下载 Jersey。解压后,会得到下面目录结构:
jaxrs-ri/ ├── api │ └── javax.ws.rs-api-2.0.1.jar ├── ext │ ├── aopalliance-repackaged-2.4.0-b34.jar │ ├── asm-debug-all-5.0.4.jar │ ├── hk2-api-2.4.0-b34.jar │ ├── hk2-locator-2.4.0-b34.jar │ ├── hk2-utils-2.4.0-b34.jar │ ├── javassist-3.18.1-GA.jar │ ├── javax.annotation-api-1.2.jar │ ├── javax.inject-2.4.0-b34.jar │ ├── javax.servlet-api-3.0.1.jar │ ├── jaxb-api-2.2.7.jar │ ├── jersey-guava-2.23.1.jar │ ├── org.osgi.core-4.2.0.jar │ ├── osgi-resource-locator-1.0.1.jar │ ├── persistence-api-1.0.jar │ └── validation-api-1.1.0.Final.jar ├── Jersey-LICENSE.txt ├── lib │ ├── jersey-client.jar │ ├── jersey-common.jar │ ├── jersey-container-servlet-core.jar │ ├── jersey-container-servlet.jar │ ├── jersey-media-jaxb.jar │ └── jersey-server.jar └── third-party-license-readme.txt
其中,子目录 ext 是它的依赖库。
2.1.2. 创建工程
这里以 Eclipse Java EE IDE 为例。
首先,创建一个工程,在菜单[File]->[New]->[Project]中选择“Dynamic Web Project”,给新工程命名为 UserManagement。
然后,把前面下载的 Jersey 包中的所有 jar 文件(包括解压后的 api,ext,lib 这 3 个子目录中所有 jar)复制到新创建工程的目录 WEB-INF/lib 中。
在包 com.tutorialspoint 下创建 3 个 java 文件。
User.java
package com.tutorialspoint;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private String profession;
public User(){}
public User(int id, String name, String profession){
this.id = id;
this.name = name;
this.profession = profession;
}
public int getId() {
return id;
}
@XmlElement
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
@XmlElement
public void setName(String name) {
this.name = name;
}
public String getProfession() {
return profession;
}
@XmlElement
public void setProfession(String profession) {
this.profession = profession;
}
}
UserDao.java
package com.tutorialspoint;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class UserDao {
public List<User> getAllUsers(){
List<User> userList = null;
try {
File file = new File("Users.dat");
if (!file.exists()) {
User user = new User(1, "Mahesh", "Teacher");
userList = new ArrayList<User>();
userList.add(user);
saveUserList(userList);
}
else{
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
userList = (List<User>) ois.readObject();
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return userList;
}
public User getUser(int id){
List<User> users = getAllUsers();
for(User user: users){
if(user.getId() == id){
return user;
}
}
return null;
}
public int addUser(User pUser){
List<User> userList = getAllUsers();
boolean userExists = false;
for(User user: userList){
if(user.getId() == pUser.getId()){
userExists = true;
break;
}
}
if(!userExists){
userList.add(pUser);
saveUserList(userList);
return 1;
}
return 0;
}
private void saveUserList(List<User> userList){
try {
File file = new File("Users.dat");
FileOutputStream fos;
fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(userList);
oos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
UserService.java
package com.tutorialspoint;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/UserService")
public class UserService {
UserDao userDao = new UserDao();
@GET
@Path("/users")
@Produces(MediaType.APPLICATION_XML)
public List<User> getUsers(){
return userDao.getAllUsers();
}
@GET
@Path("/users/{userid}")
@Produces(MediaType.APPLICATION_XML)
public User getUser(@PathParam("userid") int userid){
return userDao.getUser(userid);
}
@PUT
@Path("/users")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response createUser(@FormParam("id") int id,
@FormParam("name") String name,
@FormParam("profession") String profession,
@Context HttpServletResponse servletResponse) throws IOException{
User user = new User(id, name, profession);
int result = userDao.addUser(user);
if(result == 1){
return Response.status(201).entity(user.toString()).build();
} else {
return Response.status(400).entity("FAIL.").build();
}
}
}
工程看起来像这样:

Figure 1: 第一个 RESTful 应用
2.1.2.1. 创建 web.xml,导出 war 包
创建配置文件 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>User Management</display-name>
<servlet>
<servlet-name>Jersey RESTful Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.tutorialspoint</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey RESTful Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
有 Eclipse 菜单中,选择[File]->[export]->[Web]->[War File],可导出 war 文件 UserManagement.war。
2.1.3. 部署和测试
复制 UserManagement.war 到 tomcat 的 webapps 目录中,启动 tomcat 后,会自动解压 war 包到同一目录。
$ ls webapps/ docs examples host-manager manager ROOT UserManagement UserManagement.war
在 tomcat 安装目录,用 ./bin/startup.sh 启动 tomcat。
测试 GET(也可以直接在浏览器中打开相应网址):
$ curl -i http://localhost:8080/UserManagement/rest/UserService/users HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: application/xml Content-Length: 144 Date: Tue, 14 Jun 2016 11:33:47 GMT <?xml version="1.0" encoding="UTF-8" standalone="yes"?><users><user><id>1</id><name>Mahesh</name><profession>Teacher</profession></user></users> $ curl -i http://localhost:8080/UserManagement/rest/UserService/users/1 HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: application/xml Content-Length: 129 Date: Tue, 14 Jun 2016 12:39:49 GMT <?xml version="1.0" encoding="UTF-8" standalone="yes"?><user><id>1</id><name>Mahesh</name><profession>Teacher</profession></user>
PUT 的测试相对麻烦些,可参考:http://www.tutorialspoint.com/restful/restful_quick_guide.htm
2.2. Maven 中演示 Jersey 实例
和手动管理依赖相比,用 Maven 自动管理依赖更加方便。
下面将演示在 Maven 中使用 Jersey 的例子。这个例子比前面的更加简单,使用的是 jersey 自带的 quickstart 例子。
2.2.1. 用 Maven 创建工程
用 Maven 创建工程。这里创建 Jersey 自带的例子。如:
$ mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-webapp \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=simple-service-webapp -Dpackage=com.example \
-DarchetypeVersion=2.25
成功执行后,生成的目录结构如下:
$ find . . ./simple-service-webapp ./simple-service-webapp/pom.xml ./simple-service-webapp/src ./simple-service-webapp/src/main ./simple-service-webapp/src/main/java ./simple-service-webapp/src/main/java/com ./simple-service-webapp/src/main/java/com/example ./simple-service-webapp/src/main/java/com/example/MyResource.java ./simple-service-webapp/src/main/resources ./simple-service-webapp/src/main/webapp ./simple-service-webapp/src/main/webapp/index.jsp ./simple-service-webapp/src/main/webapp/WEB-INF ./simple-service-webapp/src/main/webapp/WEB-INF/web.xml
生成的文件“pom.xml”,其内容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>simple-service-webapp</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>simple-service-webapp</name>
<build>
<finalName>simple-service-webapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<inherited>true</inherited>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<!-- use the following artifactId if you don't need servlet 2.x compatibility -->
<!-- artifactId>jersey-container-servlet</artifactId -->
</dependency>
<!-- uncomment this to get JSON support
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
-->
</dependencies>
<properties>
<jersey.version>2.25</jersey.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
生成的文件“MyResource.java”,其内容如下:
package com.example;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/**
* Root resource (exposed at "myresource" path)
*/
@Path("myresource")
public class MyResource {
/**
* Method handling HTTP GET requests. The returned object will be sent
* to the client as "text/plain" media type.
*
* @return String that will be returned as a text/plain response.
*/
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getIt() {
return "Got it!";
}
}
生成的文件“web.xml”,其内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- This web.xml file is not required when using Servlet 3.0 container,
see implementation details http://jersey.java.net/nonav/documentation/latest/jax-rs.html -->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.example</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/webapi/*</url-pattern>
</servlet-mapping>
</web-app>
2.2.2. 部署测试(使用 jetty)
我们可以在第一步完成后,进入到工程目录,用 mvn package 生成 war 包,再部署到 Tomcat 中可进行测试。但这样很麻烦,我们将使用 Jetty 作为嵌入式的 Java Servlet Container,这样可以简化部署过程。
只需把 Jetty 的相关信息加入到 pom.xml 中即可。
......
<build>
<finalName>simple-service-webapp</finalName>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.2.17.v20160517</version>
</plugin>
......
最后,进入工程目录,执行 mvn jetty:run 即可一步完成编译部署。如:
$ cd simple-service-webapp
$ mvn jetty:run
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building simple-service-webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
......
[INFO] Started ServerConnector@5a50d9fc{HTTP/1.1}{0.0.0.0:8080}
[INFO] Started @4213ms
[INFO] Started Jetty Server
用浏览器(或 curl,wget 等工具)打开 http://127.0.0.1:8080/webapi/myresource 即可测试 REST Service 是否工作:
$ curl -i http://127.0.0.1:8080/webapi/myresource HTTP/1.1 200 OK Date: Wed, 07 Mar 2018 09:36:43 GMT Content-Type: text/plain Content-Length: 7 Server: Jetty(9.2.17.v20160517) Got it!
2.2.2.1. 启用热部署(有 class 文件修改就自动重启)
修改 Java 文件后,如何能自动应用新改动,则大大方便开发调试过程。这可以通过下面两个设置来实现:
1、设置自动编译 Java 为 class 文件。如果使用 Eclispe,点击“Project”->“Build Automatically”即可。
2、在 pom.xml 中配置 jetty 的参数 scanIntervalSeconds 。比如,配置每 2 秒钟扫描 class 文件:
......
<build>
<finalName>simple-service-webapp</finalName>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.2.17.v20160517</version>
<configuration>
<scanIntervalSeconds>2</scanIntervalSeconds>
</configuration>
</plugin>
......
2.2.3. 返回 JSON 数据
前面介绍的 Jersey 自带例子,返回的数据是“text/plain”类型。下面介绍返回 JSON 数据的实例。
首先,打开 pom.xml,增加 JSON 的支持库 jackson(这个例子中不使用 MOXy):
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
然后,把文件“MyResource.java”的内容修改为:
package com.example;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/**
* Root resource (exposed at "myresource" path)
*/
@Path("myresource")
public class MyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public MyJsonTest getIt() {
return new MyJsonTest("Got it!");
}
}
class MyJsonTest {
private String str;
public MyJsonTest(String input) {
str = input;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
重新部署测试如下:
$ curl -i http://127.0.0.1:8080/webapi/myresource
HTTP/1.1 200 OK
Date: Wed, 07 Mar 2018 09:54:31 GMT
Content-Type: application/json
Content-Length: 17
Server: Jetty(9.2.17.v20160517)
{"str":"Got it!"}
可发现,返回的数据已经是 JSON 了。