使用Spark构建简单的RESTful API
免責聲明 :這篇文章是關于名為Spark的Java微型Web框架的,而不是關于數據處理引擎Apache Spark的 。
在此博客文章中,我們將看到如何使用Spark構建簡單的Web服務。 如免責聲明中所述,Spark是受Ruby框架Sinatra啟發的Java微型Web框架。 Spark的目的是簡化操作,僅提供最少的功能集。 但是,它提供了用幾行Java代碼構建Web應用程序所需的一切。
入門
假設我們有一個帶有一些屬性的簡單域類和一個提供一些基本CRUD功能的服務:
public?class?User?{private?String?id;private?String?name;private?String?email;//?getter/setter }public?class?UserService?{//?returns?a?list?of?all?userspublic?List<User>?getAllUsers()?{?..?}//?returns?a?single?user?by?idpublic?User?getUser(String?id)?{?..?}//?creates?a?new?userpublic?User?createUser(String?name,?String?email)?{?..?}//?updates?an?existing?userpublic?User?updateUser(String?id,?String?name,?String?email)?{?..?} }現在,我們希望將UserService的功能公開為RESTful API(為簡單起見,我們將跳過REST的超媒體部分)。 為了訪問,創建和更新用戶對象,我們要使用以下URL模式:
| 得到 | /用戶 | 獲取所有用戶的列表 | 
| 得到 | / users / <id> | 獲取特定用戶 | 
| 開機自檢 | /用戶 | 創建一個新用戶 | 
| 放 | / users / <id> | 更新用戶 | 
返回的數據應為JSON格式。
要開始使用Spark,我們需要以下Maven依賴項:
<dependency><groupId>com.sparkjava</groupId><artifactId>spark-core</artifactId><version>2.0.0</version> </dependency> <dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.7</version> </dependency>Spark使用SLF4J進行日志記錄,因此我們需要SLF4J活頁夾才能查看日志和錯誤消息。 在此示例中,我們為此目的使用slf4j-simple依賴項。 但是,您也可以使用Log4j或您喜歡的任何其他綁定程序。 在類路徑中使用slf4j-simple足以在控制臺中查看日志輸出。
我們還將使用GSON生成JSON輸出,并使用JUnit編寫簡單的集成測試。 您可以在完整的pom.xml中找到這些依賴項。
返回所有用戶
現在該創建一個負責處理傳入請求的類了。 我們首先實現GET / users請求,該請求應返回所有用戶的列表。
import?static?spark.Spark.*;public?class?UserController?{public?UserController(final?UserService?userService)?{get("/users",?new?Route()?{@Overridepublic?Object?handle(Request?request,?Response?response)?{//?process?requestreturn?userService.getAllUsers();}});//?more?routes} }注意第一行中的spark.Spark。*的靜態導入。 這使我們可以訪問各種靜態方法,包括get(),post(),put()等。 在構造函數中,get()方法用于注冊一個Route,該Route偵聽/ users上的GET請求。 路由負責處理請求。 每當發出GET / users請求時,都會調用handle()方法。 在handle()內部,我們返回一個應發送給客戶端的對象(在本例中為所有用戶的列表)。
Spark從Java 8 Lambda表達式中受益匪淺。 Route是一個功能接口(僅包含一種方法),因此我們可以使用Java 8 Lambda表達式來實現它。 使用Lambda表達式,上面的Route定義如下所示:
get("/users",?(req,?res)?->?userService.getAllUsers());要啟動該應用程序,我們必須創建一個簡單的main()方法。 在main()內部,我們創建服務的實例,并將其傳遞給我們新創建的UserController:
public?class?Main?{public?static?void?main(String[]?args)?{new?UserController(new?UserService());} }如果現在運行main(),Spark將啟動一個偵聽端口4567的嵌入式Jetty服務器。我們可以通過啟動GET http:// localhost:4567 / users請求來測試我們的第一個路由。
如果服務返回包含兩個用戶對象的列表,則響應主體可能如下所示:
[com.mscharhag.sparkdemo.User@449c23fd,?com.mscharhag.sparkdemo.User@437b26fe]顯然,這不是我們想要的回應。
 Spark使用名為ResponseTransformer的接口將路由返回的對象轉換為實際的HTTP響應。 
 ReponseTransformer看起來像這樣: 
ResponseTransformer具有一個方法,該方法接受一個對象并返回此對象的String表示形式。 ResponseTransformer的默認實現只是在傳遞的對象上調用toString()(它創建如上所示的輸出)。
由于我們要返回JSON,因此我們必須創建一個ResponseTransformer,將傳遞的對象轉換為JSON。 為此,我們使用帶有兩個靜態方法的小型JsonUtil類:
public?class?JsonUtil?{public?static?String?toJson(Object?object)?{return?new?Gson().toJson(object);}public?static?ResponseTransformer?json()?{return?JsonUtil::toJson;} }toJson()是使用GSON將對象轉換為JSON的通用方法。 第二種方法利用Java 8方法引用來返回ResponseTransformer實例。 ResponseTransformer還是一個功能接口,因此可以通過提供適當的方法實現(toJson())來滿足它。 因此,每當調用json()時,我們都會獲得一個新的ResponseTransformer,它利用了我們的toJson()方法。
在我們的UserController中,我們可以將ResponseTransformer作為第三個參數傳遞給Spark的get()方法:
import?static?com.mscharhag.sparkdemo.JsonUtil.*;public?class?UserController?{public?UserController(final?UserService?userService)?{get("/users",?(req,?res)?->?userService.getAllUsers(),?json());...} }再次注意第一行中JsonUtil。*的靜態導入。 這使我們可以選擇僅通過調用json()來創建新的ResponseTransformer。
現在,我們的響應如下所示:
[{"id":?"1866d959-4a52-4409-afc8-4f09896f38b2","name":?"john","email":?"john@foobar.com" },{"id":?"90d965ad-5bdf-455d-9808-c38b72a5181a","name":?"anna","email":?"anna@foobar.com" }]我們還有一個小問題。 返回的響應帶有錯誤的Content-Type 。 為了解決這個問題,我們可以注冊一個設置JSON Content-Type的Filter:
after((req,?res)?->?{res.type("application/json"); });過濾器還是一個功能接口,因此可以通過一個簡短的Lambda表達式實現。 在我們的Route處理完請求后,過濾器會將每個響應的Content-Type更改為application / json。 我們還可以使用before()代替after()來注冊過濾器。 然后,在路由處理請求之前,將調用過濾器。
GET / users請求現在應該可以工作了!
返回特定用戶
要返回特定用戶,我們只需在UserController中創建一條新路由:
get("/users/:id",?(req,?res)?->?{String?id?=?req.params(":id");User?user?=?userService.getUser(id);if?(user?!=?null)?{return?user;}res.status(400);return?new?ResponseError("No?user?with?id?'%s'?found",?id); },?json());使用req.params(“:id”),我們可以從URL獲取:id路徑參數。 我們將此參數傳遞給我們的服務以獲取相應的用戶對象。 如果未找到具有傳遞ID的用戶,則假定服務返回null。 在這種情況下,我們將HTTP狀態代碼更改為400(錯誤請求)并返回一個錯誤對象。
ResponseError是一個小的幫助程序類,我們用于將錯誤消息和異常轉換為JSON。 看起來像這樣:
public?class?ResponseError?{private?String?message;public?ResponseError(String?message,?String...?args)?{this.message?=?String.format(message,?args);}public?ResponseError(Exception?e)?{this.message?=?e.getMessage();}public?String?getMessage()?{return?this.message;} }現在,我們可以使用以下請求查詢單個用戶:
GET / users / 5f45a4ff-35a7-47e8-b731-4339c84962be
如果存在具有此ID的用戶,我們將收到如下所示的響應:
{"id":?"5f45a4ff-35a7-47e8-b731-4339c84962be","name":?"john","email":?"john@foobar.com" }如果我們使用無效的用戶ID,將創建ResponseError對象并將其轉換為JSON。 在這種情況下,響應如下所示:
{"message":?"No?user?with?id?'foo'?found" }創建和更新用戶
創建和更新用戶非常容易。 就像返回所有用戶的列表一樣,這是通過單個服務調用完成的:
post("/users",?(req,?res)?->?userService.createUser(req.queryParams("name"),req.queryParams("email") ),?json());put("/users/:id",?(req,?res)?->?userService.updateUser(req.params(":id"),req.queryParams("name"),req.queryParams("email") ),?json());要為HTTP POST或PUT請求注冊路由,我們只需使用Spark的靜態post()和put()方法。 在Route內部,我們可以使用req.queryParams()訪問HTTP POST參數。
為了簡單起見(并顯示另一個Spark功能),我們不在路由內進行任何驗證。 相反,我們假定如果傳入無效值,則服務將引發IllegalArgumentException。
Spark為我們提供了注冊ExceptionHandlers的選項。 如果在處理路由時引發Exception,則將調用ExceptionHandler。 ExceptionHandler是我們可以使用Java 8 Lambda表達式實現的另一個單一方法接口:
exception(IllegalArgumentException.class,?(e,?req,?res)?->?{res.status(400);res.body(toJson(new?ResponseError(e))); });在這里,我們創建一個ExceptionHandler,如果拋出IllegalArgumentException則調用它。 捕獲的Exception對象作為第一個參數傳遞。 我們將響應代碼設置為400,并在響應正文中添加一條錯誤消息。
如果當email參數為空時服務拋出IllegalArgumentException,我們可能會收到如下響應:
{"message":?"Parameter?'email'?cannot?be?empty" }控制器的完整資源可以在這里找到。
測試中
由于Spark的簡單性質,因此為示例應用程序編寫集成測試非常容易。
讓我們從基本的JUnit測試設置開始:
public?class?UserControllerIntegrationTest?{@BeforeClasspublic?static?void?beforeClass()?{Main.main(null);}@AfterClasspublic?static?void?afterClass()?{Spark.stop();}... }在beforeClass()中,我們通過簡單地運行main()方法來啟動應用程序。 所有測試完成后,我們調用Spark.stop()。 這將停止運行我們的應用程序的嵌入式服務器。
之后,我們可以在測試方法中發送HTTP請求,并驗證我們的應用程序返回了正確的響應。 一個發送創建新用戶請求的簡單測試如下所示:
@Test public?void?aNewUserShouldBeCreated()?{TestResponse?res?=?request("POST",?"/users?name=john&email=john@foobar.com");Map<String,?String>?json?=?res.json();assertEquals(200,?res.status);assertEquals("john",?json.get("name"));assertEquals("john@foobar.com",?json.get("email"));assertNotNull(json.get("id")); }request()和TestResponse是兩個小型的自制測試實用程序。 request()將HTTP請求發送到傳遞的URL,并返回TestResponse實例。 TestResponse只是一些HTTP響應數據的小包裝。 request()和TestResponse的源包含在GitHub上的完整測試類中 。
結論
與其他Web框架相比,Spark僅提供了少量功能。 但是,它是如此簡單,您可以在幾分鐘之內構建小型Web應用程序(即使您以前從未使用過Spark)。 如果您想研究Spark,則應該清楚地使用Java 8,它減少了您必須編寫的代碼量。
- 您可以在GitHub上找到示例項目的完整源代碼。
 
翻譯自: https://www.javacodegeeks.com/2014/06/building-a-simple-restful-api-with-spark.html
總結
以上是生活随笔為你收集整理的使用Spark构建简单的RESTful API的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 无线路由器有哪些拓展方式便携路由器如何扩
 - 下一篇: 电脑如何设置文件共享多台电脑如何共享文件