1 全局异常处理与HttpServletResponse响应

@RestControllerAdvice是帮助我们把信息转成json格式返回
@ResponseBody是将方法中的字符串转成json格式同一返回,一般该方法返回值为Object

1.1 使用@RestControllerAdvice搭配@ExceptionHandler(推荐)

全局异常处理类只需要在类上标注@RestControllerAdvice,并在处理相应异常的方法上使用@ExceptionHandler注解,写明处理哪个异常即可。

可以在方法参数中添加HttpServletResponse参数,spring会自动帮助我们获取,然后我们拦截到具体错误信息就可以直接返回

@RestControllerAdvice
public class GlobalControllerAdvice {
    private static final String BAD_REQUEST_MSG = "客户端请求参数错误";

	@ExceptionHandler(value = {IllegalArgumentException.class})
    public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    // <1> 处理 form data方式调用接口校验失败抛出的异常 
    @ExceptionHandler(BindException.class)
    public ResultInfo bindExceptionHandler(BindException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        List<String> collect = fieldErrors.stream()
                .map(o -> o.getDefaultMessage())
                .collect(Collectors.toList());
        return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
    }
    // <2> 处理 json 请求体调用接口校验失败抛出的异常 
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultInfo methodArgumentNotValidExceptionHandler(HttpServletResponse httpServletResponse,MethodArgumentNotValidException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        List<String> collect = fieldErrors.stream()
                .map(o -> o.getDefaultMessage())
                .collect(Collectors.toList());
        return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
    }
    // <3> 处理单个参数校验失败抛出的异常
    @ExceptionHandler(ConstraintViolationException.class)
    public ResultInfo constraintViolationExceptionHandler(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        List<String> collect = constraintViolations.stream()
                .map(o -> o.getMessage())
                .collect(Collectors.toList());
        return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
    }
    
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Object  handle(Exception e) {
        logger.error(e.getMessage(), e);
        Map<String,Object> map = new HashMap<>();
        if (e instanceof MyException) {
            MyException myException = (MyException) e;
            map.put("code",500);
            map.put("msg",myException.getMessage());
            return map;
        } else {
            e.printStackTrace();
            map.put("code",500);
            map.put("msg","出错啦");
            return map;
        }
    }
    
 

事实上,在全局异常处理类中,我们可以写多个异常处理方法,以下总结了三种参数校验时可能引发的异常:

  • 使用form data方式调用接口,校验异常抛出 BindException

  • 使用 json 请求体调用接口,校验异常抛出 MethodArgumentNotValidException

  • 单个参数校验异常抛出ConstraintViolationException

注:单个参数校验需要在参数上增加校验注解,并在类上标注@Validated。

全局异常处理类可以添加各种需要处理的异常,比如添加一个对Exception.class的异常处理,当所有ExceptionHandler都无法处理时,由其记录异常信息,并返回友好提示。

1.2 继承自ResponseEntityExceptionHandler搭配@RestControllerAdvice

注意:过滤器中的异常无法被拦截

@RestControllerAdvice
@Order(80)
@Slf4j
public class ValidationExceptionHandle extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        logger.error(ex.getMessage());
        return validExceptionCommon(ex.getBindingResult());
    }

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        logger.error(ex.getMessage());
        return validExceptionCommon(ex.getBindingResult());
    }

    /**
     * 校验异常统一返回格式
     * @param result
     * @return
     */
    private ResponseEntity<Object> validExceptionCommon(BindingResult result){
        ResultVo<Object> resultVo = new ResultVo<>();
        if (result.hasErrors()) {
            List<ObjectError> errors = result.getAllErrors();
            for (ObjectError error : errors) {
                FieldError fieldError = (FieldError) error;
                resultVo.setCode(500);
                resultVo.setMsg(fieldError.getDefaultMessage());
            }

        }
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultVo);
    }


}

2.ExceptionResolver与@ControllerAdvice(@RestControllerAdvice)的选择

在基于Spring框架的项目中,可以通过在ApplicationContext-MVC.xml(即SpringMVC配置)文件中配置 ExceptionResolver 的bean ,来配置 全局捕获异常处理 类,然后自定义异常处理类处理。注意如果是spring配置文件中定义过的ExceptionResolver 类,不需要添加@Component。如果是SpringBoot 则需要。这是因为springboot没有自定义配置全局异常捕获类,所以需要添加@Component,来标识该类为Bean。

异常处理可以分为三种。

 第一种是进入@Controller标识的方法前 产生的异常,例如URL地址错误。这种异常处理需要 异常处理类通过实现 ErrorController 来处理。

 第二种是进入Controller时,但还未进行逻辑处理时 产生的异常,例如传入参数数据类型错误。这种异常处理需要用@ControllerAdvice标识并处理,建议继承 ResponseEntityExceptionHandler 来处理,该父类包括了很多已经被@ExceptionHandler 注解标识的方法,包括一些参数转换,请求方法不支持等类型等等。

 第三种时进入Controller,进行逻辑处理时产生的异常,例如NullPointerException异常等。这种异常处理也可以用@ControllerAdvice来标识并进行处理,也建议继承ResponseEntityExceptionHandler 处理, 这里我们可以用@ExceptionHandler 自定义捕获的异常并处理。

 以上三种情况都是restful的情况,结果会返回一个Json。
  • 如果希望返回跳转页面,则需要实现HandlerExceptionResolver类来进行异常处理并跳转。

  • 注意@ControllerAdvice需要搭配@ExceptionHandler来使用,自定义捕获并处理异常。

  • @ControllerAdvice一样可以做页面跳转,返回String不要加@ResponseBody

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐