Spring Boot提供了一个非常棒的开箱即用的异常处理机制。errorcontroller的默认实现在捕获和处理异常方面做得很好。此外,我们还可以定义自己的@ExceptionHandler s来捕获和处理特定的异常。但是,仍有改进的空间:
{
\"timestamp\": \"2018-09-23T15:05:32.681+0000\",
\"status\": 400,
\"error\": \"Bad Request\",
\"errors\": [
{
\"codes\": [
\"NotBlank.dto.name\",
\"NotBlank.name\",
\"NotBlank.java.lang.String\",
\"NotBlank\"
],
\"arguments\": [
{
\"codes\": [
\"dto.name\",
\"name\"
],
\"arguments\": null,
\"defaultMessage\": \"name\",
\"code\": \"name\"
}
],
\"defaultMessage\": \"{name.not_blank}\",
\"objectName\": \"dto\",
\"field\": \"name\",
\"rejectedValue\": null,
\"bindingFailure\": false,
\"code\": \"NotBlank\"
}
],
\"message\": \"Validation failed for object=\'dto\'. Error count: 1\",
\"path\": \"/\"
}
当然,我们可以通过注册一个自定义ErrorAttributes实现来改善这一点,
对于验证错误,可以轻松地将约束中的某些参数公开给内插消息。例如,int可以使用{}占位符语法将值的最小值传递给插值消息。但是,对于其他例外情况就不是这样了:
public class UserAlreadyExistsException extends RuntimeException {
//如何将该值显示给内插消息?
private final String username;
//构造函数以及getter和setter
}
如果我们为所有异常提供对应用程序级错误代码的内置支持,那就牛逼了。有时,仅使用适当的HTTP状态代码, 我们就无法找出到底出了什么问题。例如,如果同一端点上的两个完全不同的错误具有相同的状态代码又怎么办呢?
改进空间
spring-boot-starter旨在提供一种良好的、一致的、自以为是的方法来处理各种异常。建立在Spring Boot的异常处理机制之上,error - Spring - Boot -starter会提供:
- 处理所有异常的一致方法——不管它是验证/绑定错误,还是自定义特定于域的错误,甚至是与Spring相关的错误。所有这些都将由WebErrorHandler实现来处理(不再有ErrorController vs @ExceptionHandler)
- 内置对特定于应用程序的错误代码的支持,同样适用于所有可能的错误。
- 简单的错误消息插值使用MessageSource。
- 可自定义的HTTP错误表示。
- 将异常中的参数暴露给错误消息。
默认错误表示
默认情况下,错误将是具有以下架构的JSON结果:
//对于每个错误,errors数组中都有一个代码/消息组合
//对于每个错误,errors数组中都有一个代码/消息组合
为了自定义此表示形式,只需将HttpErrorAttributesAdapter实现注册为 Spring Bean即可。
一致的错误处理方法
所有异常将由实现处理WebErrorHandler。默认情况下,此入门程序将参考一些内置WebErrorHandler方法来处理以下特定异常:
- 处理所有验证/绑定异常的实现。
- 用于处理带有注释的自定义异常的实现@ExceptionMapping。
- 一个处理Spring MVC特定异常的实现。
- 并且如果Spring Security在类路径中,则为一个处理Spring Security特定异常的实现。
以通过实现WebErrorHandler实现并将其注册为Spring Bean来处理自己的异常处理程序。
内置错误代码支持
尽管在RESTful API中建议使用适当的HTTP状态代码,但有时,我们需要更多信息来找出到底出了什么问题。这就是错误代码的来源。可以将错误代码视为错误的机器可读描述。每个异常可以映射到至少一个错误代码。
异常到错误代码的映射根据异常类型而异:
- 验证错误代码将从消息对应约束注释的属性,如@NotBlank(消息= \" name.required”)。
- 的错误代码属性用于带注释的异常@ExceptionMapping如下图所示:
@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = \"user.already_exists\")
public class UserAlreadyExistsException extends RuntimeException {}
- 这是来自的自定义实现的代码WebErrorHandler:
public class ExistedUserHandler implements WebErrorHandler {
@Override
public boolean canHandle(Throwable exception) {
return exception instanceof UserAlreadyExistsException;
}
@Override
public HandledException handle(Throwable exception) {
return new HandledException(\"user.already_exists\", BAD_REQUEST, null);
}
}
暴露参数
与Spring Boot一样,可以将验证参数从约束注释(例如@Min(value = 18, message = \"age.min\")传递给要插入的消息:
age.min = The minimum age is {0}!
另外,为了支持验证错误的这个特性,我们使用@脚注扩展了自定义异常。例如,如果我们要在下面的消息中指定已经获取的用户名:
user.already_exists=Another user with the \'{0}\' username already exists
你可以写:
@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = \"user.already_exists\")
public class UserAlreadyExistsException extends RuntimeException {
@ExposeAsArg(0) private final String username;
// 构造函数
}
结论
在本文中,通过引入error - Spring - Boot -starter starter包,我们列举了Spring Boot异常处理方法的几个可能改进之处。如果对你有用就关注一下吧。