RESTful Web 服务 - 快速指南

RESTful Web 服务 - 简介

什么是 REST 架构?

REST 代表表述性状态转移。REST 是基于 Web 标准的架构,使用 HTTP 协议。它以资源为中心,每个组件都是资源,并且使用 HTTP 标准方法通过通用接口访问资源。REST 最初由 Roy Fielding 于 2000 年提出。

在 REST 架构中,REST 服务器仅提供对资源的访问,REST 客户端访问和修改资源。这里每个资源都由 URI/全局 ID 标识。REST 使用各种表示形式来表示资源,如文本、JSON、XML。JSON 是最流行的。

HTTP 方法

以下四种 HTTP 方法通常用于基于 REST 的架构。

  • GET −提供对资源的只读访问权限。

  • POST − 用于创建新资源。

  • DELETE − 用于删除资源。

  • PUT − 用于更新现有资源或创建新资源。

RESTFul Web 服务简介

Web 服务是用于在应用程序或系统之间交换数据的开放协议和标准的集合。使用各种编程语言编写并在各种平台上运行的软件应用程序可以使用 Web 服务通过 Internet 等计算机网络交换数据,方式类似于单台计算机上的进程间通信。这种互操作性(例如,Java 和 Python 之间,或 Windows 和 Linux 应用程序之间)归因于开放标准的使用。

基于 REST 架构的 Web 服务称为 RESTful Web 服务。这些 Web 服务使用 HTTP 方法来实现 REST 架构的概念。RESTful Web 服务通常定义 URI(统一资源标识符)服务,提供资源表示(例如 JSON)和 HTTP 方法集。

创建 RESTFul Web 服务

在下一章中,我们将创建一个 Web 服务,例如具有以下功能的用户管理 −

Sr.No. URI HTTP Method POST body Result
1 /UserService/users GETempty Show list of all the users.
2 /UserService/addUser POST JSON String Add details of new user.
3 /UserService/getUser/:id GETempty Show details of a user.

RESTful Web 服务 - 环境设置

本教程将指导您如何准备开发环境,以便开始使用 Jersey 框架 创建 RESTful Web 服务。Jersey 框架实现了 JAX-RS 2.0 API,这是创建 RESTful Web 服务的标准规范。本教程还将教您如何在设置 Jersey 框架之前在您的机器上设置 JDK、TomcatEclipse

设置 Java 开发工具包 (JDK)

您可以从 Oracle 的 Java 站点 − Java SE 下载 下载最新版本的 SDK。您将在下载的文件中找到安装 JDK 的说明。按照给出的说明安装和配置设置。最后,设置 PATHJAVA_HOME 环境变量,以引用包含 JavaJavac 的目录,通常分别为 java_install_dir/bin 和 java_install_dir。

如果您运行的是 Windows 并将 JDK 安装在 C:\jdk1.7.0_75 中,则必须在 C:\autoexec.bat 文件中放入以下行。

set PATH = C:\jdk1.7.0_75\bin;%PATH%
set JAVA_HOME = C:\jdk1.7.0_75

或者,在 Windows NT/2000/XP 上,您也可以右键单击"我的电脑"→ 选择"属性"→ 然后选择"高级"→ 然后选择"环境变量"。然后,您将更新 PATH 值并按下 OK 按钮。

在 Unix(Solaris、Linux 等)上,如果 SDK 安装在 /usr/local/jdk1.7.0_75 中,并且您使用 C Shell,则应将以下内容放入 .cshrc 文件中。

setenv PATH /usr/local/jdk1.7.0_75/bin:$PATH
setenv JAVA_HOME /usr/local/jdk1.7.0_75

或者,如果您使用集成开发环境 (IDE),如 Borland JBuilder、Eclipse、IntelliJ IDEA 或 Sun ONE Studio,请编译并运行一个简单的程序以确认 IDE 知道您在哪里安装了 Java,否则请按照 IDE 的给定文档进行正确的设置。

设置 Eclipse IDE

本教程中的所有示例都是使用 Eclipse IDE 编写的。因此,我建议您在计算机上安装最新版本的 Eclipse。

要安装 Eclipse IDE,请从 https://www.eclipse.org/downloads/ 下载最新的 Eclipse 二进制文件。下载安装程序后,将二进制分发包解压到方便的位置。例如,在 Windows 上位于 C:\eclipse,或在 Linux/Unix 上位于 /usr/local/eclipse,最后适当地设置 PATH 变量。

在 Windows 机器上,可以通过执行以下命令来启动 Eclipse,或者只需双击 eclipse.exe 即可。

%C:\eclipse\eclipse.exe

在 Unix(Solaris、Linux 等)机器上,可以通过执行以下命令来启动 Eclipse −

$/usr/local/eclipse/eclipse 

启动成功后,如果一切正常,您的屏幕应显示以下结果 −

Eclipse Home Page

设置 Jersey 框架库

现在,如果一切正常,您可以继续设置 Jersey 框架。以下是在您的机器上下载和安装框架的几个简单步骤。

  • 选择是否要在 Windows 或 Unix 上安装 Jersey,然后继续下一步,下载适用于 Windows 的 .zip 文件,然后下载适用于 Unix 的 .tz 文件。

  • 从以下链接下载最新版本的 Jersey 框架二进制文件 - https://jersey.java.net/download.html

  • 在撰写本教程时,我在 Windows 机器上下载了 jaxrs-ri-2.17.zip,当您解压下载的文件时,它将为您提供 E:\jaxrs-ri-2.17\jaxrs-ri 内的目录结构,如下所示屏幕截图。

Jaxrs Directory

您将在目录 C:\jaxrs-ri-2.17\jaxrs-ri\lib 中找到所有 Jersey 库,并在 C:\jaxrs-ri-2.17\jaxrs-ri\ext 中找到依赖项。请确保在此目录上正确设置 CLASSPATH 变量,否则在运行应用程序时会遇到问题。如果您使用的是 Eclipse,则无需设置 CLASSPATH,因为所有设置都将通过 Eclipse 完成。

设置 Apache Tomcat

您可以从 https://tomcat.apache.org/ 下载最新版本的 Tomcat。下载安装后,将二进制分发包解压到方便的位置。例如,Windows 上的 C:\apache-tomcat-7.0.59 或 Linux/Unix 上的 /usr/local/apache-tomcat-7.0.59,并将 CATALINA_HOME 环境变量设置为指向安装位置。

可以在 Windows 机器上执行以下命令来启动 Tomcat,或者只需双击 startup.bat 即可。

%CATALINA_HOME%\bin\startup.bat

C:\apache-tomcat-7.0.59\bin\startup.bat

可以在 Unix(Solaris、Linux 等)机器上执行以下命令来启动 Tomcat −

$CATALINA_HOME/bin/startup.sh

/usr/local/apache-tomcat-7.0.59/bin/startup.sh

成功启动后,访问 http://localhost:8080/ 即可使用 Tomcat 附带的默认 Web 应用程序。如果一切正常,则应显示以下结果 −

Tomcat

有关配置和运行 Tomcat 的更多信息,请参阅本页附带的文档。此信息也可在 Tomcat 网站上找到 − https://tomcat.apache.org.

可以在 Windows 机器上执行以下命令来停止 Tomcat −

%CATALINA_HOME%\bin\shutdown 

C:\apache-tomcat-7.0.59\bin\shutdown

可以通过在 Unix(Solaris、Linux 等)机器上执行以下命令来停止 Tomcat −

$CATALINA_HOME/bin/shutdown.sh

/usr/local/apache-tomcat-7.0.59/bin/shutdown.sh

完成最后一步后,您就可以继续执行第一个 Jersey 示例了,您将在下一章中看到该示例。

RESTful Web 服务 - 第一个应用程序

让我们开始使用 Jersey 框架编写实际的 RESTful Web 服务。在开始使用 Jersey 框架编写第一个示例之前,您必须确保已正确设置 Jersey 环境,如RESTful Web 服务 - 环境设置一章中所述。在这里,我还假设您对 Eclipse IDE 有一点实用知识。

那么,让我们继续编写一个简单的 Jersey 应用程序,它将公开一个 Web 服务方法来显示用户列表。

创建 Java 项目

第一步是使用 Eclipse IDE 创建动态 Web 项目。按照选项文件 → 新建 → 项目,最后从向导列表中选择动态 Web 项目向导。现在使用向导窗口将您的项目命名为 UserManagement,如以下屏幕截图所示 −

动态 Web 项目向导

成功创建项目后,您的 Project Explorer 中将显示以下内容 −

用户管理目录

添加所需的库

第二步,让我们在项目中添加 Jersey 框架及其依赖项(库)。从项目的 WEB-INF/lib 目录中下载的 jersey zip 文件夹的以下目录中复制所有 jar。

  • \jaxrs-ri-2.17\jaxrs-ri\api
  • \jaxrs-ri-2.17\jaxrs-ri\ext
  • \jaxrs-ri-2.17\jaxrs-ri\lib

现在,右键单击您的项目名称 UserManagement,然后按照上下文菜单中的可用选项 − Build Path →配置构建路径以显示 Java 构建路径窗口。

现在使用 Libraries 选项卡下的 Add JARs 按钮添加 WEBINF/lib 目录中的 JAR。

创建源文件

现在让我们在 UserManagement 项目下创建实际的源文件。首先,我们需要创建一个名为 com.tutorialspoint 的包。为此,右键单击包资源管理器部分中的 src,然后按照选项 − New → Package 进行操作。

接下来,我们将在 com.tutorialspoint 包下创建 UserService.java、User.java、UserDao.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; 
   } 
   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.util.List; 
import javax.ws.rs.GET; 
import javax.ws.rs.Path; 
import javax.ws.rs.Produces; 
import javax.ws.rs.core.MediaType;  
@Path("/UserService") 

public class UserService {  
   UserDao userDao = new UserDao();  
   @GET 
   @Path("/users") 
   @Produces(MediaType.APPLICATION_XML) 
   public List<User> getUsers(){ 
      return userDao.getAllUsers(); 
   }  
}

关于主程序,有两个要点需要注意,

UserService.java

  • 第一步是使用 @Path 注释为 UserService 指定 Web 服务的路径。

  • 第二步是使用 @Path 注释为 UserService 的方法指定特定 Web 服务方法的路径。

创建 Web.xml 配置文件

您需要创建一个 Web xml 配置文件,它是一个 XML 文件,用于为我们的应用程序指定 Jersey 框架 servlet。

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>

部署程序

创建完源文件和 Web 配置文件后,您就可以开始编译和运行程序了。为此,请使用 Eclipse 将应用程序导出为 war 文件,并将其部署到 tomcat 中。

要使用 eclipse 创建 WAR 文件,请按照选项 文件 → 导出 → Web → War 文件 操作,最后选择项目 UserManagement 和目标文件夹。要在 Tomcat 中部署 war 文件,请将 UserManagement.war 放在 Tomcat 安装目录 → webapps 目录并启动 Tomcat。

运行程序

我们使用 Chrome 扩展程序 Postman 来测试我们的 web 服务。

向 UserManagement 发出请求以获取所有用户的列表。使用 GET 请求将 http://localhost:8080/UserManagement/rest/UserService/users 放入 POSTMAN 中并查看以下结果。

RESTful API, 所有用户

恭喜,您已成功创建第一个 RESTful 应用程序。

RESTful Web 服务 - 资源

什么是资源?

REST 架构将每个内容视为资源。这些资源可以是文本文件、HTML 页面、图像、视频或动态业务数据。REST 服务器仅提供对资源的访问,REST 客户端访问和修改资源。这里每个资源都由 URI/全局 ID 标识。REST 使用各种表示来表示资源,包括文本、JSON、XML。最流行的资源表示是 XML 和 JSON。

资源表示

REST 中的资源类似于面向对象编程中的对象,或者类似于数据库中的实体。一旦确定了资源,就需要使用标准格式来决定其表示形式,以便服务器可以按照上述格式发送资源,并且客户端可以理解相同的格式。

例如,在 RESTful Web 服务 - 第一个应用程序 一章中,用户是使用以下 XML 格式表示的资源 −

<user> 
   <id>1</id> 
   <name>Mahesh</name>
   <profession>Teacher</profession> 
</user> 

相同资源可以用 JSON 格式表示如下 −

{ 
   "id":1, 
   "name":"Mahesh", 
   "profession":"Teacher" 
}

良好的资源表现

REST 不对资源表示形式施加任何限制。客户端可以请求 JSON 表示,而另一个客户端可以请求相同资源的 XML 表示到服务器,依此类推。REST 服务器有责任以客户端可以理解的格式将资源传递给客户端。

以下是在 RESTful Web 服务中设计资源表示格式时需要考虑的一些要点。

  • 可理解性 − 服务器和客户端都应该能够理解和利用资源的表示格式。

  • 完整性 − 格式应该能够完整地表示资源。例如,一个资源可以包含另一个资源。格式应该能够表示资源的简单和复杂结构。

  • 可链接性 −一个资源可以链接到另一个资源,格式应该能够处理这种情况。

但是,目前大多数 Web 服务都使用 XML 或 JSON 格式表示资源。有许多库和工具可用于理解、解析和修改 XML 和 JSON 数据。

RESTful Web 服务 - 消息

RESTful Web 服务使用 HTTP 协议作为客户端和服务器之间的通信媒介。客户端以 HTTP 请求的形式发送消息,服务器以 HTTP 响应的形式做出响应。这种技术称为消息传递。这些消息包含消息数据和元数据,即有关消息本身的信息。让我们来看看 HTTP 1.1 的 HTTP 请求和 HTTP 响应消息。

HTTP 请求

HTTP 请求

HTTP 请求有五个主要部分 −

  • 动词 − 表示 HTTP 方法,例如 GET、POST、DELETE、PUT 等。

  • URI −统一资源标识符 (URI),用于标识服务器上的资源。

  • HTTP 版本 − 表示 HTTP 版本。例如,HTTP v1.1。

  • 请求标头 − 包含 HTTP 请求消息的元数据(键值对)。例如,客户端(或浏览器)类型、客户端支持的格式、消息正文的格式、缓存设置等。

  • 请求正文 −消息内容或资源表示。

HTTP 响应

HTTP 响应

HTTP 响应有四个主要部分 −

  • 状态/响应代码 − 指示所请求资源的服务器状态。例如,404 表示未找到资源,200 表示响应正常。

  • HTTP 版本 − 指示 HTTP 版本。例如 HTTP v1.1。

  • 响应标头 − 包含 HTTP 响应消息的元数据作为键值对。例如,内容长度、内容类型、响应日期、服务器类型等。

  • 响应主体 − 响应消息内容或资源表示。

示例

正如我们在RESTful Web 服务 - 第一个应用程序章节中解释的那样,让我们​​将 http://localhost:8080/UserManagement/rest/UserService/users 放入 POSTMAN 中,并发出 GET 请求。如果您单击 Postman 发送按钮附近的预览按钮,然后单击发送按钮,您可能会看到以下输出。

HTTP 请求/响应

在这里您可以看到,浏览器发送了一个 GET 请求并收到了 XML 格式的响应主体。

RESTful Web 服务 - 寻址

寻址是指定位服务器上的一个或多个资源。这类似于定位某人的邮政地址。

REST 架构中的每个资源都由其 URI(统一资源标识符)标识。URI 的格式如下 −

<protocol>://<service-name>/<ResourceType>/<ResourceID>

URI 的目的是定位托管 Web 服务的服务器上的资源。请求的另一个重要属性是 VERB,它标识要对资源执行的操作。例如,在 RESTful Web 服务 - 第一个应用程序 一章中,URI 为 http://localhost:8080/UserManagement/rest/UserService/users,VERB 为 GET。

构建标准 URI

以下是在设计 URI 时需要考虑的重要事项 −

  • 使用复数名词 − 使用复数名词定义资源。例如,我们使用用户将用户标识为资源。

  • 避免使用空格 − 使用长资源名称时,请使用下划线 (_) 或连字符 (-)。例如,使用 authorized_users 而不是 authorized%20users。

  • 使用小写字母 − 尽管 URI 不区分大小写,但最好只使用小写字母来保留 URL。

  • 保持向后兼容性 − 由于 Web 服务是公共服务,因此一旦公开的 URI 应始终可用。如果 URI 更新,请使用 HTTP 状态代码 300 将旧 URI 重定向到新 URI。

  • 使用 HTTP 动词 − 始终使用 HTTP 动词(如 GET、PUT 和 DELETE)对资源执行操作。在 URI 中使用操作名称并不好。

示例

以下是获取用户的不良 URI 示例。

http://localhost:8080/UserManagement/rest/UserService/getUser/1

以下是获取用户的良好 URI 示例。

http://localhost:8080/UserManagement/rest/UserService/users/1

RESTful Web 服务 - 方法

正如我们目前所讨论的,RESTful Web 服务大量使用 HTTP 动词来确定要在指定资源上执行的操作。下表列出了 HTTP 动词的常见用法示例。

HTTP 方法 GET
URI http://localhost:8080/UserManagement/rest/UserService/users
操作 获取用户列表
操作类型 只读
HTTP方法 GET
URI http://localhost:8080/UserManagement/rest/UserService/users/1
操作 获取 ID 为 1 的用户
操作类型 只读
HTTP方法 POST
URI http://localhost:8080/UserManagement/rest/UserService/users/2
操作 插入 ID 为 2 的用户
操作类型 非幂等
HTTP方法 PUT
URI http://localhost:8080/UserManagement/rest/UserService/users/2
操作 更新 ID 为 2 的用户
操作类型 N/A
HTTP方法 DELETE
URI http://localhost:8080/UserManagement/rest/UserService/users/1
操作 删除 ID 为 1 的用户
操作类型 幂等
HTTP方法 选项
URI http://localhost:8080/UserManagement/rest/UserService/users
操作 列出 Web 服务中支持的操作
操作类型 只读
HTTP方法 HEAD
URI http://localhost:8080/UserManagement/rest/UserService/users
操作 仅返回 HTTP 标头,无正文
操作类型 只读

以下是需要考虑的重要事项:

  • GET 操作是只读的,并且是安全的。

  • PUT 和 DELETE 操作是幂等的,这意味着无论调用这些操作多少次,它们的结果始终相同。

  • PUT 和 POST 操作几乎相同,不同之处仅在于结果,其中 PUT 操作是幂等的,而 POST 操作可能导致不同的结果。

示例

让我们更新在 RESTful Web 服务 - 第一个应用程序 教程中创建的示例,以创建可以执行 CRUD(创建、读取、更新、删除)操作的 Web 服务。为简单起见,我们使用文件 I/O 来替换数据库操作。

更新 com.tutorialspoint 包下的 UserService.javaUser.javaUserDao.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;
   }	

   @Override
   public boolean equals(Object object){
      if(object == null){
         return false;
      }else if(!(object instanceof User)){
         return false;
      }else {
         User user = (User)object;
         if(id == user.getId()
            && name.equals(user.getName())
            && profession.equals(user.getProfession())
         ){
            return true;
         }			
      }
      return false;
   }	
}

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;
   }

   public int updateUser(User pUser){
      List<User> userList = getAllUsers();

      for(User user: userList){
         if(user.getId() == pUser.getId()){
            int index = userList.indexOf(user);			
            userList.set(index, pUser);
            saveUserList(userList);
            return 1;
         }
      }		
      return 0;
   }

   public int deleteUser(int id){
      List<User> userList = getAllUsers();

      for(User user: userList){
         if(user.getId() == id){
            int index = userList.indexOf(user);			
            userList.remove(index);
            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.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
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;

@Path("/UserService")
public class UserService {
	
   UserDao userDao = new UserDao();
   private static final String SUCCESS_RESULT="<result>success</result>";
   private static final String FAILURE_RESULT="<result>failure</result>";

   @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);
   }

   @POST
   @Path("/users")
   @Produces(MediaType.APPLICATION_XML)
   @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
   public String 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 SUCCESS_RESULT;
      }
      return FAILURE_RESULT;
   }

   @PUT
   @Path("/users")
   @Produces(MediaType.APPLICATION_XML)
   @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
   public String updateUser(@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.updateUser(user);
      if(result == 1){
         return SUCCESS_RESULT;
      }
      return FAILURE_RESULT;
   }

   @DELETE
   @Path("/users/{userid}")
   @Produces(MediaType.APPLICATION_XML)
   public String deleteUser(@PathParam("userid") int userid){
      int result = userDao.deleteUser(userid);
      if(result == 1){
         return SUCCESS_RESULT;
      }
      return FAILURE_RESULT;
   }

   @OPTIONS
   @Path("/users")
   @Produces(MediaType.APPLICATION_XML)
   public String getSupportedOperations(){
      return "<operations>GET, PUT, POST, DELETE</operations>";
   }
}

现在使用 Eclipse,将您的应用程序导出为 war 文件,并将其部署到 tomcat 中。要使用 eclipse 创建 WAR 文件,请按照选项 File -> export -> Web -> War File 操作,最后选择项目 UserManagement 和目标文件夹。要在 Tomcat 中部署 war 文件,请将 UserManagement.war 放在 Tomcat Installation Directory -> webapps 目录中,然后启动 Tomcat。

测试 Web 服务

Jersey 提供 API 来创建 Web 服务客户端以测试 Web 服务。我们在同一项目的 com.tutorialspoint 包下创建了一个示例测试类 WebServiceTester.java

WebServiceTester.java

package com.tutorialspoint;

import java.util.List;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;

public class WebServiceTester  {

   private Client client;
   private String REST_SERVICE_URL = "http://localhost:8080/UserManagement/rest/UserService/users";
   private static final String SUCCESS_RESULT="<result>success</result>";
   private static final String PASS = "pass";
   private static final String FAIL = "fail";

   private void init(){
      this.client = ClientBuilder.newClient();
   }

   public static void main(String[] args){
    WebServiceTester tester  = new WebServiceTester();
     //初始化测试器
     tester .init();
     //测试获取所有用户Web服务方法
     tester.testGetAllUsers();
     //测试获取用户Web服务方法
     tester.testGetUser();
     //测试更新用户Web服务方法
     tester.testUpdateUser();
     //测试添加用户Web服务方法
     tester.testAddUser();
     //测试删除用户Web服务方法
     tester.testDeleteUser();
   }
    //测试:获取所有用户列表
    //测试:检查列表是否不为空
   private void testGetAllUsers(){
      GenericType<List<User>> list = new GenericType<List<User>>() {};
      List<User> users = client
         .target(REST_SERVICE_URL)
         .request(MediaType.APPLICATION_XML)
         .get(list);
      String result = PASS;
      if(users.isEmpty()){
         result = FAIL;
      }
      System.out.println("Test case name: testGetAllUsers, Result: " + result );
   }
    //测试:获取 ID 为 1 的用户
    //测试:检查用户是否与示例用户相同
   private void testGetUser(){
      User sampleUser = new User();
      sampleUser.setId(1);

      User user = client
         .target(REST_SERVICE_URL)
         .path("/{userid}")
         .resolveTemplate("userid", 1)
         .request(MediaType.APPLICATION_XML)
         .get(User.class);
      String result = FAIL;
      if(sampleUser != null && sampleUser.getId() == user.getId()){
         result = PASS;
      }
      System.out.println("Test case name: testGetUser, Result: " + result );
   }
    //测试:更新 ID 为 1 的用户
    //测试:检查结果是否成功 XML。
   private void testUpdateUser(){
      Form form = new Form();
      form.param("id", "1");
      form.param("name", "suresh");
      form.param("profession", "clerk");

      String callResult = client
         .target(REST_SERVICE_URL)
         .request(MediaType.APPLICATION_XML)
         .put(Entity.entity(form,
            MediaType.APPLICATION_FORM_URLENCODED_TYPE),
            String.class);
      String result = PASS;
      if(!SUCCESS_RESULT.equals(callResult)){
         result = FAIL;
      }

      System.out.println("Test case name: testUpdateUser, Result: " + result );
   }
    //测试:添加 ID 为 2 的用户
    //测试:检查结果是否成功 XML。
   private void testAddUser(){
      Form form = new Form();
      form.param("id", "2");
      form.param("name", "naresh");
      form.param("profession", "clerk");

      String callResult = client
         .target(REST_SERVICE_URL)
         .request(MediaType.APPLICATION_XML)
         .post(Entity.entity(form,
            MediaType.APPLICATION_FORM_URLENCODED_TYPE),
            String.class);
   
      String result = PASS;
      if(!SUCCESS_RESULT.equals(callResult)){
         result = FAIL;
      }

      System.out.println("Test case name: testAddUser, Result: " + result );
   }
    //测试:删除 id 为 2 的用户
    //测试:检查结果是否成功 XML。
   private void testDeleteUser(){
      String callResult = client
         .target(REST_SERVICE_URL)
         .path("/{userid}")
         .resolveTemplate("userid", 2)
         .request(MediaType.APPLICATION_XML)
         .delete(String.class);

      String result = PASS;
      if(!SUCCESS_RESULT.equals(callResult)){
         result = FAIL;
      }

      System.out.println("Test case name: testDeleteUser, Result: " + result );
   }
}

现在使用 Eclipse 运行测试器。右键单击文件,然后按照选项 以 Java 应用程序身份运行。您将在 Eclipse 控制台中看到以下结果 −

Test case name: testGetAllUsers, Result: pass
Test case name: testGetUser, Result: pass
Test case name: testUpdateUser, Result: pass
Test case name: testAddUser, Result: pass
Test case name: testDeleteUser, Result: pass

RESTful Web 服务 - 无状态

根据 REST 架构,RESTful Web 服务不应在服务器上保留客户端状态。此限制称为无状态。客户端有责任将其上下文传递给服务器,然后服务器可以存储此上下文以处理客户端的进一步请求。例如,服务器维护的会话由客户端传递的会话标识符标识。

RESTful Web 服务应遵守此限制。我们在RESTful Web 服务 - 方法一章中看到,Web 服务方法不存储它们被调用的客户端的任何信息。

考虑以下 URL −

https://localhost:8080/UserManagement/rest/UserService/users/1

如果您使用浏览器或使用基于 Java 的客户端或使用 Postman 访问上述 URL,结果将始终是 Id 为 1 的用户 XML,因为服务器不存储有关客户端的任何信息。

<user> 
   <id>1</id> 
   <name>mahesh</name> 
   <profession>1</profession> 
</user>

无状态的优点

以下是 RESTful Web 服务中无状态的优点 −

  • Web 服务可以独立处理每个方法请求。

  • Web 服务不需要维护客户端以前的交互。它简化了应用程序设计。

  • 由于 HTTP 本身是一种无状态协议,RESTful Web 服务可以与 HTTP 协议无缝协作。

无状态的缺点

以下是 RESTful Web 服务中无状态的缺点 −

  • Web 服务需要在每个请求中获取额外信息,然后进行解释以获取客户端的状态,以防需要处理客户端交互。

RESTful Web 服务 - 缓存

缓存是指将服务器响应存储在客户端本身中,这样客户端就不必一次又一次地向服务器发出对同一资源的请求。服务器响应应该包含有关如何进行缓存的信息,以便客户端在一段时间内缓存响应或从不缓存服务器响应。

以下是服务器响应可以具有的标头,以便配置客户端的缓存 −

Sr.No. 标头 &描述

1

Date

资源创建的日期和时间。

2

Last Modified

资源上次修改的日期和时间。

3

Cache-Control

用于控制的主要标头缓存。

4

Expires

缓存的到期日期和时间。

5

Age

从服务器获取资源后的持续时间(秒)。

Cache-Control 标头

以下是 Cache-Control 标头的详细信息 −

序号 指令 &描述

1

Public

表示资源可由任何组件缓存。

2

Private

表示资源只能由客户端和服务器缓存,任何中介都不能缓存资源。

3

no-cache/no-store

表示资源不可缓存。

4

max-age

表示缓存在 max-age 内有效(以秒为单位)。此后,客户端必须发出另一个请求。

5

must-revalidate

如果已超过 max-age,则指示服务器重新验证资源。

最佳实践

  • 始终保持静态内容(如图像、CSS、JavaScript)可缓存,有效期为 2 到 3 天。

  • 切勿将有效期设置得太高。

  • 动态内容应仅缓存几个小时。

Cache-Control 的最佳实践

RESTful Web 服务 - 安全性

由于 RESTful Web 服务使用 HTTP URL 路径,因此以与网站相同的方式保护 RESTful Web 服务非常重要。

以下是在设计 RESTful Web 服务时应遵循的最佳实践 −

  • 验证 − 验证服务器上的所有输入。保护您的服务器免受 SQL 或 NoSQL 注入攻击。

  • 基于会话的身份验证 − 每当向 Web 服务方法发出请求时,使用基于会话的身份验证对用户进行身份验证。

  • URL 中没有敏感数据 −切勿在 URL 中使用用户名、密码或会话令牌,这些值应通过 POST 方法传递给 Web 服务。

  • 方法执行限制 − 允许限制使用 GET、POST 和 DELETE 方法等方法。GET 方法不应能够删除数据。

  • 验证格式错误的 XML/JSON − 检查传递给 Web 服务方法的输入是否格式正确。

  • 抛出一般错误消息 − Web 服务方法应使用 HTTP 错误消息(如 403)来显示禁止访问等。

HTTP 代码

Sr.No. HTTP 代码和说明

1

200

OK − 表示成功。

2

201

CREATED − 当使用 POST 或 PUT 请求成功创建资源时。使用位置标头返回指向新创建的资源的链接。

3

204

NO CONTENT − 当响应主体为空时。例如,DELETE 请求。

4

304

NOT MODIFIED − 用于在有条件的 GET 请求的情况下减少网络带宽使用。响应正文应为空。标头应包含日期、位置等。

5

400

BAD REQUEST − 表示提供了无效的输入。例如,验证错误、数据缺失。

6

401

UNAUTHORIZED − 表示用户正在使用无效或错误的身份验证令牌。

7

403

FORBIDDEN − 表示用户无权访问正在使用的方法。例如,删除没有管理员权限的访问权限。

8

404

NOT FOUND − 表示该方法不可用。

9

409

CONFLICT − 表示执行该方法时出现冲突的情况。例如,添加重复条目。

10

500

INTERNAL SERVER ERROR − 表示服务器在执行该方法时抛出了一些异常。

RESTful Web 服务 - Java (JAX-RS)

JAX-RS 代表 RESTful Web 服务的 JAVA API。JAX-RS 是一种基于 JAVA 的编程语言 API 和规范,用于为创建的 RESTful Web 服务提供支持。其 2.0 版本于 2013 年 5 月 24 日发布。JAX-RS 使用 Java SE 5 提供的注释来简化基于 JAVA 的 Web 服务创建和部署的开发。它还为创建 RESTful Web 服务的客户端提供支持。

规范

以下是将资源映射为 Web 服务资源的最常用注释。

Sr.No. 注释 &描述

1

@Path

资源类/方法的相对路径。

2

@GET

HTTP Get 请求,用于获取资源。

3

@PUT

HTTP PUT 请求,用于更新资源。

4

@POST

HTTP POST 请求,用于创建新资源。

5

@DELETE

HTTP DELETE 请求,用于删除资源。

6

@HEAD

HTTP HEAD 请求,用于获取方法的状态可用性。

7

@Produces

声明 Web 服务生成的 HTTP 响应。例如,APPLICATION/XML、TEXT/HTML、APPLICATION/JSON 等。

8

@Consumes

声明 HTTP 请求类型。例如,application/x-www-formurlencoded 表示在 POST 请求期间接受 HTTP 主体中的表单数据。

9

@PathParam

将传递给方法的参数绑定到路径中的值。

10

@QueryParam

将传递给方法的参数绑定到路径中的查询参数。

11

@MatrixParam

将传递给方法的参数绑定到路径中的 HTTP 矩阵参数。

12

@HeaderParam

将传递给方法的参数绑定到 HTTP 标头。

13

@CookieParam

将传递给方法的参数绑定到Cookie。

14

@FormParam

将传递给方法的参数绑定到表单值。

15

@DefaultValue

为传递给方法的参数分配默认值。

16

@Context

上下文资源。例如,HTTPRequest 作为上下文。

注意 − 我们在 RESTful Web 服务 - 第一个应用程序RESTful Web 服务 - 方法 章节中使用了 Jersey,这是 Oracle 的 JAX-RS 2.0 参考实现。