Spring Boot - 使用 RestControllerAdvice 的全局自定义异常处理机制
Posted
技术标签:
【中文标题】Spring Boot - 使用 RestControllerAdvice 的全局自定义异常处理机制【英文标题】:Spring Boot - Global Custom Exception Handling Mechanism using RestControllerAdvice 【发布时间】:2018-01-04 09:38:16 【问题描述】:我正在为 Restful Web 服务使用 Spring Boot。
尝试设置一个全局自定义异常处理机制,该机制依赖于@RestControllerAdvice,它可以处理已知但也未知的异常。
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
GlobalControllerExceptionHandler:
@RestControllerAdvice
public class GlobalControllerExceptionHandler
private static final Logger LOG = Logger.getLogger(GlobalControllerExceptionHandler.class);
@ExceptionHandler(value = ConstraintViolationException.class )
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiErrorResponse constraintViolationException(ConstraintViolationException ex)
LOG.error(ex.getCause().toString());
return new ApiErrorResponse(400, "Bad Request");
@ExceptionHandler(value = NoHandlerFoundException.class )
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiErrorResponse noHandlerFoundException(Exception ex)
LOG.error(ex.getCause().toString());
return new ApiErrorResponse(404, "Resource Not Found");
@ExceptionHandler(value = Exception.class )
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiErrorResponse unknownException(Exception ex)
LOG.error(ex.getCause().toString());
return new ApiErrorResponse(500, "Internal Server Error");
ApiErrorResponse:
public class ApiErrorResponse
private int status;
private String message;
public ApiErrorResponse(int status, String message)
this.status = status;
this.message = message;
public int getStatus()
return status;
public String getMessage()
return message;
@Override
public String toString()
return new ToStringBuilder(this).append(status)
.append(message)
.toString();
问题是当我使用 3rd 方库做某事时, 未知异常可能是 404,但返回为 500!
例如使用带有未知索引的 ElasticSearch(故意查看异常类型):
"timestamp": 1501236796640,
"status": 500,
"error": "Internal Server Error",
"exception": "org.elasticsearch.client.ResponseException",
"message": "POST http://localhost:9200/fn3r4343/_search?pretty=true: HTTP/1.1 404 Not Found"
"error":
"root_cause": [
"type": "index_not_found_exception",
"reason": "no such index",
"resource.type": "index_or_alias",
"resource.id": "fn3r4343",
"index_uuid": "_na_",
"index": "fn3r4343"
],
"type": "index_not_found_exception",
"reason": "nosuchindex",
"resource.type": "index_or_alias",
"resource.id": "fn3r4343",
"index_uuid": "_na_",
"index": "fn3r4343"
"root_cause" :
[
"type" :"index_not_found_exception",
"reason" : no such index", "resource.type" : "index_or_alias",
"resource.id" : "fn3r4343",
"index_uuid" : "_na_",
"index" : "fn3r4343"
],
[
"type" : "index_not_found_exception",
"reason" : "no such index",
"resource.type" : "index_or_alias",
"resource.id" : "fn3r4343",
"index_uuid" : "_na_",
"index" : "fn3r4343"
,
"status": 404
]
"path": "/myapp/search"
正如我们所看到的,这会以 HTTP 500 状态返回,但在有效负载中它实际上是 HTTP 404!
我想要的回报是这样的:
"message" : "Index Not Found Exception",
"status" : "HTTP 404"
对于已知的 HTTP 404 异常:
"message" : "Not Found",
"status" : "HTTP 404"
是否有一种好的做法/机制来使用 RestControllerAdvice 来捕获任何类型的异常并将响应自定义为 JSON 格式,该格式对于使用 REST API 的客户端来说是可读/有用的?
这篇文章并不是专门针对 Elastic Search,而是试图通过尝试使用 @RestControllerAdvice 来处理任何类型的异常来为客户端应用程序提供正确的响应...
【问题讨论】:
【参考方案1】:引发的底层异常是org.elasticsearch.client.ResponseException
(您可能正在使用低级 REST 客户端)。
因此,在您的建议中,您需要为该异常添加一个处理程序并返回底层状态代码:
@ExceptionHandler(value = ResponseException.class )
public ApiErrorResponse noHandlerFoundException(Exception ex)
LOG.error(ex.getCause().toString());
int status = ((ResponseException) ex).getResponse().getStatusLine().getStatusCode();
return new ApiErrorResponse(status, "<some message depending on status code>");
【讨论】:
谢谢@Val...我正在使用低级 REST 客户端。您的解决方案帮助很大,但我怎样才能获得实际的异常消息?顺便说一句,我们是从 Java Ranch 认识的,干杯!【参考方案2】:我认为下面的课程应该会有所帮助:
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
@ExceptionHandler(value = MyException.class)
protected ResponseEntity<Object> handleConflict(Exception exception, WebRequest request)
FinalResponse response = new FinalResponse()//Define this Bean in a way that it contains all required paramteres to be sent in response when exception occurs
response.setErrorCd("123");
response.setMessage("Exception while processing.");
if (exception instanceof MyException)
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
else
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
【讨论】:
谢谢,但是如果 MyException.class 属于第 3 方库(例如 Elastic Search)怎么办?有没有一种通用的方法来获得任何类型的异常抛出?另外,我在哪里可以获得 response.setErrorId("123") 的实际数字?我认为获得实际数字是我问题的关键解决方案。调用 RestController 将如何使用它(例如,将为 Web 请求插入什么内容)? 通过在您的代码中添加 try catch 来包装第 3 方异常并抛出您自己的异常。即使您不执行此步骤,我也提到将在我提供的逻辑的 ELSE 部分中处理,但这不会提供信息。所以包装 3rd 方异常类会更好。我还提到定义您自己的 FinalResponse 类,当发生特定错误时,该类将包含您想要作为输出提供的字段。理想情况下,您应该在响应错误时定义错误代码和相应的消息。以上是关于Spring Boot - 使用 RestControllerAdvice 的全局自定义异常处理机制的主要内容,如果未能解决你的问题,请参考以下文章
使用 spring-boot:run 时是不是可以使用 spring-boot 命令行属性?
spring boot8.spring boot的日志框架使用