使用Spring Boot開發微服務的過程當中,咱們會使用別人提供的接口,也會設計接口給別人使用,這時候微服務應用之間的協做就須要有必定的規範。html
基於rpc協議,咱們通常有兩種思路:(1)提供服務的應用統一將異常包起來,而後用錯誤碼交互;(2)提供服務的應用將運行時異常拋出,拋出自定義的業務異常,服務的調用者經過異常catch來處理異常狀況。java
基於HTTP協議,那麼最流行的就是RESTful協議,服務提供方會本身處理全部異常,而且返回的結果中會跟HTTP的狀態碼相結合,這篇文章咱們就用一個例子來講明RESTful接口的錯誤處理如何作。git
首先咱們須要新建一個簡單的Controller,代碼以下:github
@RestController
class GreetingController {
@RequestMapping("/greet")
String sayHello(@RequestParam("name") String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("The 'name' parameter must not be null or empty");
}
return String.format("Hello %s!", name);
}
}
複製代碼
經過http請求客戶端——httpie發送HTTP請求,這個工具比curl的好處是:返回值信息有語法高亮、對返回的JSON字符串自動格式化。啓動服務器,使用命令http http://127.0.0.1:8080/greet?name=duqi
發起請求,結果以下:web
HTTP/1.1 200 OK
Content-Length: 11
Content-Type: text/plain;charset=UTF-8
Date: Sat, 05 Dec 2015 05:45:03 GMT
Server: Apache-Coyote/1.1
X-Application-Context: application
複製代碼
如今咱們製造一個錯誤的請求,@RequestParam是獲取URL中的參數,若是這個參數不提供則會出錯。所以,咱們發送一個命令http http://127.0.0.1:8080
,看結果如何。面試
HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Sat, 05 Dec 2015 05:54:06 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application
{
"error": "Bad Request",
"exception": "org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter 'name' is not present",
"path": "/greet",
"status": 400,
"timestamp": 1449294846060
}
複製代碼
能夠看到,因爲沒有提供name參數,服務器返回的狀態碼是400:錯誤的請求。在響應體中的內容依次以下:spring
若是咱們給定name參數,卻不給它賦值,又會如何?發送命令http http:127.0.0.1:8080/greet?name
,則服務器的返回值以下:json
HTTP/1.1 500 Internal Server Error
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Sat, 05 Dec 2015 06:01:24 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application
{
"error": "Internal Server Error",
"exception": "java.lang.IllegalArgumentException",
"message": "The 'name' parameter must not be null or empty",
"path": "/greet",
"status": 500,
"timestamp": 1449295284160
}
複製代碼
對比上面,能夠看出,此次返回的錯誤碼是500,表示服務器內部錯誤;返回的異常類型是java.lang.IllegalArgumentException,表示參數不合法。服務器內部錯誤表示服務器拋出了異常缺沒有處理,咱們更願意API返回400,告訴調用者本身哪裏作錯了。如何實現呢?利用@ExceptionHandler註解便可。後端
在GreetingController控制器中加入以下處理函數,用於捕獲這個控制器的異常。bash
@ExceptionHandler
void handleIllegalArgumentException(IllegalArgumentException e, HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
複製代碼
再次發送命令http http:127.0.0.1:8080/greet?name
,則返回下面的結果:
HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Sat, 05 Dec 2015 06:08:50 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application
{
"error": "Bad Request",
"exception": "java.lang.IllegalArgumentException",
"message": "The 'name' parameter must not be null or empty",
"path": "/greet",
"status": 400,
"timestamp": 1449295729978
}
複製代碼
說明咱們在服務器端捕獲了IllegalArgumentException這個異常,並設置response的返回碼爲400。若是你想對多個異常都進行同樣的處理,則上述異常處理代碼能夠修改成下面這樣(給@ExceptionHandler傳入參數):
@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
void handleIllegalArgumentException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
複製代碼
如今這個異常處理代碼是加在當前的這個控制器中,所以它只處理屬於這個控制器的響應,若是咱們新建一個類,並用註解@ControllerAdvice修飾,並在這個類中定義上述的異常處理代碼,則它會負責處理全部的請求。
Spring Boot 1.2.0之後,還支持在response修改對應的message,只要將對應的message信息傳入sendError函數便可,例如:
@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
void handleIllegalArgumentException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value(),
"Please try again and with a non empty string as 'name'");
}
複製代碼
再次執行一樣的命令,會收到下列反饋:
HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Sat, 05 Dec 2015 06:21:05 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application
{
"error": "Bad Request",
"exception": "java.lang.IllegalArgumentException",
"message": "Please try again and with a non empty string as 'name'",
"path": "/greet",
"status": 400,
"timestamp": 1449296465060
}
複製代碼
若是但願驗證請求的參數,可使用JSR-303 Bean Validation API,並參考Spring Validation。在spring.io上還有一個驗證表單輸入的例子Validating Form Input。
本號專一於後端技術、JVM問題排查和優化、Java面試題、我的成長和自我管理等主題,爲讀者提供一線開發者的工做和成長經驗,期待你能在這裏有所收穫。