1. 概述
本快速教程將介紹 Spring 5 中引入的新類 ResponseStatusException。該類支持將 HTTP 狀態碼應用於 HTTP 響應。
一個 RESTful 應用程序可以通過 在響應中返回正確的狀態碼來告知客户端 HTTP 請求的成功或失敗。 簡單來説,適當的狀態碼可以幫助客户端識別請求處理過程中可能發生的任何問題。
2. ResponseStatus
在深入瞭解 ResponseStatusException 之前,我們先快速看一下 @ResponseStatus 註解。該註解自 Spring 3 版本開始引入,用於將 HTTP 狀態碼應用於 HTTP 響應。
我們可以使用 @ResponseStatus 註解來設置 HTTP 響應的狀態碼和原因:
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Actor Not Found")
public class ActorNotFoundException extends Exception {
// ...
}如果該異常在處理 HTTP 請求時拋出,則響應將包含本註解中指定的 HTTP 狀態碼。
使用 @ResponseStatus 標註的方案的一個缺點是,它與異常緊密耦合。 在我們的示例中,所有類型為 ActorNotFoundException 的異常都將生成相同的錯誤消息和響應狀態碼。
3. ResponseStatusException
ResponseStatusException 是@ResponseStatus 的編程替代方案,它是用於將狀態碼應用於 HTTP 響應所使用的異常的基礎類。它是一個 RuntimeException,因此不需要顯式地在方法簽名中添加。
Spring 提供了 3 個構造函數來生成 ResponseStatusException:
ResponseStatusException(HttpStatus status)
ResponseStatusException(HttpStatus status, java.lang.String reason)
ResponseStatusException(
HttpStatus status,
java.lang.String reason,
java.lang.Throwable cause
)ResponseStatusException,構造參數:
- status – HTTP 響應中設置的 HTTP 狀態碼
- reason – HTTP 響應中設置的解釋異常的消息
- cause – ResponseStatusException 的 Throwable 根源
注意:在 Spring 中,HandlerExceptionResolver 攔截並處理由 Controller 未處理的任何異常。
其中一個處理程序,ResponseStatusExceptionResolver,會查找任何ResponseStatusException 或由 @ResponseStatus 註解引發的未捕獲異常,然後從 HTTP 響應中提取 HTTP 狀態碼和原因,並將它們包含在 HTTP 響應中。
3.1. <em >ResponseStatusException</em> 的優勢
<em >ResponseStatusException</em> 的使用主要有以下幾點優勢:
- 首先,相同類型的異常可以單獨處理,並且可以為響應設置不同的狀態碼,從而減少緊耦合。
- 其次,它避免了創建不必要的額外異常類。
- 最後,它提供了更靈活的異常處理控制,因為異常可以編程方式創建。
4. 示例
4.1. 生成 ResponseStatusException
下面是一個生成 ResponseStatusException 的示例:
@GetMapping("/actor/{id}")
public String getActorName(@PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Actor Not Found", ex);
}
}Spring Boot 提供默認的 /error 映射,返回包含 HTTP 狀態碼的 JSON 響應。
響應示例如下:
$ curl -i -s -X GET 'http://localhost:8081/actor/8'
HTTP/1.1 404
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 26 Dec 2020 19:38:09 GMT
{
"timestamp": "2020-12-26T19:38:09.426+00:00",
"status": 404,
"error": "Not Found",
"path": "/actor/8"
}敏鋭的目光可能會注意到,響應中缺少“message”字段,用於顯示“Actor Not Found”原因文本。
接下來,讓我們找出原因以及如何在響應中包含“message”字段。
4.2 關於 server.error.include-message 屬性的説明
server.error.include-message 屬性,正如其名稱所示,控制“message” 字段是否包含在錯誤響應中,以及不包含。
它支持三種不同的值:
- always – 錯誤響應始終包含“message” 字段。
- never – “message” 字段永遠不會出現在錯誤響應中。
- on_param – “message” 字段只有在請求包含“message=true” 參數時才會出現在錯誤響應中。 否則,響應不會包含“message”。
從 2.3 版本開始,Spring Boot 將“never” 作為該屬性的默認值。 換句話説,默認錯誤頁面不會包含錯誤消息。 這是為了降低向客户端泄露信息的風險。
因此,http://localhost:8081/actor/8 的錯誤響應中沒有包含“message” 信息。
接下來,讓我們將 server.error.include-message 設置為 on_param,並查看結果:
$ curl -i -s -X GET 'http://localhost:8081/actor/8'
HTTP/1.1 404
...
{
"timestamp": "2020-12-26T19:38:49.426+00:00",
"status": 404,
"error": "Not Found",
"path": "/actor/8"
}如我們所見,如果請求未包含 “message=true” 參數,則響應中不包含 “message”。 如果我們在請求中添加該參數,我們將會看到 “message” 字段:
$ curl -i -s -X GET 'http://localhost:8081/actor/8?message=true'
HTTP/1.1 404
...
{
"timestamp": "2020-12-26T19:49:11.426+00:00",
"status": 404,
"error": "Not Found",
"message": "Actor Not Found",
"path": "/actor/8"
}值得注意的是,Spring Boot DevTools 始終將 server.error.include-message 屬性的默認值設置為 always。 因此,如果我們未指定該屬性的值並且 spring-boot-devtools 在 classpath 中,應用程序將always 在響應中顯示 “message”。 這對於我們在開發過程中測試和調試應用程序很有幫助。
為了簡化,我們將在此教程中設置 server.error.include-message=always,以查看完整的錯誤消息。
4.3. 不同狀態碼 – 同類型的異常
下面我們來看當同類型的異常被引發時,如何設置不同的狀態碼到 HTTP 響應中:
@PutMapping("/actor/{id}/{name}")
public String updateActorName(
@PathVariable("id") int id,
@PathVariable("name") String name) {
try {
return actorService.updateActor(id, name);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Provide correct Actor Id", ex);
}
}下面是響應的外觀:
$ curl -i -s -X PUT 'http://localhost:8081/actor/8/BradPitt'
HTTP/1.1 400
...
{
"timestamp": "2018-02-01T04:28:32.917+0000",
"status": 400,
"error": "Bad Request",
"message": "Provide correct Actor Id",
"path": "/actor/8/BradPitt"
}5. 結論
在本快速教程中,我們討論瞭如何在我們的程序中構建 ResponseStatusException。
我們還強調了使用程序方式設置 HTTP 狀態碼比使用 @ResponseStatus 註解更有效的方式,尤其是在 HTTP 響應中。