持续创作,加速成长!这是我参与「掘金日新计划 · 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的会直接返回,ConstraintViolationProblem和DefaultProblem分别有自己的处理返回方式。
- 代码实现
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库,并且使用Problem与ResponseEntity响应来创建包含错误详细信息的响应。首先在Spring Boot应用程序中配置maven依赖,并创建Problem对象的自定义实现。以及实现处理异常和统一返回值。