Spring Boot提供RESTful介面時的錯誤處理實踐
使用Spring Boot開發微服務的過程中,我們會使用別人提供的介面,也會設計介面給別人使用,這時候微服務應用之間的協作就需要有一定的規範。
-
基於rpc協議,我們一般有兩種思路:(1)提供服務的應用統一將異常包起來,然後用錯誤碼互動;(2)提供服務的應用將執行時異常丟擲,丟擲自定義的業務異常,服務的呼叫者通過異常catch來處理異常情況。
-
基於HTTP協議,那麼最流行的就是RESTful協議,服務提供方會自己處理所有異常,並且返回的結果中會跟HTTP的狀態碼相結合,這篇文章我們就用一個例子來說明RESTful介面的錯誤處理如何做。
首先我們需要新建一個簡單的Controller,程式碼如下:
@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
發起請求,結果如下:
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:錯誤的請求。在響應體中的內容依次如下:
- error : 錯誤資訊;
- exception:異常的型別,MissingServletRequestParameterExeption,見名知意,說明是缺少了某個請求引數;
- message:對異常的說明
- path:顯示請求的URL路徑;
- status:表示返回的錯誤碼
- timestamp:錯誤發生的時間戳,呼叫System.currentMills()
如果我們給定name引數,卻不給它賦值,又會如何?傳送命令http http:127.0.0.1:8080/greet?name
,則伺服器的返回值如下:
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","status": 500,"timestamp": 1449295284160
}
複製程式碼
對比上面,可以看出,這次返回的錯誤碼是500,表示伺服器內部錯誤;返回的異常型別是java.lang.IllegalArgumentException,表示引數不合法。伺服器內部錯誤表示伺服器丟擲了異常缺沒有處理,我們更願意API返回400,告訴呼叫者自己哪裡做錯了。如何實現呢?利用@ExceptionHandler註解即可。
在GreetingController控制器中加入如下處理函式,用於捕獲這個控制器的異常。
@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","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","message": "Please try again and with a non empty string as 'name'","timestamp": 1449296465060
}
複製程式碼
如果希望驗證請求的引數,可以使用JSR-303 Bean Validation API,並參考Spring Validation。在spring.io上還有一個驗證表單輸入的例子Validating Form Input。
參考資料
Spring Boot 1.x系列
- Spring Boot的自動配置、Command-line-Runner
- 瞭解Spring Boot的自動配置
- Spring Boot的@PropertySource註解在整合Redis中的使用
- Spring Boot專案中如何定製HTTP訊息轉換器
- Spring Boot整合Mongodb提供Restful介面
- Spring中bean的scope
- Spring Boot專案中使用事件派發器模式
本號專注於後端技術、JVM問題排查和優化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發者的工作和成長經驗,期待你能在這裡有所收穫。