统一异常处理@ControllerAdvice及参数校验@Validated

一、异常处理

有异常就必须处理,通常会在方法后面throws异常,或者是在方法内部进行try catch处理。直接throws Exception
直接throws Exception,抛的异常太过宽泛,最好能抛出准确的异常,比如throws IOException之类。

User getUserById(Integer id) throws IOException,BusinessException,InterruptedException;

如果有多种异常,那么方法后面要throws IOException,InterruptedException又显得冗长。
而且,异常一直向上抛,上层的类还是得处理这些异常。try catch捕获异常
阿里巴巴的java规范中有一条,"最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。"
也就是说在Controller层,最好不要又throws Exception继续往上抛了。
但是,如果在Controller层进行大量的捕获异常,可能会出现大量的非常多的try catch代码块。

/**
  * 以下这种代码写法很丑。
  */
@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很难看,不建议这样写。

二、统一异常处理

@ControllerAdvice配合@ExceptionHandler,可以很方便地统一处理异常。
首先是自定义的业务异常类,如下所示:

/**
 * @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();。如下示:

@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统一异常处理,那么在控制层就无须再处理了。

三、参数校验@Validated

@ControllerAdvice除了进行统一异常,还能配合@Validated注解进行参数校验。
Controller层的参数通常都需要检验,经常会看到大量的判空,然后返回错误提示,比如"名字不能为空"之类的提示。
有些人可能会像下面这样写:

/**
  *  以下的参数校验实在是太繁杂了。不建议这样写。
  */
@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对象上面添加注解:

@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注解。常见的参数校验注解如下:

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
参考资料:@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常_kinginblue的博客-CSDN博客使用spring validation完成数据后端校验_下一秒升华的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值