Java Spring Boot REST 服务异常处理
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28902374/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot REST service exception handling
提问by ogradyjd
I am trying to set up a large-scale REST services server. We're using Spring Boot 1.2.1 Spring 4.1.5, and Java 8. Our controllers are implementing @RestController and the standard @RequestMapping annotations.
我正在尝试设置大型 REST 服务服务器。我们使用的是 Spring Boot 1.2.1、Spring 4.1.5 和 Java 8。我们的控制器正在实现 @RestController 和标准的 @RequestMapping 注释。
My problem is that Spring Boot sets up a default redirect for controller exceptions to /error
. From the docs:
我的问题是 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.
Spring Boot 默认提供了一个 /error 映射,以合理的方式处理所有错误,并在 servlet 容器中注册为“全局”错误页面。
Coming from years writing REST applications with Node.js, this is, to me, anything but sensible. Any exception a service endpoint generates should return in the response. I can't understand why you'd send a redirect to what is most likely an Angular or JQuery SPA consumer which is only looking for an answer and can't or won't take any action on a redirect.
多年来使用 Node.js 编写 REST 应用程序,对我来说,这绝非明智之举。服务端点生成的任何异常都应在响应中返回。我不明白为什么您要向最有可能是 Angular 或 JQuery SPA 消费者的用户发送重定向,该消费者只是在寻找答案,而不能或不会对重定向采取任何行动。
What I want to do is set up a global error handler that can take any exception - either purposefully thrown from a request mapping method or auto-generated by Spring (404 if no handler method is found for the request path signature), and return a standard formatted error response (400, 500, 503, 404) to the client without any MVC redirects. Specifically, we are going to take the error, log it to NoSQL with a UUID, then return to the client the right HTTP error code with the UUID of the log entry in the JSON body.
我想要做的是设置一个可以接受任何异常的全局错误处理程序 - 有目的地从请求映射方法抛出或由 Spring 自动生成(如果没有找到请求路径签名的处理程序方法,则为 404),并返回一个标准格式的错误响应(400、500、503、404)到客户端,没有任何 MVC 重定向。具体来说,我们将获取错误,使用 UUID 将其记录到 NoSQL,然后将正确的 HTTP 错误代码与 JSON 正文中日志条目的 UUID 返回给客户端。
The docs have been vague on how to do this. It seems to me that you have to either create your own ErrorControllerimplementation or use ControllerAdvicein some fashion, but all the examples I've seen still include forwarding the response to some kind of error mapping, which doesn't help. Other examples suggest that you'd have to list every Exception type you want to handle instead of just listing "Throwable" and getting everything.
文档对如何执行此操作含糊不清。在我看来,您必须要么创建自己的ErrorController实现,要么以某种方式使用ControllerAdvice,但我所看到的所有示例仍然包括将响应转发到某种错误映射,这无济于事。其他示例表明您必须列出要处理的每个异常类型,而不仅仅是列出“Throwable”并获取所有内容。
Can anyone tell me what I missed, or point me in the right direction on how to do this without suggesting up the chain that Node.js would be easier to deal with?
谁能告诉我我错过了什么,或者为我指出如何做到这一点的正确方向,而不建议使用 Node.js 更容易处理的链?
采纳答案by ogradyjd
New answer (2016-04-20)
新答案 (2016-04-20)
Using Spring Boot 1.3.1.RELEASE
使用 Spring Boot 1.3.1.RELEASE
New Step 1 -It is easy and less intrusive to add the following properties to the application.properties:
新的第 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'
比修改现有的 DispatcherServlet 实例(如下)容易得多!- 乔'
If working with a full RESTful Application, it is very important to disable the automatic mapping of static resources since if you are using Spring Boot's default configuration for handling static resources then the resource handler will be handling the request (it's ordered last and mapped to /** which means that it picks up any requests that haven't been handled by any other handler in the application) so the dispatcher servlet doesn't get a chance to throw an exception.
如果使用完整的 RESTful 应用程序,禁用静态资源的自动映射非常重要,因为如果您使用 Spring Boot 的默认配置来处理静态资源,那么资源处理程序将处理请求(它最后排序并映射到 / ** 这意味着它会接收应用程序中任何其他处理程序尚未处理的任何请求),因此调度程序 servlet 没有机会抛出异常。
New Answer (2015-12-04)
新答案 (2015-12-04)
Using Spring Boot 1.2.7.RELEASE
使用 Spring Boot 1.2.7.RELEASE
New Step 1 -I found a much less intrusive way of setting the "throExceptionIfNoHandlerFound" flag. Replace the DispatcherServlet replacement code below (Step 1) with this in your application initialization class:
新的第 1 步 -我发现了一种设置“throExceptionIfNoHandlerFound”标志的侵入性小得多的方法。在您的应用程序初始化类中将下面的 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);
}
In this case, we're setting the flag on the existing DispatcherServlet, which preserves any auto-configuration by the Spring Boot framework.
在这种情况下,我们在现有的 DispatcherServlet 上设置标志,它保留 Spring Boot 框架的任何自动配置。
One more thing I've found - the @EnableWebMvc annotation is deadly to Spring Boot. Yes, that annotation enables things like being able to catch all the controller exceptions as described below, but it also kills a LOT of the helpful auto-configuration that Spring Boot would normally provide. Use that annotation with extreme caution when you use Spring Boot.
我发现的另一件事 - @EnableWebMvc 注释对 Spring Boot 是致命的。是的,该注解可以像下面描述的那样捕获所有控制器异常,但它也杀死了很多 Spring Boot 通常会提供的有用的自动配置。当您使用 Spring Boot 时,请格外小心地使用该注释。
Original Answer:
原答案:
After a lot more research and following up on the solutions posted here (thanks for the help!) and no small amount of runtime tracing into the Spring code, I finally found a configuration that will handle all Exceptions (not Errors, but read on) including 404s.
经过大量研究并跟进此处发布的解决方案(感谢您的帮助!)以及对 Spring 代码进行大量运行时跟踪后,我终于找到了一个可以处理所有异常的配置(不是错误,而是继续阅读)包括 404。
Step 1 -tell SpringBoot to stop using MVC for "handler not found" situations. We want Spring to throw an exception instead of returning to the client a view redirect to "/error". To do this, you need to have an entry in one of your configuration classes:
第 1 步 -告诉 SpringBoot 在“找不到处理程序”的情况下停止使用 MVC。我们希望 Spring 抛出异常,而不是将视图重定向到“/error”返回给客户端。为此,您需要在配置类之一中有一个条目:
// 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;
}
}
The downside of this is that it replaces the default dispatcher servlet. This hasn't been a problem for us yet, with no side effects or execution problems showing up. If you're going to do anything else with the dispatcher servlet for other reasons, this is the place to do them.
这样做的缺点是它替换了默认的调度程序 servlet。这对我们来说还不是问题,没有出现副作用或执行问题。如果您出于其他原因要对调度程序 servlet 执行任何其他操作,则可以在此处执行这些操作。
Step 2 -Now that spring boot will throw an exception when no handler is found, that exception can be handled with any others in a unified exception handler:
第 2 步 -现在 Spring Boot 将在未找到处理程序时抛出异常,该异常可以在统一异常处理程序中与任何其他异常一起处理:
@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);
}
...
}
Keep in mind that I think the "@EnableWebMvc" annotation is significant here. It seems that none of this works without it. And that's it - your Spring boot app will now catch all exceptions, including 404s, in the above handler class and you may do with them as you please.
请记住,我认为“@EnableWebMvc”注释在这里很重要。没有它,这些似乎都不起作用。就是这样 - 您的 Spring Boot 应用程序现在将捕获上述处理程序类中的所有异常,包括 404,您可以随意处理它们。
One last point - there doesn't seem to be a way to get this to catch thrown Errors. I have a wacky idea of using aspects to catch errors and turn them into Exceptions that the above code can then deal with, but I have not yet had time to actually try implementing that. Hope this helps someone.
最后一点 - 似乎没有办法让它捕获抛出的错误。我有一个古怪的想法,即使用方面来捕获错误并将它们转换为上面代码可以处理的异常,但我还没有时间实际尝试实现它。希望这可以帮助某人。
Any comments/corrections/enhancements will be appreciated.
任何评论/更正/改进将不胜感激。
回答by Efe Kahraman
I think ResponseEntityExceptionHandler
meets your requirements. A sample piece of code for HTTP 400:
我认为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) {
// ...
}
}
You can check this post
你可以看看这个帖子
回答by PaintedRed
By default Spring Boot gives json with error details.
默认情况下,Spring Boot 提供带有错误详细信息的 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"
}
It also works for all kind of request mapping errors. Check this article http://www.jayway.com/2014/10/19/spring-boot-error-responses/
它也适用于所有类型的请求映射错误。检查这篇文章 http://www.jayway.com/2014/10/19/spring-boot-error-responses/
If you want to create log it to NoSQL. You can create @ControllerAdvice where you would log it and then re-throw the exception. There is example in documentation https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
如果要创建将其记录到 NoSQL。您可以创建@ControllerAdvice,您可以在其中记录它,然后重新抛出异常。文档中有示例 https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
回答by Dennis R
Solution with
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
and
@EnableWebMvc
@ControllerAdvice
worked for me with Spring Boot 1.3.1, while was not working on 1.2.7
使用 Spring Boot 1.3.1 的解决方案
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
并
@EnableWebMvc
@ControllerAdvice
为我工作,但不适用于 1.2.7
回答by Ludovic Martin
What about this code ? I use a fallback request mapping to catch 404 errors.
这段代码怎么样?我使用回退请求映射来捕获 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;
}
}
回答by JeanValjean
For REST controllers, I would recommend to use Zalando Problem Spring Web
.
对于 REST 控制器,我建议使用Zalando Problem Spring Web
.
https://github.com/zalando/problem-spring-web
https://github.com/zalando/problem-spring-web
If Spring Boot aims to embed some auto-configuration, this library does more for exception handling. You just need to add the dependency:
如果 Spring Boot 旨在嵌入一些自动配置,那么这个库会做更多的异常处理。您只需要添加依赖项:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
<version>LATEST</version>
</dependency>
And then define one or more advice traits for your exceptions (or use those provided by default)
然后为您的异常定义一个或多个建议特征(或使用默认提供的特征)
public interface NotAcceptableAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMediaTypeNotAcceptable(
final HttpMediaTypeNotAcceptableException exception,
final NativeWebRequest request) {
return Responses.create(Status.NOT_ACCEPTABLE, exception, request);
}
}
Then you can defined the controller advice for exception handling as:
然后,您可以将异常处理的控制器建议定义为:
@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {
}
回答by magiccrafter
With Spring Boot 1.4+ new cool classes for easier exception handling were added that helps in removing the boilerplate code.
在 Spring Boot 1.4+ 中添加了新的很酷的类,可以更轻松地处理异常,这有助于删除样板代码。
A new @RestControllerAdvice
is provided for exception handling, it is combination of @ControllerAdvice
and @ResponseBody
. You can remove the @ResponseBody
on the @ExceptionHandler
method when use this new annotation.
@RestControllerAdvice
提供了一个新的异常处理,它是@ControllerAdvice
和 的组合@ResponseBody
。使用此新注释时,您可以删除方法@ResponseBody
上的@ExceptionHandler
。
i.e.
IE
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(value = { Exception.class })
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiErrorResponse unknownException(Exception ex, WebRequest req) {
return new ApiErrorResponse(...);
}
}
For handling 404 errors adding @EnableWebMvc
annotation and the following to application.properties was enough:spring.mvc.throw-exception-if-no-handler-found=true
为了处理 404 错误@EnableWebMvc
,向 application.properties添加注释和以下内容就足够了:spring.mvc.throw-exception-if-no-handler-found=true
You can find and play with the sources here:
https://github.com/magiccrafter/spring-boot-exception-handling
您可以在此处找到并使用源代码:https:
//github.com/magiccrafter/spring-boot-exception-handling
回答by André Gasser
Although this is an older question, I would like to share my thoughts on this. I hope, that it will be helpful to some of you.
虽然这是一个较老的问题,但我想分享我对此的看法。我希望,它会对你们中的一些人有所帮助。
I am currently building a REST API which makes use of Spring Boot 1.5.2.RELEASE with Spring Framework 4.3.7.RELEASE. I use the Java Config approach (as opposed to XML configuration). Also, my project uses a global exception handling mechanism using the @RestControllerAdvice
annotation (see later below).
我目前正在构建一个 REST API,它使用 Spring Boot 1.5.2.RELEASE 和 Spring Framework 4.3.7.RELEASE。我使用 Java Config 方法(而不是 XML 配置)。此外,我的项目使用使用@RestControllerAdvice
注释的全局异常处理机制(见下文)。
My project has the same requirements as yours: I want my REST API to return a HTTP 404 Not Found
with an accompanying JSON payload in the HTTP response to the API client when it tries to send a request to an URL which does not exist. In my case, the JSON payload looks like this (which clearly differs from the Spring Boot default, btw.):
我的项目与您的要求相同:我希望我的 REST APIHTTP 404 Not Found
在 API 客户端尝试向不存在的 URL 发送请求时,在 HTTP 响应中向 API 客户端返回一个带有随附 JSON 有效负载的 。就我而言,JSON 有效负载如下所示(这显然不同于 Spring Boot 默认设置,顺便说一句。):
{
"code": 1000,
"message": "No handler found for your request.",
"timestamp": "2017-11-20T02:40:57.628Z"
}
I finally made it work. Here are the main tasks you need to do in brief:
我终于让它工作了。以下是您需要完成的主要任务:
- Make sure that the
NoHandlerFoundException
is thrown if API clients call URLS for which no handler method exists (see Step 1 below). - Create a custom error class (in my case
ApiError
) which contains all the data that should be returned to the API client (see step 2). - Create an exception handler which reacts on the
NoHandlerFoundException
and returns a proper error message to the API client (see step 3). - Write a test for it and make sure, it works (see step 4).
NoHandlerFoundException
如果 API 客户端调用不存在处理程序方法的 URL,请确保抛出 。(请参阅下面的步骤 1)。- 创建一个自定义错误类(在我的例子中
ApiError
),其中包含应该返回给 API 客户端的所有数据(参见步骤 2)。 - 创建一个异常处理程序,该处理程序
NoHandlerFoundException
对 API 客户端做出反应并向 API 客户端返回正确的错误消息(请参阅步骤 3)。 - 为它编写一个测试并确保它有效(参见步骤 4)。
Ok, now on to the details:
好的,现在进入细节:
Step 1: Configure application.properties
第一步:配置application.properties
I had to add the following two configuration settings to the project's application.properties
file:
我必须将以下两个配置设置添加到项目application.properties
文件中:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
This makes sure, the NoHandlerFoundException
is thrown in cases where a client tries to access an URL for which no controller method exists which would be able to handle the request.
这可以确保NoHandlerFoundException
在客户端尝试访问不存在能够处理请求的控制器方法的 URL 的情况下抛出 。
Step 2: Create a Class for API Errors
步骤 2:为 API 错误创建一个类
I made a class similar to the one suggested in this articleon Eugen Paraschiv's blog. This class represents an API error. This information is sent to the client in the HTTP response body in case of an error.
我创建了一个类似于Eugen Paraschiv 博客上这篇文章中建议的类。此类表示 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...
}
Step 3: Create / Configure a Global Exception Handler
步骤 3:创建/配置全局异常处理程序
I use the following class to handle exceptions (for simplicity, I have removed import statements, logging code and some other, non-relevant pieces of code):
我使用以下类来处理异常(为简单起见,我删除了导入语句、日志代码和其他一些不相关的代码段):
@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 ...
}
Step 4: Write a test
第 4 步:编写测试
I want to make sure, the API always returns the correct error messages to the calling client, even in the case of failure. Thus, I wrote a test like this:
我想确保 API 始终向调用客户端返回正确的错误消息,即使在失败的情况下也是如此。因此,我写了一个这样的测试:
@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);
}
The @ActiveProfiles("dev")
annotation can be left away. I use it only as I work with different profiles. The RegexMatcher
is a custom Hamcrest matcherI use to better handle timestamp fields. Here's the code (I found it here):
该@ActiveProfiles("dev")
注解可以留下了。我只在处理不同的配置文件时使用它。这RegexMatcher
是我用来更好地处理时间戳字段的自定义Hamcrest 匹配器。这是代码(我在这里找到了):
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);
}
}
Some further notes from my side:
我这边的一些进一步说明:
- In many other posts on StackOverflow, people suggested setting the
@EnableWebMvc
annotation. This was not necessary in my case. - This approach works well with MockMvc (see test above).
- 在 StackOverflow 上的许多其他帖子中,人们建议设置
@EnableWebMvc
注释。在我的情况下,这不是必需的。 - 这种方法适用于 MockMvc(见上面的测试)。
回答by vaquar khan
@RestControllerAdvice is a new feature of Spring Framework 4.3 to handle Exception with RestfulApi by a cross-cutting concern solution:
@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.Hymanson.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");
}
}
回答by Lym Zoy
For people that want to response according to http status code, you can use the ErrorController
way:
对于想根据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);
}
}
The ResponseBean
here is my custom pojo for response.
这ResponseBean
是我的自定义 pojo 响应。