有异常就必须处理,一般会在方法后面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/