Spring Boot實際應用講解(四):RESTful API 一、RESTful API通俗解釋 二、爲什麼選擇它 三、實例 四、最後

文/ZYRzyr
原文鏈接:http://www.jianshu.com/p/e907595e9d1d

本文提綱
一、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狀態碼

假設一種場景:有一個APIPOST請求,請求參數是nameage,其中age有一個後臺驗證,小於18則不添加並返回錯誤信息,當客戶端調用此API時,age=11,此時不滿足後臺驗證,返回了錯誤信息必須是成年人,狀態碼是200.

到此,看似一切都很正常:請求成功,並且也有錯誤信息。但熟悉HTTP狀態碼的話就會發現:明明已經返回錯誤信息了,說明出錯了,但還是返回了表示成功的200,這讓客戶端在做處理時面臨一種比較尷尬的情況:需要在success中寫錯誤處理邏輯。

因此,RESTful API中要求:當客戶端請求時,達到預期目的才能返回200,其它時候,根據具體的情況返回具體的狀態碼,這樣既標準,又能反應出問題出在何處。上面這種情況應該返回錯誤信息的同時,返回狀態碼400,這樣,客戶端就能從自身找原因。

1.4 適當的表示

REST並沒有規定有何種方式來表示資源,但在HTTP協議下,一般的選擇就是JSONXML,因此本文將以JSON表示這些資源。通俗點講,就是請求一個API時得到的返回值中的數據的表現形式是JSON還是XML

一般情況下,滿足以上4點,即可稱爲RESTful API,需要強調的是REST並不僅僅以上4點,想了解比較全面的REST,可以看看這篇比較長的文章

二、爲什麼選擇它

服務器提供的API可以有很多種形式,但爲什麼選擇RESTful API呢?主要有以下兩點:

2.1 統一的接口

在面對各種客戶端存在的情況下:WebAndroidiOS等,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": "密碼錯誤"
}

這種形式,看起來似乎很標準,很合理,但其實它有幾個坑:

  1. 所有的code需要與客戶端重新定義,哪個值表示什麼意思,一旦有修改,需要重新規定,而RESTful API使用現成的HTTP狀態碼;
  2. 如果data中的形式複雜,解析起來很麻煩,而RESTful API直接返回需要的數據,即使麻煩,始終少一層解析;
  3. 客戶端的所有響應邏輯全都寫在success中,對於追求代碼質量的人來說,不能容忍這種尷尬的局面,而RESTful API只要有錯,就能在error中操作;
  4. 看起來很專業,其實一點也不。

三、實例

Spring Boot對於RESTful API也有很好的支持,本次實例繼續使用前篇的項目,依次演示GETPOSTPUTDELETE,4種請求方式。由於還沒講到數據庫,所以暫時不涉及數據庫的操作,只需關注上面第一大點中提到的4小點。

3.1 UserController

在上一篇文章中的UserController中新增以下方法,分別對應GETPOSTPUTDELETE,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);
    }
  1. UserController的類名上除了@RestController,還有一個@RequestMapping("/user"),表示URLlocalhost:8080/user
  2. @GetMapping@PostMapping@PutMapping@DeleteMapping:對應的該方法的訪問方式分別是GETPOSTPUTDELETE,其括號中的值接在上一點的後面,如:localhost:8080/user/users
  3. 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 methodHost/portPath,若有請求參數,在RequestRequest 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章