基於 springMVC 的 RESTful HTTP API 實踐(服務端)

理解 REST

REST(Representational State Transfer),中文翻譯叫「表述性狀態轉移」。是 <span style="color:blue;font-size:15px;font-family:Microsoft YaHei;font-style:oblique;">Roy Thomas Fielding</span> 在他2000年的博士論文中提出的。它與傳統的 SOAP Web 服務區別在於,REST關注的是要處理的數據,而 SOAP 主要關注行爲和處理。要理解好 REST,根據其首字母拆分出的英文更容易理解。 **表述性(Representational):**對於 REST 來講,咱們網絡上的一個個URI資源能夠用各類形式來表述,例如:XML、JSON或者HTML等。 **狀態(State):**REST 更關注資源的狀態而不是對資源採起的行爲。 **轉移(Transfer):**在網絡傳輸過程當中,REST 使資源以某種表述性形式從一個應用轉移到另外一個應用(如從服務端轉移到客戶端)。html

具體來講,REST 中存在行爲,它的行爲是經過 HTTP 表示操做的方法來定義的即:GET、POST、PUT、DELETE、PATCH;GET用來獲取資源,POST用來新建資源(也能夠用於更新資源),PUT用來更新資源,DELETE用來刪除資源,PATCH用來更新資源。 基於 REST 這樣的觀點,咱們須要避免使用 REST服務、REST Web服務 這樣的稱呼,這些稱呼多少都帶有一些強調行爲的味道。java


使用 RESTful 架構設計使用誤區

**RESTful 架構:**是基於 REST 思想的時下比較流行的一種互聯網軟件架構。它結構清晰、符合標準、易於理解、擴展方便,因此正獲得愈來愈多網站的採用。git

  • 在沒有足夠了解 REST 的時候,咱們很容易錯誤的將其視爲 「基於 URL 的 Web 服務」,即將 REST 和 SOAP 同樣,是一種遠程過程調用(remote procedure call,RPC)的機制。可是 REST 和 RPC 幾乎沒有任何關係,RPC 是面向服務的,而 REST 是面向資源的,強調描述應用程序的事物和名詞。這樣很容易致使的一個結果是咱們在設計 RESTful API 時,在 URI 中使用動詞。例如:GET /user/getUser/123。正確寫法應該是 GET /user/123。

使用 springMVC 支持 RESTful

在 spring 3.0 之後,spring 這對 springMVC 的一些加強功能對 RESTful 提供了良好的支持。在4.0後的版本中,spring 支持一下方式建立 REST 資源:github

  1. 控制器能夠處理全部的 HTTP 方法,包含幾個主要的 REST 方法:GET、POST、PUT、DELETE、PATCH;
  2. 藉助 @PathVariable 註解,控制器可以處理參數化的 URL(將變量輸入做爲 URL 的一部分);
  3. 藉助 spring 的視圖解析器,資源可以以多種方式進行表述,包括將模型數據渲染爲 XML、JSON、Atom、已經 RSS 的 View 實現;
  4. 可使用 ContentNegotiatingViewResolver 來選擇最適合客戶端的表述;
  5. 藉助 @ResponseBody 註解和各類 HttpMethodConverter 實現,可以替換基於視圖的渲染方式;
  6. 相似地,@RequestBody 註解以及 HttpMethodConverter 實現能夠將傳入的 HTTP 數據轉化爲傳入控制器處理方法的 Java 對象;
  7. 藉助 RestTemplate ,spring 應用可以方便地使用 REST 資源。

建立 RESTful 控制器

代碼清單web

package com.pengdh.controller;

import com.pengdh.entity.EmployeeEntity;
import com.pengdh.service.EmployeeService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * @author pengdh
 * @date: 2017-06-27 0:08
 */
@Controller
@RequestMapping("/employs")
public class EmployeeController {

  @Autowired
  private EmployeeService empService;

  @RequestMapping(value = "/list", method = RequestMethod.GET, produces = { "application/json;charset=UTF-8" })
  public List<EmployeeEntity> employs(Integer offset,Integer limit) {
    offset = offset == null ? 0 : offset;
    limit = limit == null ? 20 : limit;
    return empService.queryEmployList(offset,limit);
  }
}

代碼的大體過程是當客戶端發起對 "/employs" 的 GET 請求時,將調用服務端的 employs 方法,服務端經過注入的 EmployeeService 獲取到一個 EmployeeEntity 列表,並將列表以 JSON 的表述形式返回給客戶端。spring

  • 須要注意的是這裏控制器自己並不關心資源如何表述。控制器以 Java 對象的方式來處理資源。控制器完成了它的工做之後,資源纔會被轉化成爲適合客戶端的形式。spring 提供了兩種方法將資源的 java 表述形式轉化爲發送給客戶端的表述形式:
  • 內容協商(Content negotiation):選擇一個視圖,它可以將模型渲染爲呈現給客戶端的表述形式;
  • 消息轉化器(Message conversion):經過一個消息轉換器將控制器所返回的對象轉換爲呈現給客戶端的表述形式。

對於上述兩種方式,第一種方式是經過 ContentNegotiatingViewResolver 做爲 ViewResolver 的實現,主要是用於將資源渲染人類用戶接口所須要的視圖模型,如:HTML、JSP等也能夠渲染。也能夠針對不是人類客戶端產生 JSON 或 XML,可是效果不是很理想,每每會產生一些不是客戶端所須要的預期結果。如:客戶端但願獲得的響應多是:{"name":"zhangs","age":"20"}。而模型是 key-value 組成的 map ,可能最終的響應是這樣的:{"user":{"name":"zhangs","age":"20"}}。基於內容協商的這些限制,這裏咱們主要討論第二種方式:使用 Spring 的消息轉換功能來生成資源表述。json


使用 HTTP 消息轉換器

這是一種更爲直接的方式,消息轉換器可以將控制器產生的數據轉換爲服務於客戶端的表述形式。經常使用的一些消息轉換器如:Jackson 的 MappingJacksonHttpMessageConverter 實現 JSON 消息和 Java 對象的互相轉換; JAXB 庫的 Jaxb2RootElementHttpMessageConverter 實現 XML 和 Java 對象的相互轉換等。api

經過 @ResponseBody 註解實現響應體中返回資源狀態。

正常狀況下,當處理方法返回 Java 對象時,這個對象會放在模型中並在視圖中渲染使用。可是,若是使用了消息轉換功能的話,咱們須要告訴 Spring 跳過正常的模型/視圖流程,並使用消息轉換器。實現這種方式最簡單的方式是在控制器的方法上添加 @ResponseBody 註解。如:restful

@RequestMapping(value = "/list", method = RequestMethod.GET, produces = { "application/json;charset=UTF-8" })
  @ResponseBody
  public List<EmployeeEntity> employs(Integer offset,Integer limit) {
    offset = offset == null ? 0 : offset;
    limit = limit == null ? 20 : limit;
    return empService.queryEmployList(offset,limit);
  }

這裏 @ResponseBody 註解會告知 Spring 將 List<EmployeeEntity> 轉換成 JSON 這樣的表述形式做爲資源發送給客戶端。網絡

使用 @RequestBody 註解實如今請求體中接收資源狀態

使用 @RequestBody 註解能夠告知 Spring 查找一個消息轉換器,未來自客戶端的資源表述轉換爲對象。如:

@RequestMapping(value = "/save", method = RequestMethod.POST, produces = { "application/json;charset=UTF-8" })
  public int saveEmploy(@RequestBody EmployeeEntity employeeEntity) {
    return empService.save(employeeEntity);
  }
使用 @RestController 註解爲控制器默認設置消息轉換

Spring 4.0 引入了 @RestController 註解,在控制器是用 @RestController 代替 @Controller 的話,Spring 將會爲該控制器的全部處理方法應用消息轉換功能。咱們沒必要在每一個方法都添加 @ResponseBody 註解了。如:

package com.pengdh.controller;

import com.pengdh.entity.EmployeeEntity;
import com.pengdh.service.EmployeeService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author pengdh
 * @date: 2017-06-27 0:08
 */
@RestController
@RequestMapping("/employs")
public class EmployeeController {

  @Autowired
  private EmployeeService empService;

  @RequestMapping(value = "/list", method = RequestMethod.GET, produces = { "application/json;charset=UTF-8" })
  public List<EmployeeEntity> employs(Integer offset,Integer limit) {
    offset = offset == null ? 0 : offset;
    limit = limit == null ? 20 : limit;
    return empService.queryEmployList(offset,limit);
  }

  @RequestMapping(value = "/save", method = RequestMethod.POST, produces = { "application/json;charset=UTF-8" })
  public int saveEmploy(@RequestBody EmployeeEntity employeeEntity) {
    return empService.save(employeeEntity);
  }
}

爲客戶端提供其餘元數據

使用 ResponseEntity 提供更多響應相關的元數據

能夠利用 ResponseEntity 給客戶端返回狀態碼、設置響應頭信息等,如給客戶端提供返回碼:

@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = { "application/json;charset=UTF-8" })
  public ResponseEntity<EmployeeEntity> employById(@PathVariable long id) {
    HttpStatus status = null;
    EmployeeEntity employeeEntity = empService.selectById(id);
    if (employeeEntity != null) {
      status = HttpStatus.OK;
    } else {
      status = HttpStatus.NOT_FOUND;
    }
    return new ResponseEntity<EmployeeEntity>(employeeEntity, status);
  }

若是沒有 if 判斷,當根據 id 找不到對應的信息的時候,返回給客戶端的狀態碼是默認的 HttpStatus.OK;當加上了判斷條件後若是沒有相應的信息返回則設置返回狀態碼爲 HttpStatus.NOT_FOUND,最後經過 new 一個 ResponseEntity 會將查詢信息和狀態碼一塊兒返回到客戶端。

  • 另外,ResponseEntity 還包含有 @ResponseBody 的語義,上面示例中並無使用 @ResponseBody 註解,可是 ResponseEntity 的負載部分一樣能夠渲染到響應體中。
使用控制器異常處理器 @ExceptionHandler 處理異常信息

@ExceptionHandler 能夠用到控制器的方法中,處理特定的異常:

建立響應包裝類 ResponseResult

package com.pengdh.dto;

import java.io.Serializable;
import org.springframework.http.HttpStatus;

/**
 * 響應結果封裝類
 *
 * @author pengdh
 * @date: 2017-06-29 0:34
 */
public class ResponseResult<T> implements Serializable {

  private static final long serialVersionUID = -3371934618173052904L;
  private int code;
  private String desc;
  private T data;

  public ResponseResult() {
  }

  public ResponseResult(int code, String desc) {
    this.code = code;
    this.desc = desc;
  }

  public ResponseResult(int code, T data) {
    this.code = code;
    this.data = data;
  }

  public int getCode() {
    return code;
  }

  public void setCode(HttpStatus code) {
    this.code = code;
  }

  public String getDesc() {
    return desc;
  }

  public void setDesc(String desc) {
    this.desc = desc;
  }

  public T getData() {
    return data;
  }

  public void setData(T data) {
    this.data = data;
  }

  @Override
  public String toString() {
    return "ResponseResult{" +
        "code=" + code +
        ", desc='" + desc + '\'' +
        ", data=" + data +
        '}';
  }
}

建立一個異常類 ResourceNotFound

package com.pengdh.exception;

/**
 * 資源未找到異常
 *
 * @author pengdh
 * @date: 2017-06-29 0:55
 */
public class ResourceNotFound extends RuntimeException {

  private static final long serialVersionUID = 4880328265878141724L;

  public ResourceNotFound() {
    super();
  }

  public ResourceNotFound(String message) {
    super(message);
  }

  public ResourceNotFound(String message, Throwable cause) {
    super(message, cause);
  }
}

控制器 EmployeeController

package com.pengdh.controller;

import com.pengdh.dto.ResponseResult;
import com.pengdh.entity.EmployeeEntity;
import com.pengdh.exception.ResourceNotFound;
import com.pengdh.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author pengdh
 * @date: 2017-06-27 0:08
 */
@RestController
@RequestMapping("/employs")
public class EmployeeController {

  @Autowired
  private EmployeeService empService;

  @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = { "application/json;charset=UTF-8" })
  public ResponseResult<EmployeeEntity> employById(@PathVariable long id) {
    ResponseResult<EmployeeEntity> result = new ResponseResult<EmployeeEntity>();
    HttpStatus status = null;
    EmployeeEntity employeeEntity = empService.selectById(id);
    if (employeeEntity == null) {
      throw new ResourceNotFound(String.valueOf(id));
    }
    result.setCode(HttpStatus.OK);
    result.setData(employeeEntity);
    return result;
  }

  @ExceptionHandler(ResourceNotFound.class)
  public ResponseResult<Object> handlerException(ResourceNotFound e) {
    ResponseResult<Object> result = new ResponseResult<Object>();
    result.setCode(HttpStatus.NOT_FOUND);
    result.setDesc(e.getMessage());
    return result;
  }
}

從控制器代碼能夠看出,咱們經過 @ExceptionHandler 能將控制器的方法的異常場景分出來單獨處理。


參考文獻

歡迎關注公衆號 程序猿pdh

相關文章
相關標籤/搜索