統一異常處理@ControllerAdvice

1、異常處理

有異常就必須處理,一般會在方法後面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

2、統一異常處理

@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

3、參數校驗@Validated

@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/

相關文章
相關標籤/搜索