Spring引导REST服务异常处理
Spring Boot REST service exception handling
我正在尝试建立一个大型 REST 服务服务器。我们正在使用 Spring Boot 1.2.1 Spring 4.1.5 和 Java 8。我们的控制器正在实现 @RestController 和标准的 @RequestMapping 注释。
我的问题是 Spring Boot 设置了控制器异常的默认重定向到 /error
。来自文档:
Spring Boot provides an /error mapping by default that handles all errors in a sensible way, and it is registered as a ‘global’ error page in the servlet container.
多年来使用 Node.js 编写 REST 应用程序,这对我来说是不明智的。服务端点生成的任何异常都应该在响应中 return。我不明白为什么你会发送一个重定向到最有可能是 Angular 或 JQuery SPA 消费者的地方,他们只是在寻找答案而不能或不会采取任何行动重定向。
我想做的是设置一个全局错误处理程序,它可以接受任何异常——有目的地从请求映射方法中抛出或由 Spring 自动生成(如果没有找到处理程序方法则为 404请求路径签名)和 return 标准格式的错误响应(400、500、503、404)到客户端,没有任何 MVC 重定向。具体来说,我们将获取错误,使用 UUID 将其记录到 NoSQL,然后 return 向客户端发送正确的 HTTP 错误代码以及 JSON 正文中日志条目的 UUID。
文档对如何执行此操作含糊不清。在我看来,您必须以某种方式创建自己的 ErrorController implementation or use ControllerAdvice,但我看到的所有示例仍然包括将响应转发到某种错误映射,这无济于事。其他示例建议您必须列出要处理的每个异常类型,而不是仅列出 "Throwable" 并获取所有内容。
任何人都可以告诉我我错过了什么,或者在不建议 Node.js 更容易处理的链条的情况下为我指明正确的方向吗?
我认为ResponseEntityExceptionHandler
符合您的要求。 HTTP 400 的示例代码片段:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class,
HttpRequestMethodNotSupportedException.class})
public ResponseEntity<Object> badRequest(HttpServletRequest req, Exception exception) {
// ...
}
}
你可以查看这个post
默认情况下 Spring 引导给出 json 错误详细信息。
curl -v localhost:8080/greet | json_pp
[...]
< HTTP/1.1 400 Bad Request
[...]
{
"timestamp" : 1413313361387,
"exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
"status" : 400,
"error" : "Bad Request",
"path" : "/greet",
"message" : "Required String parameter 'name' is not present"
}
它也适用于所有类型的请求映射错误。查看这篇文章
http://www.jayway.com/2014/10/19/spring-boot-error-responses/
如果你想创建日志到NoSQL。您可以在记录它的地方创建 @ControllerAdvice,然后重新抛出异常。
文档中有示例
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
新答案(2016-04-20)
使用 Spring 引导 1.3。1.RELEASE
新步骤 1 - 将以下属性添加到 application.properties:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Much easier than modifying the existing DispatcherServlet instance (as below)! - JO'
如果使用完整的 RESTful 应用程序,禁用静态资源的自动映射非常重要,因为如果您使用 Spring Boot 的默认配置来处理静态资源,那么资源处理程序将处理请求(它最后排序并映射到 /** 这意味着它会拾取应用程序中任何其他处理程序尚未处理的任何请求)因此调度程序 servlet 没有机会抛出异常。
新答案(2015-12-04)
使用 Spring 启动 1.2.7.RELEASE
新步骤 1 - 我发现设置 "throExceptionIfNoHandlerFound" 标志的侵入性小得多的方法。在您的应用程序初始化 class:
中用此替换下面的 DispatcherServlet 替换代码(第 1 步)
@ComponentScan()
@EnableAutoConfiguration
public class MyApplication extends SpringBootServletInitializer {
private static Logger LOG = LoggerFactory.getLogger(MyApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
在这种情况下,我们在现有的 DispatcherServlet 上设置标志,它保留 Spring 引导框架的任何自动配置。
我发现的另一件事 - @EnableWebMvc 注释对 Spring Boot.是的,该注释启用了诸如能够捕获所有控制器异常之类的功能,如下所述,但它也扼杀了 Spring Boot 通常会提供的很多有用的自动配置。使用 Spring Boot.
时要格外小心地使用该注释
原答案:
经过大量研究并跟进此处发布的解决方案(感谢您的帮助!)以及大量跟踪 Spring 代码的运行时,我终于找到了一个可以处理所有问题的配置异常(不是错误,而是继续阅读)包括 404。
步骤 1 - 告诉 SpringBoot 在 "handler not found" 情况下停止使用 MVC。我们希望 Spring 抛出异常,而不是向客户端返回一个重定向到“/error”的视图。为此,您需要在其中一项配置中添加一个条目 classes:
// NEW CODE ABOVE REPLACES THIS! (2015-12-04)
@Configuration
public class MyAppConfig {
@Bean // Magic entry
public DispatcherServlet dispatcherServlet() {
DispatcherServlet ds = new DispatcherServlet();
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
}
这样做的缺点是它取代了默认的调度程序 servlet。这对我们来说还不是问题,没有出现副作用或执行问题。如果您出于其他原因要对调度程序 servlet 执行任何其他操作,那么这里就是执行它们的地方。
第 2 步 - 现在 spring 引导将在未找到处理程序时抛出异常,该异常可以在统一的异常处理程序中与任何其他异常一起处理:
@EnableWebMvc
@ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Throwable.class)
@ResponseBody
ResponseEntity<Object> handleControllerException(HttpServletRequest req, Throwable ex) {
ErrorResponse errorResponse = new ErrorResponse(ex);
if(ex instanceof ServiceException) {
errorResponse.setDetails(((ServiceException)ex).getDetails());
}
if(ex instanceof ServiceHttpException) {
return new ResponseEntity<Object>(errorResponse,((ServiceHttpException)ex).getStatus());
} else {
return new ResponseEntity<Object>(errorResponse,HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String,String> responseBody = new HashMap<>();
responseBody.put("path",request.getContextPath());
responseBody.put("message","The URL you have reached is not in service at this time (404).");
return new ResponseEntity<Object>(responseBody,HttpStatus.NOT_FOUND);
}
...
}
请记住,我认为“@EnableWebMvc”注释在这里很重要。似乎 none 没有它也能工作。就是这样 - 您的 Spring 启动应用程序现在将在上述处理程序 class 中捕获所有异常,包括 404,您可以随意处理它们。
最后一点 - 似乎没有办法让它捕获抛出的错误。我有一个古怪的想法,即使用方面来捕获错误并将它们转化为上述代码随后可以处理的异常,但我还没有时间实际尝试实现它。希望这对某人有所帮助。
任何 comments/corrections/enhancements 将不胜感激。
解决方案
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
和
@EnableWebMvc
@ControllerAdvice
在 Spring Boot 1.3.1 上为我工作,但在 1.2.7
上没有工作
这段代码怎么样?我使用回退请求映射来捕获 404 错误。
@Controller
@ControllerAdvice
public class ExceptionHandlerController {
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
//If exception has a ResponseStatus annotation then use its response code
ResponseStatus responseStatusAnnotation = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
return buildModelAndViewErrorPage(request, response, ex, responseStatusAnnotation != null ? responseStatusAnnotation.value() : HttpStatus.INTERNAL_SERVER_ERROR);
}
@RequestMapping("*")
public ModelAndView fallbackHandler(HttpServletRequest request, HttpServletResponse response) throws Exception {
return buildModelAndViewErrorPage(request, response, null, HttpStatus.NOT_FOUND);
}
private ModelAndView buildModelAndViewErrorPage(HttpServletRequest request, HttpServletResponse response, Exception ex, HttpStatus httpStatus) {
response.setStatus(httpStatus.value());
ModelAndView mav = new ModelAndView("error.html");
if (ex != null) {
mav.addObject("title", ex);
}
mav.addObject("content", request.getRequestURL());
return mav;
}
}
对于 REST 控制器,我建议使用 Zalando Problem Spring Web
。
https://github.com/zalando/problem-spring-web
如果Spring Boot 旨在嵌入一些自动配置,那么这个库在异常处理方面做的更多。您只需要添加依赖项:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
<version>LATEST</version>
</dependency>
然后为您的异常定义一个或多个建议特征(或使用默认提供的特征)
public interface NotAcceptableAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMediaTypeNotAcceptable(
final HttpMediaTypeNotAcceptableException exception,
final NativeWebRequest request) {
return Responses.create(Status.NOT_ACCEPTABLE, exception, request);
}
}
那么你可以定义异常处理的controller advice为:
@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {
}
添加了 Spring Boot 1.4+ 新酷 类 以简化异常处理,这有助于删除样板代码。
为异常处理提供了一个新的@RestControllerAdvice
,它是@ControllerAdvice
和@ResponseBody
的组合。使用这个新注释时,您可以删除 @ExceptionHandler
方法上的 @ResponseBody
。
即
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(value = { Exception.class })
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiErrorResponse unknownException(Exception ex, WebRequest req) {
return new ApiErrorResponse(...);
}
}
为了处理 404 错误,添加 @EnableWebMvc
注释和以下 application.properties 就足够了:
spring.mvc.throw-exception-if-no-handler-found=true
您可以在此处找到并使用这些资源:
https://github.com/magiccrafter/spring-boot-exception-handling
虽然这是一个较老的问题,但我想分享一下我对此的看法。希望对大家有所帮助。
我目前正在构建一个 REST API,它使用 Spring Boot 1.5.2.RELEASE 和 Spring Framework 4.3.7.RELEASE。我使用 Java 配置方法(与 XML 配置相反)。此外,我的项目使用 @RestControllerAdvice
注释(见下文)的全局异常处理机制。
我的项目与您的要求相同:我希望我的 REST API 到 return 一个 HTTP 404 Not Found
并在对API 客户端尝试向不存在的 URL 发送请求时。在我的例子中,JSON 有效载荷看起来像这样(顺便说一句,这明显不同于 Spring 引导默认值。):
{
"code": 1000,
"message": "No handler found for your request.",
"timestamp": "2017-11-20T02:40:57.628Z"
}
我终于成功了。以下是您需要完成的主要任务:
- 确保
NoHandlerFoundException
在 API 个客户端时被抛出
调用不存在处理程序方法的 URLS(请参阅下面的步骤 1)。
- 创建一个自定义错误 class(在我的例子中是
ApiError
),其中包含应该 return 发送到 API 客户端的所有数据(参见步骤 2 ).
- 创建一个对
NoHandlerFoundException
做出反应的异常处理程序
并且 return 向 API 客户端发送正确的错误消息(参见步骤 3)。
- 为它编写一个测试并确保它有效(参见第 4 步)。
好的,现在进入细节:
第 1 步:配置 application.properties
我必须将以下两个配置设置添加到项目的 application.properties
文件中:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
这确保在客户端尝试访问 URL 的情况下抛出 NoHandlerFoundException
,但不存在能够处理请求的控制器方法。
步骤 2:为 API 错误创建 Class
我做了一个 class 类似于 Eugen Paraschiv 博客上 this article 中建议的那个。此 class 表示 API 错误。这个信息是
发生错误时在 HTTP 响应正文中发送给客户端。
public class ApiError {
private int code;
private String message;
private Instant timestamp;
public ApiError(int code, String message) {
this.code = code;
this.message = message;
this.timestamp = Instant.now();
}
public ApiError(int code, String message, Instant timestamp) {
this.code = code;
this.message = message;
this.timestamp = timestamp;
}
// Getters and setters here...
}
步骤 3:创建/配置全局异常处理程序
我使用以下 class 来处理异常(为简单起见,我删除了导入语句、日志代码和其他一些不相关的代码片段):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiError noHandlerFoundException(
NoHandlerFoundException ex) {
int code = 1000;
String message = "No handler found for your request.";
return new ApiError(code, message);
}
// More exception handlers here ...
}
第 4 步:编写测试
我想确保 API 始终 return 向调用客户端发送正确的错误消息,即使在失败的情况下也是如此。因此,我写了一个这样的测试:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SprintBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("dev")
public class GlobalExceptionHandlerIntegrationTest {
public static final String ISO8601_DATE_REGEX =
"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$";
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser(roles = "DEVICE_SCAN_HOSTS")
public void invalidUrl_returnsHttp404() throws Exception {
RequestBuilder requestBuilder = getGetRequestBuilder("/does-not-exist");
mockMvc.perform(requestBuilder)
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code", is(1000)))
.andExpect(jsonPath("$.message", is("No handler found for your request.")))
.andExpect(jsonPath("$.timestamp", RegexMatcher.matchesRegex(ISO8601_DATE_REGEX)));
}
private RequestBuilder getGetRequestBuilder(String url) {
return MockMvcRequestBuilders
.get(url)
.accept(MediaType.APPLICATION_JSON);
}
@ActiveProfiles("dev")
注解可以省略。我只在使用不同的配置文件时使用它。 RegexMatcher
是自定义 Hamcrest matcher I use to better handle timestamp fields. Here's the code (I found it here):
public class RegexMatcher extends TypeSafeMatcher<String> {
private final String regex;
public RegexMatcher(final String regex) {
this.regex = regex;
}
@Override
public void describeTo(final Description description) {
description.appendText("matches regular expression=`" + regex + "`");
}
@Override
public boolean matchesSafely(final String string) {
return string.matches(regex);
}
// Matcher method you can call on this matcher class
public static RegexMatcher matchesRegex(final String string) {
return new RegexMatcher(regex);
}
}
我的一些补充说明:
- 在 Whosebug 上的许多其他帖子中,人们建议设置
@EnableWebMvc
注释。这对我来说不是必需的。
- 这种方法适用于 MockMvc(参见上面的测试)。
@RestControllerAdvice 是Spring Framework 4.3 的新功能,通过横切关注点解决方案来处理RestfulApi 的异常:
package com.khan.vaquar.exception;
import javax.servlet.http.HttpServletRequest;
import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.khan.vaquar.domain.ErrorResponse;
/**
* Handles exceptions raised through requests to spring controllers.
**/
@RestControllerAdvice
public class RestExceptionHandler {
private static final String TOKEN_ID = "tokenId";
private static final Logger log = LoggerFactory.getLogger(RestExceptionHandler.class);
/**
* Handles InstructionExceptions from the rest controller.
*
* @param e IntrusionException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IntrusionException.class)
public ErrorResponse handleIntrusionException(HttpServletRequest request, IntrusionException e) {
log.warn(e.getLogMessage(), e);
return this.handleValidationException(request, new ValidationException(e.getUserMessage(), e.getLogMessage()));
}
/**
* Handles ValidationExceptions from the rest controller.
*
* @param e ValidationException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ValidationException.class)
public ErrorResponse handleValidationException(HttpServletRequest request, ValidationException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
if (e.getUserMessage().contains("Token ID")) {
tokenId = "<OMITTED>";
}
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getUserMessage());
}
/**
* Handles JsonProcessingExceptions from the rest controller.
*
* @param e JsonProcessingException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = JsonProcessingException.class)
public ErrorResponse handleJsonProcessingException(HttpServletRequest request, JsonProcessingException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getOriginalMessage());
}
/**
* Handles IllegalArgumentExceptions from the rest controller.
*
* @param e IllegalArgumentException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = UnsupportedOperationException.class)
public ErrorResponse handleUnsupportedOperationException(HttpServletRequest request, UnsupportedOperationException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
/**
* Handles MissingServletRequestParameterExceptions from the rest controller.
*
* @param e MissingServletRequestParameterException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public ErrorResponse handleMissingServletRequestParameterException( HttpServletRequest request,
MissingServletRequestParameterException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
/**
* Handles NoHandlerFoundExceptions from the rest controller.
*
* @param e NoHandlerFoundException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(value = NoHandlerFoundException.class)
public ErrorResponse handleNoHandlerFoundException(HttpServletRequest request, NoHandlerFoundException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.NOT_FOUND.value(),
e.getClass().getSimpleName(),
"The resource " + e.getRequestURL() + " is unavailable");
}
/**
* Handles all remaining exceptions from the rest controller.
*
* This acts as a catch-all for any exceptions not handled by previous exception handlers.
*
* @param e Exception
* @return error response POJO
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(value = Exception.class)
public ErrorResponse handleException(HttpServletRequest request, Exception e) {
String tokenId = request.getParameter(TOKEN_ID);
log.error(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.INTERNAL_SERVER_ERROR.value(),
e.getClass().getSimpleName(),
"An internal error occurred");
}
}
想要根据http状态码响应的人,可以使用ErrorController
方式:
@Controller
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ServerProperties serverProperties) {
super(new DefaultErrorAttributes(), serverProperties.getError());
}
@Override
public ResponseEntity error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)){
return ResponseEntity.status(status).body(ResponseBean.SERVER_ERROR);
}else if (status.equals(HttpStatus.BAD_REQUEST)){
return ResponseEntity.status(status).body(ResponseBean.BAD_REQUEST);
}
return super.error(request);
}
}
这里的ResponseBean
是我自定义的响应pojo。
我正在尝试建立一个大型 REST 服务服务器。我们正在使用 Spring Boot 1.2.1 Spring 4.1.5 和 Java 8。我们的控制器正在实现 @RestController 和标准的 @RequestMapping 注释。
我的问题是 Spring Boot 设置了控制器异常的默认重定向到 /error
。来自文档:
Spring Boot provides an /error mapping by default that handles all errors in a sensible way, and it is registered as a ‘global’ error page in the servlet container.
多年来使用 Node.js 编写 REST 应用程序,这对我来说是不明智的。服务端点生成的任何异常都应该在响应中 return。我不明白为什么你会发送一个重定向到最有可能是 Angular 或 JQuery SPA 消费者的地方,他们只是在寻找答案而不能或不会采取任何行动重定向。
我想做的是设置一个全局错误处理程序,它可以接受任何异常——有目的地从请求映射方法中抛出或由 Spring 自动生成(如果没有找到处理程序方法则为 404请求路径签名)和 return 标准格式的错误响应(400、500、503、404)到客户端,没有任何 MVC 重定向。具体来说,我们将获取错误,使用 UUID 将其记录到 NoSQL,然后 return 向客户端发送正确的 HTTP 错误代码以及 JSON 正文中日志条目的 UUID。
文档对如何执行此操作含糊不清。在我看来,您必须以某种方式创建自己的 ErrorController implementation or use ControllerAdvice,但我看到的所有示例仍然包括将响应转发到某种错误映射,这无济于事。其他示例建议您必须列出要处理的每个异常类型,而不是仅列出 "Throwable" 并获取所有内容。
任何人都可以告诉我我错过了什么,或者在不建议 Node.js 更容易处理的链条的情况下为我指明正确的方向吗?
我认为ResponseEntityExceptionHandler
符合您的要求。 HTTP 400 的示例代码片段:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class,
HttpRequestMethodNotSupportedException.class})
public ResponseEntity<Object> badRequest(HttpServletRequest req, Exception exception) {
// ...
}
}
你可以查看这个post
默认情况下 Spring 引导给出 json 错误详细信息。
curl -v localhost:8080/greet | json_pp
[...]
< HTTP/1.1 400 Bad Request
[...]
{
"timestamp" : 1413313361387,
"exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
"status" : 400,
"error" : "Bad Request",
"path" : "/greet",
"message" : "Required String parameter 'name' is not present"
}
它也适用于所有类型的请求映射错误。查看这篇文章 http://www.jayway.com/2014/10/19/spring-boot-error-responses/
如果你想创建日志到NoSQL。您可以在记录它的地方创建 @ControllerAdvice,然后重新抛出异常。 文档中有示例 https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
新答案(2016-04-20)
使用 Spring 引导 1.3。1.RELEASE
新步骤 1 - 将以下属性添加到 application.properties:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Much easier than modifying the existing DispatcherServlet instance (as below)! - JO'
如果使用完整的 RESTful 应用程序,禁用静态资源的自动映射非常重要,因为如果您使用 Spring Boot 的默认配置来处理静态资源,那么资源处理程序将处理请求(它最后排序并映射到 /** 这意味着它会拾取应用程序中任何其他处理程序尚未处理的任何请求)因此调度程序 servlet 没有机会抛出异常。
新答案(2015-12-04)
使用 Spring 启动 1.2.7.RELEASE
新步骤 1 - 我发现设置 "throExceptionIfNoHandlerFound" 标志的侵入性小得多的方法。在您的应用程序初始化 class:
中用此替换下面的 DispatcherServlet 替换代码(第 1 步)@ComponentScan()
@EnableAutoConfiguration
public class MyApplication extends SpringBootServletInitializer {
private static Logger LOG = LoggerFactory.getLogger(MyApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
在这种情况下,我们在现有的 DispatcherServlet 上设置标志,它保留 Spring 引导框架的任何自动配置。
我发现的另一件事 - @EnableWebMvc 注释对 Spring Boot.是的,该注释启用了诸如能够捕获所有控制器异常之类的功能,如下所述,但它也扼杀了 Spring Boot 通常会提供的很多有用的自动配置。使用 Spring Boot.
时要格外小心地使用该注释原答案:
经过大量研究并跟进此处发布的解决方案(感谢您的帮助!)以及大量跟踪 Spring 代码的运行时,我终于找到了一个可以处理所有问题的配置异常(不是错误,而是继续阅读)包括 404。
步骤 1 - 告诉 SpringBoot 在 "handler not found" 情况下停止使用 MVC。我们希望 Spring 抛出异常,而不是向客户端返回一个重定向到“/error”的视图。为此,您需要在其中一项配置中添加一个条目 classes:
// NEW CODE ABOVE REPLACES THIS! (2015-12-04)
@Configuration
public class MyAppConfig {
@Bean // Magic entry
public DispatcherServlet dispatcherServlet() {
DispatcherServlet ds = new DispatcherServlet();
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
}
这样做的缺点是它取代了默认的调度程序 servlet。这对我们来说还不是问题,没有出现副作用或执行问题。如果您出于其他原因要对调度程序 servlet 执行任何其他操作,那么这里就是执行它们的地方。
第 2 步 - 现在 spring 引导将在未找到处理程序时抛出异常,该异常可以在统一的异常处理程序中与任何其他异常一起处理:
@EnableWebMvc
@ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Throwable.class)
@ResponseBody
ResponseEntity<Object> handleControllerException(HttpServletRequest req, Throwable ex) {
ErrorResponse errorResponse = new ErrorResponse(ex);
if(ex instanceof ServiceException) {
errorResponse.setDetails(((ServiceException)ex).getDetails());
}
if(ex instanceof ServiceHttpException) {
return new ResponseEntity<Object>(errorResponse,((ServiceHttpException)ex).getStatus());
} else {
return new ResponseEntity<Object>(errorResponse,HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String,String> responseBody = new HashMap<>();
responseBody.put("path",request.getContextPath());
responseBody.put("message","The URL you have reached is not in service at this time (404).");
return new ResponseEntity<Object>(responseBody,HttpStatus.NOT_FOUND);
}
...
}
请记住,我认为“@EnableWebMvc”注释在这里很重要。似乎 none 没有它也能工作。就是这样 - 您的 Spring 启动应用程序现在将在上述处理程序 class 中捕获所有异常,包括 404,您可以随意处理它们。
最后一点 - 似乎没有办法让它捕获抛出的错误。我有一个古怪的想法,即使用方面来捕获错误并将它们转化为上述代码随后可以处理的异常,但我还没有时间实际尝试实现它。希望这对某人有所帮助。
任何 comments/corrections/enhancements 将不胜感激。
解决方案
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
和
@EnableWebMvc
@ControllerAdvice
在 Spring Boot 1.3.1 上为我工作,但在 1.2.7
这段代码怎么样?我使用回退请求映射来捕获 404 错误。
@Controller
@ControllerAdvice
public class ExceptionHandlerController {
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
//If exception has a ResponseStatus annotation then use its response code
ResponseStatus responseStatusAnnotation = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
return buildModelAndViewErrorPage(request, response, ex, responseStatusAnnotation != null ? responseStatusAnnotation.value() : HttpStatus.INTERNAL_SERVER_ERROR);
}
@RequestMapping("*")
public ModelAndView fallbackHandler(HttpServletRequest request, HttpServletResponse response) throws Exception {
return buildModelAndViewErrorPage(request, response, null, HttpStatus.NOT_FOUND);
}
private ModelAndView buildModelAndViewErrorPage(HttpServletRequest request, HttpServletResponse response, Exception ex, HttpStatus httpStatus) {
response.setStatus(httpStatus.value());
ModelAndView mav = new ModelAndView("error.html");
if (ex != null) {
mav.addObject("title", ex);
}
mav.addObject("content", request.getRequestURL());
return mav;
}
}
对于 REST 控制器,我建议使用 Zalando Problem Spring Web
。
https://github.com/zalando/problem-spring-web
如果Spring Boot 旨在嵌入一些自动配置,那么这个库在异常处理方面做的更多。您只需要添加依赖项:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
<version>LATEST</version>
</dependency>
然后为您的异常定义一个或多个建议特征(或使用默认提供的特征)
public interface NotAcceptableAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMediaTypeNotAcceptable(
final HttpMediaTypeNotAcceptableException exception,
final NativeWebRequest request) {
return Responses.create(Status.NOT_ACCEPTABLE, exception, request);
}
}
那么你可以定义异常处理的controller advice为:
@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {
}
添加了 Spring Boot 1.4+ 新酷 类 以简化异常处理,这有助于删除样板代码。
为异常处理提供了一个新的@RestControllerAdvice
,它是@ControllerAdvice
和@ResponseBody
的组合。使用这个新注释时,您可以删除 @ExceptionHandler
方法上的 @ResponseBody
。
即
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(value = { Exception.class })
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiErrorResponse unknownException(Exception ex, WebRequest req) {
return new ApiErrorResponse(...);
}
}
为了处理 404 错误,添加 @EnableWebMvc
注释和以下 application.properties 就足够了:
spring.mvc.throw-exception-if-no-handler-found=true
您可以在此处找到并使用这些资源:
https://github.com/magiccrafter/spring-boot-exception-handling
虽然这是一个较老的问题,但我想分享一下我对此的看法。希望对大家有所帮助。
我目前正在构建一个 REST API,它使用 Spring Boot 1.5.2.RELEASE 和 Spring Framework 4.3.7.RELEASE。我使用 Java 配置方法(与 XML 配置相反)。此外,我的项目使用 @RestControllerAdvice
注释(见下文)的全局异常处理机制。
我的项目与您的要求相同:我希望我的 REST API 到 return 一个 HTTP 404 Not Found
并在对API 客户端尝试向不存在的 URL 发送请求时。在我的例子中,JSON 有效载荷看起来像这样(顺便说一句,这明显不同于 Spring 引导默认值。):
{
"code": 1000,
"message": "No handler found for your request.",
"timestamp": "2017-11-20T02:40:57.628Z"
}
我终于成功了。以下是您需要完成的主要任务:
- 确保
NoHandlerFoundException
在 API 个客户端时被抛出 调用不存在处理程序方法的 URLS(请参阅下面的步骤 1)。 - 创建一个自定义错误 class(在我的例子中是
ApiError
),其中包含应该 return 发送到 API 客户端的所有数据(参见步骤 2 ). - 创建一个对
NoHandlerFoundException
做出反应的异常处理程序 并且 return 向 API 客户端发送正确的错误消息(参见步骤 3)。 - 为它编写一个测试并确保它有效(参见第 4 步)。
好的,现在进入细节:
第 1 步:配置 application.properties
我必须将以下两个配置设置添加到项目的 application.properties
文件中:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
这确保在客户端尝试访问 URL 的情况下抛出 NoHandlerFoundException
,但不存在能够处理请求的控制器方法。
步骤 2:为 API 错误创建 Class
我做了一个 class 类似于 Eugen Paraschiv 博客上 this article 中建议的那个。此 class 表示 API 错误。这个信息是 发生错误时在 HTTP 响应正文中发送给客户端。
public class ApiError {
private int code;
private String message;
private Instant timestamp;
public ApiError(int code, String message) {
this.code = code;
this.message = message;
this.timestamp = Instant.now();
}
public ApiError(int code, String message, Instant timestamp) {
this.code = code;
this.message = message;
this.timestamp = timestamp;
}
// Getters and setters here...
}
步骤 3:创建/配置全局异常处理程序
我使用以下 class 来处理异常(为简单起见,我删除了导入语句、日志代码和其他一些不相关的代码片段):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiError noHandlerFoundException(
NoHandlerFoundException ex) {
int code = 1000;
String message = "No handler found for your request.";
return new ApiError(code, message);
}
// More exception handlers here ...
}
第 4 步:编写测试
我想确保 API 始终 return 向调用客户端发送正确的错误消息,即使在失败的情况下也是如此。因此,我写了一个这样的测试:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SprintBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("dev")
public class GlobalExceptionHandlerIntegrationTest {
public static final String ISO8601_DATE_REGEX =
"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$";
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser(roles = "DEVICE_SCAN_HOSTS")
public void invalidUrl_returnsHttp404() throws Exception {
RequestBuilder requestBuilder = getGetRequestBuilder("/does-not-exist");
mockMvc.perform(requestBuilder)
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code", is(1000)))
.andExpect(jsonPath("$.message", is("No handler found for your request.")))
.andExpect(jsonPath("$.timestamp", RegexMatcher.matchesRegex(ISO8601_DATE_REGEX)));
}
private RequestBuilder getGetRequestBuilder(String url) {
return MockMvcRequestBuilders
.get(url)
.accept(MediaType.APPLICATION_JSON);
}
@ActiveProfiles("dev")
注解可以省略。我只在使用不同的配置文件时使用它。 RegexMatcher
是自定义 Hamcrest matcher I use to better handle timestamp fields. Here's the code (I found it here):
public class RegexMatcher extends TypeSafeMatcher<String> {
private final String regex;
public RegexMatcher(final String regex) {
this.regex = regex;
}
@Override
public void describeTo(final Description description) {
description.appendText("matches regular expression=`" + regex + "`");
}
@Override
public boolean matchesSafely(final String string) {
return string.matches(regex);
}
// Matcher method you can call on this matcher class
public static RegexMatcher matchesRegex(final String string) {
return new RegexMatcher(regex);
}
}
我的一些补充说明:
- 在 Whosebug 上的许多其他帖子中,人们建议设置
@EnableWebMvc
注释。这对我来说不是必需的。 - 这种方法适用于 MockMvc(参见上面的测试)。
@RestControllerAdvice 是Spring Framework 4.3 的新功能,通过横切关注点解决方案来处理RestfulApi 的异常:
package com.khan.vaquar.exception;
import javax.servlet.http.HttpServletRequest;
import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.khan.vaquar.domain.ErrorResponse;
/**
* Handles exceptions raised through requests to spring controllers.
**/
@RestControllerAdvice
public class RestExceptionHandler {
private static final String TOKEN_ID = "tokenId";
private static final Logger log = LoggerFactory.getLogger(RestExceptionHandler.class);
/**
* Handles InstructionExceptions from the rest controller.
*
* @param e IntrusionException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IntrusionException.class)
public ErrorResponse handleIntrusionException(HttpServletRequest request, IntrusionException e) {
log.warn(e.getLogMessage(), e);
return this.handleValidationException(request, new ValidationException(e.getUserMessage(), e.getLogMessage()));
}
/**
* Handles ValidationExceptions from the rest controller.
*
* @param e ValidationException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ValidationException.class)
public ErrorResponse handleValidationException(HttpServletRequest request, ValidationException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
if (e.getUserMessage().contains("Token ID")) {
tokenId = "<OMITTED>";
}
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getUserMessage());
}
/**
* Handles JsonProcessingExceptions from the rest controller.
*
* @param e JsonProcessingException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = JsonProcessingException.class)
public ErrorResponse handleJsonProcessingException(HttpServletRequest request, JsonProcessingException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getOriginalMessage());
}
/**
* Handles IllegalArgumentExceptions from the rest controller.
*
* @param e IllegalArgumentException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = UnsupportedOperationException.class)
public ErrorResponse handleUnsupportedOperationException(HttpServletRequest request, UnsupportedOperationException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
/**
* Handles MissingServletRequestParameterExceptions from the rest controller.
*
* @param e MissingServletRequestParameterException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public ErrorResponse handleMissingServletRequestParameterException( HttpServletRequest request,
MissingServletRequestParameterException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
/**
* Handles NoHandlerFoundExceptions from the rest controller.
*
* @param e NoHandlerFoundException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(value = NoHandlerFoundException.class)
public ErrorResponse handleNoHandlerFoundException(HttpServletRequest request, NoHandlerFoundException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.NOT_FOUND.value(),
e.getClass().getSimpleName(),
"The resource " + e.getRequestURL() + " is unavailable");
}
/**
* Handles all remaining exceptions from the rest controller.
*
* This acts as a catch-all for any exceptions not handled by previous exception handlers.
*
* @param e Exception
* @return error response POJO
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(value = Exception.class)
public ErrorResponse handleException(HttpServletRequest request, Exception e) {
String tokenId = request.getParameter(TOKEN_ID);
log.error(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.INTERNAL_SERVER_ERROR.value(),
e.getClass().getSimpleName(),
"An internal error occurred");
}
}
想要根据http状态码响应的人,可以使用ErrorController
方式:
@Controller
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ServerProperties serverProperties) {
super(new DefaultErrorAttributes(), serverProperties.getError());
}
@Override
public ResponseEntity error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)){
return ResponseEntity.status(status).body(ResponseBean.SERVER_ERROR);
}else if (status.equals(HttpStatus.BAD_REQUEST)){
return ResponseEntity.status(status).body(ResponseBean.BAD_REQUEST);
}
return super.error(request);
}
}
这里的ResponseBean
是我自定义的响应pojo。