SpringBoot学习——全局处理异常

实际项目开发中,程序往往会发生各式各样的异常情况,特别是身为服务端开发人员的我们,总是不停的编写接口提供给前端调用,分工协作的情况下,避免不了异常的发生,如果直接将错误的信息直接暴露给用户,这样的体验可想而知,且对黑客而言,详细异常信息往往会提供非常大的帮助…

初窥异常

一个简单的异常请求的接口

    @GetMapping("/test")
    public String test() {
        // TODO 这里只是模拟异常,假设业务处理的时候出现错误了,或者空指针了等等...
        int i = 10/0;
        return "test1";
    }

通过swagger调用:

{
  "timestamp": "2019-01-28T06:38:42.517+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "/ by zero",
  "path": "/order/test"
}

如果这接口是给第三方调用或者是自己公司的系统,看到这种错误估计得暴走吧….

全局处理异常(自己定义返回模板)

自定义异常

在应用开发过程中,除系统自身的异常外,不同业务场景中用到的异常也不一样,为了与标题 轻松搞定全局异常 更加的贴切,定义个自己的异常,看看如何捕获…

/**
 * 自定义异常
 */
public class BusinessErrorException extends RuntimeException {

    private int code;

    public BusinessErrorException() {
        super();
    }

    public BusinessErrorException(ErrorCodeEnum errorCodeEnum) {
        super(errorCodeEnum.getMsg());
        this.setCode(errorCodeEnum.getCode());
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}
异常信息模板

定义返回的异常信息的格式,这样异常信息风格更为统一。

public class ErrorResponseEntity {
    private int code;

    private String message;

    public ErrorResponseEntity(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
控制层

仔细一看是不是和平时正常写的代码没啥区别,不要急,接着看….

    @GetMapping("/test")
    public String test(@RequestParam int num) {
        if(num < 0) {
            throw new BusinessErrorException(ErrorCodeEnum.MY_TEST_EXCEPTION);
        }
        int i = 10/num;
        return "result" + i;
    }
异常处理(关键)

注解概述

  • @ControllerAdvice 捕获 Controller 层抛出的异常,如果添加 @ResponseBody 返回信息则为JSON格式。
  • @RestControllerAdvice 相当于 @ControllerAdvice 与 @ResponseBody 的结合体。
  • @ExceptionHandler 统一处理一种类的异常,减少代码重复率,降低复杂度。

创建一个 GlobalExceptionHandler 类,并添加上 @RestControllerAdvice 注解就可以定义出异常通知类了,然后在定义的方法中添加上 @ExceptionHandler 即可实现异常的捕捉…

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {


    /**
     * 定义要捕获的异常 可以多个 @ExceptionHandler({})
     *
     * @param request  request
     * @param e        exception
     * @param response response
     * @return 响应结果
     */
    @ExceptionHandler(BusinessErrorException.class)
    public ErrorResponseEntity customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        BusinessErrorException exception = (BusinessErrorException) e;
        return new ErrorResponseEntity(exception.getCode(), exception.getMessage());
    }

    /**
     * 捕获  RuntimeException 异常
     * TODO  如果你觉得在一个 exceptionHandler 通过  if (e instanceof xxxException) 太麻烦
     * TODO  那么你还可以自己写多个不同的 exceptionHandler 处理不同异常
     *
     * @param request  request
     * @param e        exception
     * @param response response
     * @return 响应结果
     */
    @ExceptionHandler(RuntimeException.class)
    public ErrorResponseEntity runtimeExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        RuntimeException exception = (RuntimeException) e;
        return new ErrorResponseEntity(400, exception.getMessage());
    }

    /**
     * 通用的接口映射异常处理方
     * 当上面的注解都不能够命中异常时 会进入当前处理类
     */
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
                                                             HttpStatus status, WebRequest request) {
        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException exception = (MethodArgumentNotValidException) ex;
            return new ResponseEntity<>(new ErrorResponseEntity(status.value(), exception.getBindingResult().getAllErrors().get(0).getDefaultMessage()), status);
        }
        if (ex instanceof MethodArgumentTypeMismatchException) {
            MethodArgumentTypeMismatchException exception = (MethodArgumentTypeMismatchException) ex;
            logger.error("参数转换失败,方法:" + exception.getParameter().getMethod().getName() + ",参数:" + exception.getName()
                    + ",信息:" + exception.getLocalizedMessage());
            return new ResponseEntity<>(new ErrorResponseEntity(status.value(), "参数转换失败"), status);
        }
        return new ResponseEntity<>(new ErrorResponseEntity(status.value(), "系统内部异常"), status);
    }
}

全局处理异常(引入第三方jar包)

除了上面那种方式我们还可以引入第三方的jar包。为我们封装好了返回的形式。也提供了特定的方法帮助我们进行组装。

导入依赖

在 pom.xml 中添加上 spring-boot-starter-web 的依赖即可。

		<dependency>
			<groupId>org.zalando</groupId>
			<artifactId>problem-spring-web</artifactId>
			<version>0.22.1</version>
		</dependency>
自定义异常

这里要继承AbstractThrowableProblem,这是第三方jar提供的异常模板类。

/**
 * 自定义异常
 */
public class BusinessErrorException extends AbstractThrowableProblem {

    private final String errorCode;

    private final String errorMessage;

    public BusinessErrorException(ErrorCodeEnum errorCodeEnum) {
        this(ErrorConstants.DEFAULT_TYPE, errorCodeEnum.getCode(), errorCodeEnum.getMsg());
    }

    public BusinessErrorException(String msg) {
        this(ErrorConstants.DEFAULT_TYPE, "99999", msg);
    }

    public BusinessErrorException(URI type, String errorCode, String errorMessage) {
        super(type, errorMessage, Status.BAD_REQUEST, null, null, null, getErrorParameters(errorCode, errorMessage));
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    private static Map<String, Object> getErrorParameters(String errorCode, String errorMessage) {
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("errorCode", errorCode);
        parameters.put("errorMessage", errorMessage);
        return parameters;
    }

}
异常处理(关键)

异常转换的类中,我们处理到异常之后返回的都是Problem类,这个也是第三方jar提供的返回前端的异常模板类。具体我们定义的异常信息放到了响应的header中。前端可以判断status并从header中返回异常信息。

@ControllerAdvice
public class ExceptionTranslator implements ProblemHandling {
    private final Logger log = LoggerFactory.getLogger(ExceptionTranslator.class);

    /**
     * Post-process Problem payload to add the message key for front-end if needed
     */
    @Override
    public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) {
        if (entity == null || entity.getBody() == null) {
            return entity;
        }
        Problem problem = entity.getBody();
        if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) {
            return entity;
        }
        ProblemBuilder builder = Problem.builder()
            .withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
            .withStatus(problem.getStatus())
            .withTitle(problem.getTitle())
            .with("path", request.getNativeRequest(HttpServletRequest.class).getRequestURI());

        if (problem instanceof ConstraintViolationProblem) {
            builder
                .with("violations", ((ConstraintViolationProblem) problem).getViolations())
                .with("message", ErrorConstants.ERR_VALIDATION);
            return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
        } else {
            builder
                .withCause(((DefaultProblem) problem).getCause())
                .withDetail(problem.getDetail())
                .withInstance(problem.getInstance());
            problem.getParameters().forEach(builder::with);
            if (!problem.getParameters().containsKey("message") && problem.getStatus() != null) {
                builder.with("message", "error.http." + problem.getStatus().getStatusCode());
            }
            return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
        }
    }

    @Override
    public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) {
        BindingResult result = ex.getBindingResult();
        List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
            .map(f -> new FieldErrorVM(f.getObjectName(), f.getField(), f.getCode()))
            .collect(Collectors.toList());

        Problem problem = Problem.builder()
            .withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
            .withTitle("Method argument not valid")
            .withStatus(defaultConstraintViolationStatus())
            .with("message", ErrorConstants.ERR_VALIDATION)
            .with("fieldErrors", fieldErrors)
            .build();
        return create(ex, problem, request);
    }



    @ExceptionHandler(BadRequestAlertException.class)
    public ResponseEntity<Problem> handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) {
        return create(ex, request, HeaderUtil.createFailureAlert(ex.getEntityName(), ex.getErrorKey(), ex.getMessage()));
    }

    @ExceptionHandler(BusinessErrorException.class)
    public ResponseEntity<Problem> handleBusinessErrorException(BusinessErrorException ex, NativeWebRequest request) {
        return create(ex, request, HeaderUtil.createBusinessFailureAlert(ex.getErrorCode(), ex.getErrorMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Problem> handleException(Exception ex, NativeWebRequest request) {
        log.warn("resource 层抛出异常,Exception=",ex);
        if (ex instanceof BusinessErrorException) {
            BusinessErrorException businessErrorException = (BusinessErrorException) ex;
            return create(businessErrorException, request, HeaderUtil.createBusinessFailureAlert(businessErrorException.getErrorCode(), businessErrorException.getErrorMessage()));
        }else if(ex instanceof MethodArgumentNotValidException){
            return handleMethodArgumentNotValid((MethodArgumentNotValidException)ex,request);
        } else {
            BusinessErrorException businessErrorExceptionOther = new BusinessErrorException(ErrorCodeEnum.SYSTEM_RPC_EXCEPTION);
            return create(businessErrorExceptionOther, request, HeaderUtil.createBusinessFailureAlert(businessErrorExceptionOther.getErrorCode(), businessErrorExceptionOther.getErrorMessage()));
        }
    }

    @ExceptionHandler(ConcurrencyFailureException.class)
    public ResponseEntity<Problem> handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) {
        Problem problem = Problem.builder()
            .withStatus(Status.CONFLICT)
            .with("message", ErrorConstants.ERR_CONCURRENCY_FAILURE)
            .build();
        return create(ex, problem, request);
    }
}

总结

两种处理异常方式大同小异,以这种方式统处理异常能方便我们的开发,工作当中也是使用这种方式。


版权声明:本文为hxyascx原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。