【简约入门】Springboot统一异常处理

2,017 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

Springboot统一异常处理是一直有的需求,这次使用第三方包zalando的problem-spring-web来实现。

概述

通过Spring Boot整合Problem Spring Web,可以方便的处理服务异常,并且可以给出合适的返回值。

Problem

Problem类部分源码如下,它用于规范返回值内容,统一Restful Api,可以使服务中异常返回值具有一定标准。

public interface Problem {
    URI DEFAULT_TYPE = URI.create("about:blank");

    default URI getType() {
        return DEFAULT_TYPE;
    }

    @Nullable
    default String getTitle() {
        return null;
    }

    @Nullable
    default StatusType getStatus() {
        return null;
    }

    @Nullable
    default String getDetail() {
        return null;
    }

    @Nullable
    default URI getInstance() {
        return null;
    }

    default Map<String, Object> getParameters() {
        return Collections.emptyMap();
    }

示例

Problem简单的构建方式如下

ProblemBuilder builder = Problem.builder()
.withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
.withStatus(problem.getStatus())
.withTitle(problem.getTitle())
.with("path", ((HttpServletRequest)request.getNativeRequest(HttpServletRequest.class)).getRequestURI());

实践

maven

首先引入依赖

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem-spring-web</artifactId>
    <version>${zalando.version}</version>
</dependency>
异常处理实现

ExceptionHandler实现ProblemHandling,并添加自定义异常处理方法ExceptionHandler#handleException来处理MyException自定义异常。

注解解释
  • @ControllerAdvice:Spring框架提供了@ControllerAdvice注解,帮助你将其应用到所有的控制器上
  • @ExceptionHandler:接受请求处理方法抛出的异常。@ExceptionHandler标记的方法类里加上@ControllerAdvice,这样,所有的控制器都可以用了这个异常处理了。
ExceptionHandler代码
@ControllerAdvice 
public class ExceptionHandler implements ProblemHandling {

    @ExceptionHandler({MyException.class})
    public ResponseEntity<Problem> handleException(MyException ex, NativeWebRequest request) {
        ex.setStackTrace(new StackTraceElement[0]);
        return this.create(ex, request, HeaderUtil.createError(ex.getErrorCode(), ex.getTitle()));
    }
}
自定义异常类

自定义异常类MyException,继承AbstractThrowableProblem,添加需要的参数

public class MyException extends AbstractThrowableProblem {
    private static final long serialVersionUID = 1L;
    private String errorCode;
    private Object[] args;
    private String title;

    public MyException(MyError myError) {
        this(myError, Status.BAD_REQUEST);
    }

    public MyException(MyError myError, Status status) {
        super(ErrorConstants.DEFAULT_TYPE, myError.getTitle(), status, myError.getDetail());
        this.errorCode = myError.getCode();
        this.args = myError.getArgs();
        this.title = myError.getTitle();
    }
}
通用的返回异常处理

增加通用的返回异常处理,可以将返回值都设置为ResponseEntity,保证了标准Restful Api json 返回。在实际返回前,会将返回值的异常赋值给MyException这个自定义异常类中,随后异常类交由ExceptionHandler#process进行处理。拿出ResponseEntity<Problem>的body信息,判断对应的Problem,根据不同Problem进行处理。

既不是ConstraintViolationProblem,也不是DefaultProblem的会直接返回,ConstraintViolationProblemDefaultProblem分别有自己的处理返回方式。

  • 代码实现
public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) {
    if (entity != null && entity.getBody() != null) {
        Problem problem = (Problem)entity.getBody();
        if (!(problem instanceof ConstraintViolationProblem) && !(problem instanceof DefaultProblem)) {
            return entity;
        } else {
            ProblemBuilder builder = Problem.builder().withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType()).withStatus(problem.getStatus()).withTitle(problem.getTitle()).with("path", ((HttpServletRequest)request.getNativeRequest(HttpServletRequest.class)).getRequestURI());
            ThrowableProblem newProblem;
            if (problem instanceof ConstraintViolationProblem) {
                builder.with("violations", ((ConstraintViolationProblem)problem).getViolations()).with("message", "验证错误");
                newProblem = builder.build();
                newProblem.printStackTrace();
                newProblem.setStackTrace(new StackTraceElement[0]);
                return new ResponseEntity(newProblem, entity.getHeaders(), entity.getStatusCode());
            } else {
                builder.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());
                }

                newProblem = builder.build();
                newProblem.setStackTrace(new StackTraceElement[0]);
                return new ResponseEntity(newProblem, entity.getHeaders(), entity.getStatusCode());
            }
        }
    } else {
        return entity;
    }
}
  • 业务代码异常抛出
throw MyException.MyException(new MyExceptions("2001", "数据同步失败"));
  • 返回消息体
{
    "cause": null,
    "stackTrace": [],
    "type": "",
    "title": "数据同步失败",
    "status": "BAD_REQUEST",
    "detail": null,
    "instance": null,
    "parameters": {},
    "errorCode": "2001",
    "args": null,
    "message": "数据同步失败",
    "localizedMessage": "数据同步失败",
    "suppressed": []
}

小结

在本文中,说明了如何使用Problem Spring Web库,并且使用ProblemResponseEntity响应来创建包含错误详细信息的响应。首先在Spring Boot应用程序中配置maven依赖,并创建Problem对象的自定义实现。以及实现处理异常和统一返回值。