Spring Boot REST服务异常处理

199

我正在尝试搭建一个大规模的REST服务服务器。我们使用Spring Boot 1.2.1,Spring 4.1.5和Java 8。我们的控制器实现了@RestController和标准的@RequestMapping注释。

我的问题是Spring Boot为控制器异常设置了默认重定向到/error。根据文档:

Spring Boot提供默认的/error映射,以明智的方式处理所有错误,并在Servlet容器中注册为“全局”错误页面。

对我来说,作为多年使用Node.js编写REST应用程序的开发者,这一点绝不明智。任何服务端点生成的异常都应该返回响应。我无法理解为什么会将重定向发送到最可能是Angular或JQuery SPA消费者的地方,这些消费者只寻求答案,无法或不愿执行重定向。

我想做的是设置一个全局错误处理程序,可以处理任何异常——无论是从请求映射方法有意抛出的还是由Spring自动生成的(如果找不到请求路径签名的处理程序方法,则返回404),并向客户端返回一个标准格式的错误响应(400、500、503、404)而没有任何MVC重定向。具体而言,我们将捕获错误、将其记录到NoSQL中并使用UUID返回正确的HTTP错误代码和JSON主体中的日志条目UUID。

文档对如何做这件事含糊不清。在我看来,您必须创建自己的ErrorController实现或以某种方式使用ControllerAdvice,但我看到的所有示例仍然包括将响应转发到某种错误映射,这并没有帮助解决问题。其他示例表明,您必须列出要处理的每个异常类型,而不是只列出“Throwable”并捕获每一种异常。

有人能告诉我错过了什么,或者指导我如何做到这一点,而不建议使用 Node.js 吗?


8
客户端实际上并没有收到重定向。重定向由Servlet容器(例如Tomcat)在内部处理。 - OrangeDog
1
移除我的异常处理程序上的@ResponseStatus注解是我需要的;请参见https://dev59.com/nZTfa4cB1Zd3GeqPPVr9。 - pmorken
11个回答

155

新答案(2016-04-20)

使用Spring Boot 1.3.1.RELEASE

新步骤1 - 将以下属性添加到application.properties中既简单又不会产生太多干扰:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

如果您正在处理一个完整的RESTful应用程序,那么非常重要的一点是禁用静态资源的自动映射。如果您使用Spring Boot的默认配置来处理静态资源,则资源处理程序将处理该请求(它是最后一个被映射到/**的处理程序,这意味着它会捕获任何其他处理程序未处理的请求),因此调度程序Servlet没有机会抛出异常。比修改现有的DispatcherServlet实例(如下)容易得多!- JO'
新答案(2015-12-04):
使用Spring Boot 1.2.7.RELEASE
新步骤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);
    }

在这种情况下,我们正在对现有的DispatcherServlet设置标记,这将保留Spring Boot框架的任何自动配置。
还有一件事我发现的是 - @EnableWebMvc注解对Spring Boot有致命的影响。是的,该注解使得像下面描述的能够捕获所有控制器异常的事情成为可能,但它也会杀掉Spring Boot通常提供的许多有用的自动配置。在使用Spring Boot时,请非常小心地使用该注解。

原始回答:

经过更多的研究并跟进此处发布的解决方案(感谢帮助!)以及对Spring代码的运行时跟踪,我最终找到了一个配置,可以处理所有异常(不是错误,但请继续阅读),包括404s。

步骤1-告诉SpringBoot停止使用MVC处理“handler not found”情况。我们希望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;
    }
}

这样做的缺点是它会替换默认的调度servlet。目前为止,这对我们来说还没有成为问题,没有出现任何副作用或执行问题。如果你因其它原因要处理调度servlet,那么这就是处理它们的地方。
第二步 - 现在,当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);
    }
    ...
}

请记住,我认为"@EnableWebMvc"注释在这里非常重要。如果没有它,似乎所有这些都不起作用。就这样 - 您的Spring Boot应用程序现在将捕获上述处理程序类中的所有异常,包括404,并且您可以根据需要处理它们。
最后一点 - 似乎没有办法让其捕获抛出的错误。我有一个疯狂的想法,使用切面来捕获错误并将其转换为上述代码可以处理的异常,但我还没有时间尝试实现它。
欢迎任何意见/更正/增强建议。

不需要创建新的调度程序servlet bean,您可以在后处理器中翻转标志:YourClass实现BeanPostProcessor { ... `public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{ if (bean instanceof DispatcherServlet) { // 否则我们会在异常处理程序启动之前得到404 ((DispatcherServlet) bean).setThrowExceptionIfNoHandlerFound(true); } return bean; }public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } - wwadge
1
我有这个问题,但自定义DispatcherServlet对我没有用。在Boot中使用此额外的bean和配置是否需要任何其他魔法? - Ian Gilham
3
@IanGilham 我也无法在Spring Boot 1.2.7中使用它。即使我把它放到@ControllerAdvice类中,我也没有任何@ExceptionHandler方法被调用,尽管如果放在@RestController类中,它们可以正常工作。 @EnableWebMvc已经在@ControllerAdvice@Configuration(我测试了每种组合)类中启用了。有什么想法或者可行的例子吗?// @Andy Wilkinson - FrVaBe
1
阅读此问题和答案的人应该查看 github 上相应的 SpringBoot 问题。 - FrVaBe
1
不确定 @agpt。我有一个内部项目,可以将其升级到1.3.0版本,看看对我的设置有什么影响,并让您知道我发现了什么。 - ogradyjd
显示剩余10条评论

45

在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


7
非常有帮助,谢谢。但是我不明白为什么我们需要在 spring.mvc.throw-exception-if-no-handler-found=true 的情况下使用 @EnableWebMvc 。我的期望是通过 @RestControllerAdvice 处理所有异常,而无需额外的配置。我错过了什么吗? - fiskra

29

我认为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) {
    // ...
  }
}

你可以查看这个文章


8
我之前看过这段代码,实现后,该类确实捕获了控制器请求映射方法引发的异常。但是仍然无法捕获404错误,在ResourceHttpRequestHandler.handleRequest方法中处理,如果使用@EnableWebMvc注解,则在DispatcherServlet.noHandlerFound中处理。我们想要处理任何错误,包括404错误,但最新版本的Spring Boot似乎在如何处理此问题上非常难懂。 - ogradyjd
我以相同的方式编写了处理HttpRequestMethodNotSupportedException的代码,并在多个微服务中插入相同的jar包,由于一些业务需求,我们需要在响应中返回微服务别名。有没有办法可以获取底层微服务名称/控制器名称? 我知道HandlerMethod将提供引发异常的Java方法名称。 但是,在此处,没有任何方法接收请求,因此HandlerMethod将不会被初始化。那么,有没有解决这个问题的方法? - Paramesh Korrakuti
控制器建议是一个不错的方法,但请记住,异常并不是流程的一部分,它们只会在特殊情况下发生! - Jorge Tovar

27
尽管这是一个较旧的问题,但我想分享我的想法。我希望对你们中的一些人有所帮助。
我目前正在构建一个REST API,它使用Spring Boot 1.5.2.RELEASE和Spring Framework 4.3.7.RELEASE。我使用Java Config方法(而不是XML配置)。此外,我的项目使用@RestControllerAdvice注释实现全局异常处理机制(稍后会详细介绍)。
我的项目具有与你的要求相同的要求:当API客户端尝试向不存在的URL发送请求时,我希望我的REST API返回HTTP 404 Not Found,并在HTTP响应中附带JSON有效负载。在我的情况下,JSON有效负载如下所示(顺便说一句,这与Spring Boot默认值明显不同):
{
    "code": 1000,
    "message": "No handler found for your request.",
    "timestamp": "2017-11-20T02:40:57.628Z"
}

我终于让它工作了。以下是你需要简要完成的主要任务:

  • 确保如果API客户端调用没有处理程序方法的URL,抛出NoHandlerFoundException(请参见下面的步骤1)。
  • 创建一个自定义错误类(在我的情况下为ApiError),其中包含应返回给API客户端的所有数据(请参见步骤2)。
  • 创建一个异常处理程序,该处理程序对NoHandlerFoundException进行反应,并向API客户端返回适当的错误消息(请参见步骤3)。
  • 编写测试并确保其正常工作(请参见步骤4)。

好的,现在进入详细信息:

步骤1:配置 application.properties

我不得不将以下两个配置设置添加到项目的application.properties文件中:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

这样做可以确保当客户端试图访问没有控制器方法能够处理请求的URL时,会抛出NoHandlerFoundException异常。
第二步:为API错误创建一个类
我创建了一个类,类似于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...
}

步骤 3:创建 / 配置全局异常处理程序

我使用以下类来处理异常(为简单起见,我已删除了导入语句、日志记录代码和一些其他非相关的代码):

@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 ...
}

步骤四:编写测试

我希望确保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);
    }

@ActiveProfiles("dev")注解可以省略。我只是在使用不同的配置文件时才会用到它。 RegexMatcher 是一个自定义的Hamcrest matcher,我用它来更好地处理时间戳字段。以下是代码(我在这里找到的):

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);
    }
}

我的一些进一步的注释:

  • 在StackOverflow上的许多其他帖子中,人们建议设置@EnableWebMvc注释。但在我的情况下并不需要。
  • 这种方法与MockMvc很好地配合(请参见上面的测试)。

这对我解决了问题。只是补充一下,我缺少了@ RestControllerAdvice注释,所以我添加了它和@ ControllerAdvice注释,以便处理所有内容,这就解决了问题。 - PGMacDesign

13

这段代码怎么样?我使用回退请求映射来捕捉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;
    }

}

7

@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");
    }   

}

6

默认情况下,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"
}

它还可用于处理所有类型的请求映射错误。查看此文章: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

默认的DispatcherServlet硬编码为使用MVC进行重定向,而不是在接收到不存在映射的请求时抛出异常 - 除非您像我在上面的帖子中所做的那样设置标志。 - ogradyjd
此外,我们实现ResponseEntityExceptionHandler类的原因是为了控制输出格式,并将错误堆栈日志记录到NoSQL解决方案中,然后发送客户端安全的错误消息。 - ogradyjd

4
对于REST控制器,我建议使用Zalando Problem Spring Web
如果Spring Boot旨在嵌入某些自动配置,则此库可用于更好地处理异常。您只需要添加依赖项即可:https://github.com/zalando/problem-spring-web
<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);
    }

}

然后,您可以定义控制器建议来处理异常,如下所示:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}

3

如果想根据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。

1

使用RestController注释的简单异常控制器类将负责在控制器级别处理异常。

    @RestControllerAdvice
    public class ExceptionController
    {
        // Mention the exception here..
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public ResponseEntity<?> exceptionHandler(MethodArgumentNotValidException e)
        {
            var errors = e.getBindingResult().getAllErrors().stream()
                          .map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
            var response = new ResponseBuilder()
                .withHttpStatus(HttpStatus.BAD_REQUEST.value())
                .withMessage(CustomStatus.FAILED.getMessage())
                .withErrorCode(CustomStatus.FAILED.getValue())
                .withErrorDescription(errors)
                .build();
            return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接