Spring Boot 集成教程
- Spring Boot 介紹
- Spring Boot 開發環境搭建(Eclipse)
- Spring Boot Hello World (restful接口)例子
- spring boot 連接Mysql
- spring boot配置druid連接池連接mysql
- spring boot集成mybatis(1)
- spring boot集成mybatis(2) – 使用pagehelper實現分頁
- spring boot集成mybatis(3) – mybatis generator 配置
- spring boot 接口返回值封裝
- spring boot輸入數據校驗(validation)
- spring boot rest 接口集成 spring security(1) – 最簡配置
- spring boot rest 接口集成 spring security(2) – JWT配置
- spring boot 異常(exception)處理
- spring boot 環境配置(profile)切換
- spring boot redis 緩存(cache)集成
概述
異常處理注解
spring中處理異常可以通過2個注解:
@ControllerAdvice
全局,處理所有控制器中的異常@ExceptionHandler
局部,只針對某個控制器中的異常
先有ExceptionHandler
,再有ControllerAdvice
,ExceptionHandler
不能集中處理異常,ControllerAdvice
為彌補此缺點引入,推薦使用ControllerAdvice
。本文介紹ControllerAdvice
的用法,對ExceptionHandler
不作介紹,如需了解可參考相關資料。
錯誤處理頁面:ErrorController
ErrorController
的作用是為servlet設置錯誤頁,默認錯誤頁是Whitelabel,訪問不存在的頁面就會顯示此錯誤頁。
通過繼承ErrorController
接口可以設置自定義的錯誤頁。
@RestController
public class MyErrorController implements ErrorController {
@RequestMapping(value = "/error")
public ResponseEntity<Result> error() {
Result res = new Result(404, "頁面未找到");
return new ResponseEntity<Result>(res, HttpStatus.NOT_FOUND);
}
@Override
public String getErrorPath() {
return "/error";
}
}
如果ControllerAdvice
中沒有直接返回http響應,繼續拋出異常,將會調用ErrorController
顯示錯誤頁。如果在ControllerAdvice
中捕獲異常並直接返回http響應,就沒必要配置ErrorController
中的錯誤頁了。
404 異常
spring boot默認不會拋出404異常(NoHandlerFoundException),所以在ControllerAdvice
中捕獲不到該異常,導致404總是跳過ContollerAdvice
,直接顯示ErrorController
的錯誤頁。需要改變配置,讓404錯誤拋出異常(NoHandlerFoundException),這樣便可在ControllerAdvice
中捕獲此異常。改變配置,在application.properties
中添加:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
過濾器異常
ContollerAdvice
不能捕獲過濾器拋出的異常,對於此類異常需要特別處理。
如 [spring boot rest 接口集成 spring security(2) – JWT配置] 章節中的jwt過濾器,異常處理需要設置特別的處理類。
使用ContollerAdvice
可以實現對所有控制器異常的集中處理,下面通過一個實際項目介紹此過程。
項目內容
模擬一個用戶注冊的接口,拋出各類異常讓ContollerAdvice
處理。
要求
- JDK1.8或更新版本
- Eclipse開發環境
如沒有開發環境,可參考前面章節 [spring boot 開發環境搭建(Eclipse)]。
項目創建
創建spring boot項目
打開Eclipse,創建spring boot的spring starter project項目,選擇菜單:File > New > Project ...
,彈出對話框,選擇:Spring Boot > Spring Starter Project
,在配置依賴時,勾選web
,完成項目創建。
項目配置
application.properties配置
## 服務器端口,默認是8080
server.port=8096
## 讓404錯誤拋出異常,需要同時設置spring.resources.add-mappings為false
# 讓404錯誤拋出異常
spring.mvc.throw-exception-if-no-handler-found=true
# 禁用靜態資源的自動映射,如不禁用,不存在的url將被映射到/**,servlet不有機會拋出異常
spring.resources.add-mappings=false
## log級別設置為debug, 通過log.debug打印異常信息
logging.level.root=DEBUG
如果使用靜態資源的自動映射,不存在的url將被映射到/**,servlet不有機會拋出異常。在rest api的項目中沒有靜態資源的處理,完全可以禁止。
代碼實現
項目目錄結構如下圖,我們添加了幾個類,下面將詳細介紹。
異常處理類 ErrorHandler.java
這個類就是加ControllerAdvice
注解的異常處理類,所有控制器的異常,都在這里集中處理。這里我們實現了2類特殊異常的處理函數,以及1個缺省的異常處理函數。
- 輸入參數校驗異常處理
- 404異常處理
- 缺省的異常處理函數,處理所有其他異常
@ControllerAdvice //表明這是控制器的異常處理類
public class ErrorHandler {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(ErrorHandler.class);
/**
* 輸入參數校驗異常
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<Result> NotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) throws Exception {
log.debug("異常詳情", e);
BindingResult bindingResult = e.getBindingResult();
//rfc4918 - 11.2. 422: Unprocessable Entity
Result res = MiscUtil.getValidateError(bindingResult);
return new ResponseEntity<Result>(res, HttpStatus.UNPROCESSABLE_ENTITY);
}
/**
* 404異常處理
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
public ResponseEntity<Result> NoHandlerFoundExceptionHandler(HttpServletRequest req, Exception e) throws Exception {
log.debug("異常詳情", e);
Result res = new Result(404, "頁面不存在");
return new ResponseEntity<Result>(res, HttpStatus.NOT_FOUND);
}
/**
* 默認異常處理,前面未處理
*/
@ExceptionHandler(value = Throwable.class)
public ResponseEntity<Result> defaultHandler(HttpServletRequest req, Exception e) throws Exception {
Result res = new Result(500, "服務器內部錯誤");
log.debug("異常詳情", e);
return new ResponseEntity<Result>(res, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
輸入參數校驗異常處理,在 [spring boot輸入數據校驗(validation)] 章節里,是直接在控制器返回bindingResult
,現在放在這里統一處理,好處是無需再在每個接口里重復寫返回bindingResult
的代碼。
控制器 AuthController
AuthController
實現了一個模擬用戶注冊的接口,數據校驗返回bindingResult
的代碼被去除,spring boot將直接拋出MethodArgumentNotValidException
,然后由ErrorHandler
捕獲處理。
@RestController
@RequestMapping("/auth")
public class AuthController {
@RequestMapping(value = "/register", method = RequestMethod.POST, produces="application/json")
public ResponseEntity<Result> register(
@Valid @RequestBody RegisterRequest register
// , BindingResult bindingResult
) throws Exception {
// if(bindingResult.hasErrors()) {
//
// Result res1 = MiscUtil.getValidateError(bindingResult);
// return new ResponseEntity<Result>(res1, HttpStatus.UNPROCESSABLE_ENTITY);
// }
Result res = new Result(200, "ok");
return ResponseEntity.ok(res);
}
}
RegisterRequest (DTO/數據傳輸對象)
RegisterRequest類接受並校驗用戶注冊時輸入的信息。關於數據校驗可參考教程 [spring boot輸入數據校驗(validation)]。
public class RegisterRequest {
@SuppressWarnings("unused")
private static final org.slf4j.Logger log = LoggerFactory.getLogger(RegisterRequest.class);
@NotNull(message="手機號必須填")
@Pattern(regexp = "^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$", message="賬號請輸入11位手機號") // 手機號
private String mobile;
@NotNull(message="昵稱必須填")
@Size(min=1, max=20, message="昵稱1~20個字")
private String nickname;
@NotNull(message="密碼必須填")
@Size(min=6, max=16, message="密碼6~16位")
private String password;
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
輔助類
- Result 結果封裝類
- MiscUtil 雜項功能
運行
Eclipse左側,在項目根目錄上點擊鼠標右鍵彈出菜單,選擇:run as -> spring boot app
運行程序。 打開Postman訪問接口,運行結果如下:
用戶注冊,輸入錯誤的信息
訪問不存在的網址