
RESTful Web 服務 - 快速指南
RESTful Web 服務 - 簡介
什麼是 REST 架構?
REST 代表 REpresentational State Transfer。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 服務,例如使用者管理,它具有以下功能:
序號 | URI | HTTP 方法 | POST 主體 | 結果 |
---|---|---|---|---|
1 | /UserService/users | GET | 空 | 顯示所有使用者的列表。 |
2 | /UserService/addUser | POST | JSON 字串 | 新增新使用者的詳細資訊。 |
3 | /UserService/getUser/:id | GET | 空 | 顯示使用者資訊。 |
RESTful Web 服務 - 環境搭建
本教程將指導您如何準備開發環境,以便開始使用Jersey 框架建立 RESTful Web 服務。Jersey 框架實現了JAX-RS 2.0 API,這是一個建立 RESTful Web 服務的標準規範。本教程還將教您如何在安裝 Jersey 框架之前在您的機器上安裝JDK、Tomcat和Eclipse。
安裝 Java 開發工具包 (JDK)
您可以從 Oracle 的 Java 網站下載最新版本的 SDK:Java SE 下載。您將在下載的檔案中找到安裝 JDK 的說明。按照給定的說明安裝和配置設定。最後設定PATH和JAVA_HOME環境變數以引用包含Java和Javac的目錄,通常分別為 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 值並按“確定”按鈕。
在 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
成功啟動後,如果一切正常,則您的螢幕應顯示以下結果:

安裝 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 中獲得目錄結構,如下面的螢幕截圖所示。

您將在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
成功啟動後,可以透過訪問https://:8080/訪問 Tomcat 附帶的預設 Web 應用程式。如果一切正常,則應顯示以下結果:

有關配置和執行 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,如下面的螢幕截圖所示:

成功建立專案後,您的專案資源管理器中將包含以下內容:

新增所需的庫
作為第二步,讓我們在專案中新增 Jersey 框架及其依賴項(庫)。將以下下載的 jersey zip 資料夾中的所有 jar 檔案複製到專案的 WEB-INF/lib 目錄中。
- \jaxrs-ri-2.17\jaxrs-ri\api
- \jaxrs-ri-2.17\jaxrs-ri\ext
- \jaxrs-ri-2.17\jaxrs-ri\lib
現在,右鍵單擊您的專案名稱UserManagement,然後選擇上下文選單中的可用選項:構建路徑→配置構建路徑以顯示 Java 構建路徑視窗。
現在,使用庫選項卡下可用的新增 JAR按鈕新增 WEBINF/lib 目錄中存在的 JAR。
建立原始檔
現在讓我們在UserManagement專案下建立實際的原始檔。首先,我們需要建立一個名為com.tutorialspoint的包。為此,右鍵單擊包資源管理器部分中的 src,然後選擇:新建→包。
接下來,我們將在 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。
執行程式
我們使用Postman(一個Chrome擴充套件程式)來測試我們的Web服務。
向UserManagement傳送請求以獲取所有使用者的列表。在POSTMAN中使用GET請求輸入https://:8080/UserManagement/rest/UserService/users,然後檢視以下結果。

恭喜您,您已成功建立了第一個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方法,例如GET、POST、DELETE、PUT等。
**URI** - 統一資源識別符號(URI),用於標識伺服器上的資源。
**HTTP版本** - 指示HTTP版本。例如,HTTP v1.1。
**請求頭** - 以鍵值對的形式包含HTTP請求訊息的元資料。例如,客戶端(或瀏覽器)型別、客戶端支援的格式、訊息正文的格式、快取設定等。
**請求正文** - 訊息內容或資源表示。
HTTP響應

HTTP響應有四個主要部分:
**狀態/響應程式碼** - 指示伺服器對請求資源的狀態。例如,404表示資源未找到,200表示響應正常。
**HTTP版本** - 指示HTTP版本。例如HTTP v1.1。
**響應頭** - 以鍵值對的形式包含HTTP響應訊息的元資料。例如,內容長度、內容型別、響應日期、伺服器型別等。
**響應正文** - 響應訊息內容或資源表示。
示例
正如我們在RESTful Web服務 - 第一個應用程式章節中所解釋的,讓我們在POSTMAN中使用GET請求輸入https://:8080/UserManagement/rest/UserService/users。如果您點選Postman傳送按鈕旁邊的“預覽”按鈕,然後點選“傳送”按鈕,您可能會看到以下輸出。

在這裡您可以看到,瀏覽器傳送了一個GET請求,並接收了一個XML格式的響應正文。
RESTful Web服務 - 地址
定址是指定位伺服器上存在的資源或多個資源。這類似於查詢某人的郵政地址。
REST架構中的每個資源都由其URI(統一資源識別符號)標識。URI具有以下格式:
<protocol>://<service-name>/<ResourceType>/<ResourceID>
URI的目的是定位託管Web服務的伺服器上的資源。請求的另一個重要屬性是VERB,它標識要對資源執行的操作。例如,在RESTful Web服務 - 第一個應用程式章節中,URI為**https://:8080/UserManagement/rest/UserService/users**,VERB為GET。
構建標準URI
設計URI時,需要考慮以下要點:
**使用複數名詞** - 使用複數名詞來定義資源。例如,我們使用users來標識使用者作為資源。
**避免使用空格** - 使用下劃線(_)或連字元(-)來使用長資源名稱。例如,使用authorized_users而不是authorized%20users。
**使用小寫字母** - 儘管URI不區分大小寫,但最好只使用小寫字母。
**保持向後相容性** - 由於Web服務是公共服務,因此一旦公開的URI應始終可用。如果URI更新,請使用HTTP狀態程式碼300將舊URI重定向到新URI。
**使用HTTP動詞** - 始終使用HTTP動詞(如GET、PUT和DELETE)對資源執行操作。在URI中使用操作名稱不是一個好習慣。
示例
以下是一個獲取使用者的糟糕URI示例。
https://:8080/UserManagement/rest/UserService/getUser/1
以下是一個獲取使用者的良好URI示例。
https://:8080/UserManagement/rest/UserService/users/1
RESTful Web服務 - 方法
正如我們在前面章節中討論的那樣,RESTful Web服務使用大量HTTP動詞來確定要對指定資源執行的操作。下表列出了最常用的HTTP動詞的示例。
序號 | HTTP方法、URI和操作 |
---|---|
1 | GET https://:8080/UserManagement/rest/UserService/users 獲取使用者列表。 (只讀) |
2 |
GET https://:8080/UserManagement/rest/UserService/users/1 獲取ID為1的使用者 (只讀) |
3 |
PUT https://:8080/UserManagement/rest/UserService/users/2 插入ID為2的使用者 (冪等) |
4 |
POST https://:8080/UserManagement/rest/UserService/users/2 更新ID為2的使用者 (N/A) |
5 |
DELETE https://:8080/UserManagement/rest/UserService/users/1 刪除ID為1的使用者 (冪等) |
6 |
OPTIONS https://:8080/UserManagement/rest/UserService/users 列出Web服務中支援的操作。 (只讀) |
7 |
HEAD https://:8080/UserManagement/rest/UserService/users 僅返回HTTP標頭,不返回正文。 (只讀) |
需要考慮以下幾點。
GET操作是隻讀且安全的。
PUT和DELETE操作是冪等的,這意味著無論呼叫這些操作多少次,其結果都將始終相同。
PUT和POST操作幾乎相同,區別僅在於結果,其中PUT操作是冪等的,而POST操作可能導致不同的結果。
示例
讓我們更新RESTful Web服務 - 第一個應用程式章節中建立的示例,以建立一個可以執行CRUD(建立、讀取、更新、刪除)操作的Web服務。為簡單起見,我們使用了檔案I/O來代替資料庫操作。
讓我們更新com.tutorialspoint包下的**User.java、UserDao.java**和**UserService.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); } @PUT @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; } @POST @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檔案,請按照以下路徑操作:**檔案→匯出→Web→WAR檔案**,最後選擇專案UserManagement和目標資料夾。要在Tomcat中部署WAR檔案,請將UserManagement.war放在**Tomcat安裝目錄→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 = " https://: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(); //initialize the tester tester.init(); //test get all users Web Service Method tester.testGetAllUsers(); //test get user Web Service Method tester.testGetUser(); //test update user Web Service Method tester.testUpdateUser(); //test add user Web Service Method tester.testAddUser(); //test delete user Web Service Method tester.testDeleteUser(); } //Test: Get list of all users //Test: Check if list is not empty 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 ); } //Test: Get User of id 1 //Test: Check if user is same as sample user 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 ); } //Test: Update User of id 1 //Test: Check if result is success 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) .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: testUpdateUser, Result: " + result); } //Test: Add User of id 2 //Test: Check if result is success 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) .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: testAddUser, Result: " + result ); } //Test: Delete User of id 2 //Test: Check if result is success 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://: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服務 - 快取
快取是指將伺服器響應儲存在客戶端本身,以便客戶端無需反覆向伺服器請求相同的資源。伺服器響應應包含有關如何執行快取的資訊,以便客戶端在一段時間內快取響應或從不快取伺服器響應。
以下是可以配置客戶端快取的伺服器響應標頭:
序號 | 標頭和描述 |
---|---|
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 天的過期日期。
不要設定過高的過期日期。
動態內容應僅快取幾個小時。

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 程式碼
序號 | 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 代表 JAVA API for RESTful Web Services。JAX-RS 是一個基於 JAVA 的程式語言 API 和規範,用於提供對建立 RESTful Web 服務的支援。其 2.0 版本於 2013 年 5 月 24 日釋出。JAX-RS 使用 Java SE 5 中提供的註解來簡化基於 JAVA 的 Web 服務的建立和部署。它還提供對建立 RESTful Web 服務客戶端的支援。
規範
以下是將資源對映為 Web 服務資源最常用的註解。
序號 | 註解 & 描述 |
---|---|
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 參考實現。