1. 程式人生 > >【Spring實戰(第四版)筆記】——REST在響應中設定頭部資訊

【Spring實戰(第四版)筆記】——REST在響應中設定頭部資訊

<Spring實戰(第四版)筆記>——REST在響應中設定頭部資訊

情景描述:客戶端新增資源物件,服務端儲存資源並返回資訊。

@RestController
@RequestMapping(value = "city")
public class CityHeaderController {
    @Autowired
    private CityService cityService;

    /**
     * @param city
     * @return
     */
    @RequestMapping(value = "/api/add", method =
RequestMethod.POST) public @ResponseBody Long saveCity(@RequestBody City city) { return cityService.saveCity(city); } }

在saveCity()處理完請求之後,伺服器在響應體中包含了City的表述以及HTTP狀態碼200(OK),將其返回給客戶端。這裡沒有什麼大問題,但是還不是完全準確。
當然,假設處理請求的過程中成功建立了資源,狀態可以視為OK。但是,我們不僅僅需要說“OK”。我們建立了新的內容,HTTP狀態碼也將這種情況告訴給了客戶端。
不過,HTTP 201不僅能夠表明請求成功完成,而且還能描述建立了新資源。如果我們希望完整準確地與客戶端交流,那麼響應是不是應該為201(Created),而不僅僅是200(OK)呢?

Spring提供了方式來處理這樣的場景:
** 我們需要做的就是為方法新增@ResponseStatus註解**

新增@ResponseStatus註解,返回指定的狀態碼

@RestController
@RequestMapping(value = "city")
public class CityHeaderController {
    @Autowired
    private CityService cityService;
    /**
     * HTTP 201不僅能夠表明請求成功完成,而且還能描述建立了新資源。
     * 如果我們希望完整準確地與客戶端交流,那麼響應是不是應該為201(Created),而不僅僅是200(OK)
     *
     * @param city
     * @return
     */
@RequestMapping(value = "/api1/add", method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) public @ResponseBody Long saveCity1(@RequestBody City city) { return cityService.saveCity(city); } }

當建立新資源的時候,將資源的URL放在響應的Location頭部資訊中,並返回給客戶端是一種很好的方式。
因此,我們需要有一種方式來填充響應頭部資訊,此時我們的老朋友ResponseEntity就能提供幫助了。

使用ResponseEntity,將資源的URL放在響應的Location頭部資訊

如下的程式清單展現了一個新版本的saveCity(),它會返回ResponseEntity用來告訴客戶端新建立的資源。

/**
 * @author gucailiang
 * @date 2018/10/10
 */
@Controller
@RequestMapping(value = "city")
public class CityController {
    @Autowired
    private CityService cityService;

     /**
     * 當建立新資源的時候,將資源的URL放在響應的Location頭部資訊中,並返回給客戶端是一種很好的方式。
     * 因此,我們需要有一種方式來填充響應頭部資訊,此時我們的老朋友ResponseEntity就能提供幫助了。
     * <p>
     * 注意:mapper介面返回值依然是成功插入的記錄數,但不同的是主鍵值已經賦值到領域模型實體的id中了。
     */
    @RequestMapping(value = "/api2/add", method = RequestMethod.POST, consumes = "application/json")
    @ResponseStatus(HttpStatus.CREATED)
    public @ResponseBody
    ResponseEntity<City> saveCity2(@RequestBody City city) {
        HttpHeaders httpHeaders = new HttpHeaders();
        Long id = cityService.saveCity(city);
        URI locationUrl = URI.create("localhost:8081/city/api5/" + city.getId());
        httpHeaders.setLocation(locationUrl);
        return new ResponseEntity<City>(city, httpHeaders, HttpStatus.CREATED);
    }

}

mapper介面的sql

	<!--useGeneratedKeys="true" keyProperty="id" 自動生產主鍵,
	並把主鍵繫結在keyProperty對應的屬性中,mapper介面返回值依然是成功插入的記錄數,
	但不同的是主鍵值已經賦值到領域模型實體的id中了-->
	<insert id="saveCity" parameterMap="City" useGeneratedKeys="true" keyProperty="id">
		insert into
			city(id,province_id,city_name,description)
		values
			(#{id},#{provinceId},#{cityName},#{description})
	</insert>

這個新的版本中,我們建立了一個HttpHeaders例項,用來存放希望在響應中包含的頭部資訊值。
HttpHeaders是MultiValueMap<String, String>的特殊實現,它有一些便利的Setter方法(如setLocation()),用來設定常見的HTTP頭部資訊。
在得到新建立City資源的URL之後,接下來使用這個頭部資訊來建立ResponseEntity

使用UriComponentsBuilder解決響應Location頭部資訊的硬編碼問題

使用硬編碼值的方式來構建Location頭部資訊。URL中“localhost”以及“8080”這兩個部分尤其需要注意,因為如果我們將應用部署到其他地方,而不是在本地執行的話,它們就不適用了.
使用UriComponentsBuilder,我們需要做的就是在處理器方法中將其作為一個引數.

@Controller
@RequestMapping(value = "city")
public class CityController {
   
    /**
     * 原本簡單的saveSpittle()方法瞬間變得臃腫了。但是,更值得關注的是,它使用硬編碼值的方式來構建Location頭部資訊。
     * URL中“localhost”以及“8080”這兩個部分尤其需要注意,因為如果我們將應用部署到其他地方,而不是在本地執行的話,它們就不適用了
     * <p>
     * 其實沒有必要手動構建URL,Spring提供了UriComponentsBuilder,可以給我們一些幫助。它是一個構建類,通過逐步指定URL中的各種組成部分(如host、埠、路徑以及查詢),
     * 我們能夠使用它來構建UriComponents例項。藉助UriComponentsBuilder所構建的UriComponents物件,我們就能獲得適合設定給Location頭部資訊的URI。
     * <p>
     * 為了使用UriComponentsBuilder,我們需要做的就是在處理器方法中將其作為一個引數
     */
    @RequestMapping(value = "/api3/add", method = RequestMethod.POST, consumes = "application/json")
    @ResponseStatus(HttpStatus.CREATED)
    public @ResponseBody
    ResponseEntity<City> saveCity3(@RequestBody City city, UriComponentsBuilder uriComponentsBuilder) {
        HttpHeaders httpHeaders = new HttpHeaders();
        Long id = cityService.saveCity(city);
        URI locationUrl = uriComponentsBuilder.path("city/api5/").path(String.valueOf(city.getId())).build().toUri();
        httpHeaders.setLocation(locationUrl);
        return new ResponseEntity<City>(city, httpHeaders, HttpStatus.CREATED);
    }
}

請求返回的headers中location如下:

content-type →application/json;charset=UTF-8
location →http://localhost:8081/city/api5/city/api5/city/api5/22

在REST API中暴露資源只代表了會話的一端。如果釋出的API沒有人關心和使用的話,那也沒有什麼價值