有異常就必須處理,一般會在方法後面throws異常,或者是在方法內部進行try catch處理。
直接throws Exception
直接throws Exception,拋的異常太過寬泛,最好能拋出準確的異常,好比throws IOException之類。html
User getUserById(Integer id) throws IOException,BusinessException,InterruptedException;
若是有多種異常,那麼方法後面要throws IOException,InterruptedException又顯得冗長。
並且,異常一直向上拋,上層的類仍是得處理這些異常。
try catch捕獲異常
阿里巴巴的java規範中有一條,"最外層的業務使用者,必須處理異常,將其轉化爲用戶能夠理解的內容。"
也就是說在Controller層,最好不要又throws Exception繼續往上拋了。
可是,若是在Controller層進行大量的捕獲異常,可能會出現大量的很是多的try catch代碼塊。java
/** * 如下這種代碼寫法很醜。 */ @PostMapping("/id") public ResultInfo getUserById(HttpServletRequest request) { String postData = null; try { postData = IOUtils.toString(request.getInputStream(), "UTF-8"); } catch (IOException e) { logger.error("IO異常); } JSONObject postJson = JSONObject.parseObject(postData); logger.info("請求中獲取的參數爲:" + postJson); String id=postJson.getString("id"); User user=new User(); try{ user=getUserById(id) }catch(BusinessException e){ logger.error("根據id查找用戶發生異常,id:"+{}); } //... }
這麼多的try catch很難看,不建議這樣寫。git
@ControllerAdvice配合@ExceptionHandler,能夠很方便地統一處理異常。
首先是自定義的業務異常類,以下所示:github
/** * @Description: 自定義異常。 * 這裏的BusinessException繼承於RuntimeException,而非Exception。 * 若是繼承的是Exception,那麼在服務層仍是得進行異常處理。 */ public class BusinessException extends RuntimeException { private static final long serialVersionUID = 1L; private String code; private String msg; public BusinessException(ErrorType error) { this.msg=error.getMsg(); this.code = error.getCode(); } public BusinessException(String code, String msg) { super(msg); this.code = code; } public BusinessException(String msg) { super(msg); } //屬性的getter、setter,這裏忽略不寫。請自行補上。 }
接着是重點,@ControllerAdvice進行統一異常處理。經過 @ExceptionHandler指定對應的異常處理措施。
以下所示:正則表達式
@ControllerAdvice @Slf4j public class GlobalExceptionHandler { /** * 處理全部業務異常 * @param e * @return */ @ExceptionHandler(BusinessException.class) @ResponseBody public ResultInfo handleBusinessException(BusinessException e){ log.error(e.getMessage()); ResultInfo response = new ResultInfo(); response.setMsg(e.getMsg()); response.setCode(e.getCode()); response.setData(e.getMessage()); return response; } /** * 處理全部接口數據驗證異常。對應的是@Validated註解。 * @param e * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody public ResultInfo handleMethodArgumentNotValidException(MethodArgumentNotValidException e){ log.error(e.getMessage(), e); ResultInfo response = new ResultInfo(ErrorType.FAIL); response.setData(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); return response; } //這個方法能夠攔截全部的異常,最好放在最下面。 @ExceptionHandler(Exception.class) @ResponseBody public ResultInfo handleException(){ return new ResultInfo(ErrorType.EXCEPTION_FAIL); } //handleException()方法也能夠寫成以下格式 。 // @ExceptionHandler() // @ResponseBody // public String handleException(Exception e){ // return "Exception Deal! " + e.getMessage(); // } }
有了自定義異常,就能夠在服務層拋出,直接在方法內部 throw new BusinessException();。以下示:app
@Service public class ExceptionServiceImpl implements ExceptionService { @Override @Transactional public User getUserById(Integer id) { //實際項目中這裏通常都會有Dao層查詢user, // 好比 User user =userDao.getUser(id); // 此Demo爲了方便,忽略不寫。直接假設user查詢結果爲null User user=null; if(user==null) { throw new BusinessException(ErrorType.ID_IS_NULL); } return user; } @Override @Transactional public String getUserName(User user) { String name=user.getUserName(); if(name==null) { throw new BusinessException(ErrorType.NAME_IS_NULL); } return name; } }
有了@ControllerAdvice統一異常處理,那麼在控制層就無須再處理了。ide
@ControllerAdvice除了進行統一異常,還能配合@Validated註解進行參數校驗。
Controller層的參數一般都須要檢驗,常常會看到大量的判空,而後返回錯誤提示,好比"名字不能爲空"之類的提示。
有些人可能會像下面這樣寫:post
/** * 如下的參數校驗實在是太繁雜了。不建議這樣寫。 */ @PostMapping("register/h5") @ResponseBody public BaseResult registerInMiniProgram(HttpServletRequest request,HttpServletResponse response) throws BusinessException, IOException { //從請求中取出參數的代碼,此處忽略,如下是參數校驗 //稅號爲空就返回錯誤提示"稅號不能爲空" if(StringUtils.isEmpty(taxNo)){ return new BaseResult( ErrorType.COMPANY_TAX_NO_NOT_NULL ); } //企業名字爲空就返回錯誤提示"企業名字不能爲空" if(StringUtils.isEmpty(companyName)){ return new BaseResult( ErrorType.COMPANY_NAME_NOT_NULL ); } //手機號碼爲空就返回錯誤提示"手機號碼不能爲空" if(StringUtils.isEmpty(phoneNumber)){ return new BaseResult( ErrorType.PHONENUMBER_IS_NULL ); } // ... }
這些冗長的參數校驗,能夠經過@Validated註解簡化。
以下所示,直接在bean對象上面添加註解:this
@Data @AllArgsConstructor @NoArgsConstructor public class User { @NotNull(message = "id不能爲空") private Integer id; @NotBlank(message = "名字不能爲空") private String userName; @Min(value = 18,message = "年齡不能小於18歲") private Integer age; @NotNull(message = "手機號碼不能爲空") private String phoneNumber; }
其中的類上方註解@Data之類是Lombok註解,詳情見:https://www.cnblogs.com/expiator/p/10854141.html
而@NotNull,@Min這些是Validation註解。常見的參數校驗註解以下:.net
JSR提供的校驗註解: @Null 被註釋的元素必須爲 null @NotNull 被註釋的元素必須不爲 null @NotBlank 被註釋的元素必須不爲 null,不爲空格組成 @AssertTrue 被註釋的元素必須爲 true @AssertFalse 被註釋的元素必須爲 false @Min(value) 被註釋的元素必須是一個數字,其值必須大於等於指定的最小值 @Max(value) 被註釋的元素必須是一個數字,其值必須小於等於指定的最大值 @DecimalMin(value) 被註釋的元素必須是一個數字,其值必須大於等於指定的最小值 @DecimalMax(value) 被註釋的元素必須是一個數字,其值必須小於等於指定的最大值 @Size(max=, min=) 被註釋的元素的大小必須在指定的範圍內 @Digits (integer, fraction) 被註釋的元素必須是一個數字,其值必須在可接受的範圍內 @Past 被註釋的元素必須是一個過去的日期 @Future 被註釋的元素必須是一個未來的日期 @Pattern(regex=,flag=) 被註釋的元素必須符合指定的正則表達式
@Validated註解的參數校驗一樣能夠進行統一異常處理。
異常類型爲MethodArgumentNotValidException.class 。
在統一異常處理類GlobalExceptionHandler 中加入以下代碼:
/** * 處理全部接口數據驗證異常。對應的是@Validated註解。 * @param e * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody public ResultInfo handleMethodArgumentNotValidException(MethodArgumentNotValidException e){ log.error(e.getMessage(), e); ResultInfo response = new ResultInfo(); response.setCode(ErrorType.FAIL.getCode()); response.setMsg(ErrorType.FAIL.getMsg()); response.setData(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); return response; }
只須要在方法參數前面加上註解@Validated ,以下所示:
@RestController public class ExceptionController { @Autowired private ExceptionService exceptionService; /** * 使用了ControllerAdvice進行統一異常處理,就不須要在Controller層再拋異常的。 * @param id * @return * @throws BusinessException */ @PostMapping("/id") public ResultInfo getUserById(@Validated @RequestParam("id") Integer id) { User user=exceptionService.getUserById(id); return new ResultInfo(ErrorType.SUCCESS.getCode(),ErrorType.SUCCESS.getMsg(),user); } /** * 使用@Validated校驗數據。 * 校驗發生異常時,在GlobalExceptionHandler類中經過MethodArgumentNotValidException處理。 * @param user * @return * @throws BusinessException */ @PostMapping("/name") public ResultInfo get(@Validated @RequestBody User user) { String name=exceptionService.getUserName(user); return new ResultInfo(ErrorType.SUCCESS.getCode(),ErrorType.SUCCESS.getMsg(),name); } }
完整代碼: https://github.com/firefoxer1992/SpringBootDemo/tree/master/controllerAdvice 參考資料: https://blog.csdn.net/kinginblue/article/details/70186586 https://blog.csdn.net/u013815546/article/details/77248003/