本文提綱
一、RESTful API通俗解釋
二、爲什麼選擇它
三、實例
四、最後
本文運行環境
Ubuntu 16.04 LTS
JDK 8 +
IntelliJ IDEA ULTIMATE 2017.2
Maven 3.5.0
Spring Boot 1.5.8.RELEASE
一、RESTful API通俗解釋
RESTful API
即具有REST
風格的API
,那什麼是REST
呢?
網上有很多介紹什麼是REST
的,但大多都是長篇大論的理論,即使有耐心讀完,也不一定能真正理解其意。比如下面這一小句:
REST全稱Representational State Transfer,中文翻譯【表述性狀態傳遞】。
所以這個【表述性狀態傳遞】到底是什麼鬼?反正我是不明白。
經過自己長時間的使用與對標準化的追求,再加上平時的網絡交互中,使用最多的是HTTP
協議,所以自己總結出的在HTTP
協議基礎下的REST
即:
客戶端與服務器交互過程中,客戶端用URL定位服務器資源,用一些動詞(POST、GET、PUT、PATCH、DELETE等)描述對資源的操作,得到狀態碼判斷操作結果如何。
實際項目中,很難寫出完全滿足REST
標準的API
,所以此處只說一般情況下需要滿足的點:
1.1 資源的定義
一般使用名詞而不是動詞來定義URL
,比如:
api.example.com/version/products
而不是api.example.com/version/getProducts
表示獲取商品。
1.2 使用合適的動詞
GET
——獲取資源
POST
——新建資源(很少情況下也可用於更新)
PUT
——更新資源(對整個資源的更新)
PATCH
——更新資源(對部分資源的更新,很少使用,某些類庫不支持)
DELETE
——刪除資源
1.3 使用標準的HTTP狀態碼
假設一種場景:有一個API
是POST
請求,請求參數是name
和age
,其中age
有一個後臺驗證,小於18則不添加並返回錯誤信息,當客戶端調用此API
時,age=11
,此時不滿足後臺驗證,返回了錯誤信息必須是成年人
,狀態碼是200
.
到此,看似一切都很正常:請求成功,並且也有錯誤信息。但熟悉HTTP狀態碼的話就會發現:明明已經返回錯誤信息了,說明出錯了,但還是返回了表示成功的200
,這讓客戶端在做處理時面臨一種比較尷尬的情況:需要在success
中寫錯誤處理邏輯。
因此,RESTful API
中要求:當客戶端請求時,達到預期目的才能返回200
,其它時候,根據具體的情況返回具體的狀態碼,這樣既標準,又能反應出問題出在何處。上面這種情況應該返回錯誤信息的同時,返回狀態碼400
,這樣,客戶端就能從自身找原因。
1.4 適當的表示
REST
並沒有規定有何種方式來表示資源,但在HTTP
協議下,一般的選擇就是JSON
或XML
,因此本文將以JSON
表示這些資源。通俗點講,就是請求一個API
時得到的返回值中的數據的表現形式是JSON
還是XML
。
一般情況下,滿足以上4點,即可稱爲RESTful API
,需要強調的是REST
並不僅僅以上4點,想了解比較全面的REST
,可以看看這篇比較長的文章
二、爲什麼選擇它
服務器提供的API
可以有很多種形式,但爲什麼選擇RESTful API
呢?主要有以下兩點:
2.1 統一的接口
在面對各種客戶端存在的情況下:Web
、Android
,iOS
等,RESTful API
可以提供一套統一的接口爲它們服務,另外,對於一些可以提供第三方服務的平臺,如微信登錄,支付寶支付等,它們並不需要顯示的客戶端存在,只需要一套提供服務的接口,RESTful API
就是很好的選擇,並且RESTful API
在微服務架構中也是常用的服務間通信方式。
2.2 對代碼質量的追求
就像上一點說的統一接口,其它形式的API
也能做到,比如:
只要服務器沒有出系統問題,所有的請求都返回下面這種格式:
POST請求成功—無返回值
{
"code": "1",
"data": null,
"message": "客戶端請求成功"
}
GET請求成功—返回
{
"code": "1",
"data": [
{
"name": "Tom",
"age": 12,
"money": 100.5
},
{
"name": "Bob",
"age": 13,
"money": 200.5
}
],
"message": "客戶端請求成功"
}
請求失敗—無返回值
{
"code": "101",
"data": null,
"message": "密碼錯誤"
}
這種形式,看起來似乎很標準,很合理,但其實它有幾個坑:
- 所有的
code
需要與客戶端重新定義,哪個值表示什麼意思,一旦有修改,需要重新規定,而RESTful API
使用現成的HTTP
狀態碼; - 如果
data
中的形式複雜,解析起來很麻煩,而RESTful API
直接返回需要的數據,即使麻煩,始終少一層解析; - 客戶端的所有響應邏輯全都寫在
success
中,對於追求代碼質量的人來說,不能容忍這種尷尬的局面,而RESTful API
只要有錯,就能在error
中操作; - 看起來很專業,其實一點也不。
三、實例
Spring Boot
對於RESTful API
也有很好的支持,本次實例繼續使用前篇的項目,依次演示GET
、POST
、PUT
、DELETE
,4種請求方式。由於還沒講到數據庫,所以暫時不涉及數據庫的操作,只需關注上面第一大點中提到的4小點。
3.1 UserController
在上一篇文章中的UserController
中新增以下方法,分別對應GET
、POST
、PUT
、DELETE
,4種請求方式:
@GetMapping("/users")
public ResponseEntity<List<User>> getUsers() {
List<User> users = new ArrayList<>();
User user1 = new User();
user1.setName("Bob");
user1.setAge(20);
user1.setPassword("123456");
User user2 = new User();
user2.setName("Tom");
user2.setAge(22);
user2.setPassword("654321");
//模擬從數據庫取出數據
users.add(user1);
users.add(user2);
return new ResponseEntity<>(users, HttpStatus.OK);
}
@PostMapping("/register")
public ResponseEntity<String> register(@Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
//如果驗證出錯,則返回錯誤信息,狀態碼400
return new ResponseEntity<>(bindingResult.getFieldError().getDefaultMessage(), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("success", HttpStatus.OK);
}
@PutMapping("/profile")
public ResponseEntity<User> updateUser(User newUser) {
User oldUser = new User();
oldUser.setName("old");
oldUser.setAge(20);
oldUser.setPassword("123456");
//假設oldUser爲數據庫中已存在數據,用newUser更新oldUser
if (!TextUtil.isEmpty(newUser.getName())) {
oldUser.setName(newUser.getName());
}
if (newUser.getAge() != null) {
oldUser.setAge(newUser.getAge());
}
if (!TextUtil.isEmpty(newUser.getPassword())) {
oldUser.setPassword(newUser.getPassword());
}
return new ResponseEntity<>(oldUser, HttpStatus.OK);
}
@DeleteMapping("/user")
public ResponseEntity deleteUser(String name) {
//省略數據庫刪除數據操作
return new ResponseEntity(HttpStatus.OK);
}
- 在
UserController
的類名上除了@RestController
,還有一個@RequestMapping("/user")
,表示URL
是localhost:8080/user
; -
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
:對應的該方法的訪問方式分別是GET
、POST
、PUT
、DELETE
,其括號中的值接在上一點的後面,如:localhost:8080/user/users
; -
ResponseEntity<T>
:響應實體,其中可包含數據與狀態碼,或只包含狀態碼。
3.2 測試
3.2.1 單元測試
在UserControllerTest
中添加如下測試用例:
@Test
public void testRegister_success() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/user/register")
.param("name", "Bob")
.param("age", "20")
.param("password", "123456"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("success"));
}
@Test
public void testRegister_age_error() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/user/register")
.param("name", "Bob")
.param("age", "10")
.param("password", "123456"))
.andExpect(MockMvcResultMatchers.status().isBadRequest())//age=10,驗證不通過,返回狀態碼400
.andExpect(MockMvcResultMatchers.content().string("必須是成年人"));
}
@Test
public void testGetUsers() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user/users"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json("[{\"name\":\"Bob\",\"age\":20,\"password\":\"123456\"},{\"name\":\"Tom\",\"age\":22,\"password\":\"654321\"}]"));
}
@Test
public void testUpdateUser() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.put("/user/profile")
.param("name", "new")
.param("age", "30")
.param("password", "555555"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json("{\"name\":\"new\",\"age\":30,\"password\":\"555555\"}"));
}
@Test
public void testDelete() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.delete("/user/user"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string(""));
}
運行,全部通過
3.2.2 IDEA REST Client
IDEA
自帶API
測試工具,打開Tools
->Test RESTful Web Service
:
分別輸入HTTP method
,Host/port
,Path
,若有請求參數,在Request
的Request Parameters
中添加參數,完成之後,點左邊綠色第一個播放按鈕,即可運行。
運行之後,可在Response
中查看返回數據,在Response Headers
中第一行查看狀態碼。
四、最後
本文先介紹了RESTful API
的一些基本概念,後又在Spring Boot
中實例演示。本次代碼爲了演示需要,在UserController
中有很多重複代碼,並且代碼耦合嚴重,這些問題將會隨着本系列文章的推出而逐步消除。
本文代碼已上傳至我的GitHub倉庫,進入以後將branches切換爲4-RESTful即可看見。
前篇:
Spring Boot實際應用講解(一):Hello World
Spring Boot實際應用講解(二):配置詳解
Spring Boot實際應用講解(三):表單驗證
後續將推出以下文章,敬請關注!
Spring Boot實際應用講解(五):AOP之請求日誌
Spring Boot實際應用講解(六):MySQL + Spring-data-jpa(Hibernate)
Spring Boot實際應用講解(七):統一異常處理
Spring Boot實際應用講解(八):MySQL + Mybatis
Spring Boot實際應用講解(九):MySQL + Mybatis + Redis
文中若有錯之處,還請各位批評指正,謝謝!
原文作者/ZYRzyr
原文鏈接:http://www.jianshu.com/p/e907595e9d1d
請進入這裏獲取授權:https://101709080007647.bqy.mobi