【问题标题】:Spring boot 404 error custom error response ReSTSpring boot 404错误自定义错误响应ReST
【发布时间】:2015-09-04 05:38:16
【问题描述】:

我正在使用 Spring Boot 来托管 REST API。即使浏览器正在访问 URL 以及自定义数据结构,我也希望始终发送 JSON 响应,而不是标准错误响应。

我可以使用@ControllerAdvice 和@ExceptionHandler 来处理自定义异常。但是我找不到任何好的方法来处理标准和处理 404 和 401 等错误。

有没有什么好的模式可以做到这一点?

【问题讨论】:

  • Markus,我的回答能解决问题吗?如果是,请接受。

标签: java json spring rest spring-boot


【解决方案1】:

您似乎需要引入一个适当注释的方法,例如对于不受支持的媒体类型 (415),它将是:

  @ExceptionHandler(MethodArgumentNotValidException)
  public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
    logger.error('Caught exception', e)
    def response = new ExceptionResponse(
            error: 'Validation error',
            exception: e.class.name,
            message: e.bindingResult.fieldErrors.collect { "'$it.field' $it.defaultMessage" }.join(', '),
            path: req.servletPath,
            status: BAD_REQUEST.value(),
            timestamp: currentTimeMillis()
    )
    new ResponseEntity<>(response, BAD_REQUEST)
  }

但是这可能是不可能的,因为 401 和 404 可能会在它们到达 DispatcherServlet 之前被抛出 - 在这种情况下 ControllerAdvice 将不起作用。

【讨论】:

  • 是的,但在这种情况下,我对 401 和 404 情况感兴趣,以便能够对客户端的所有错误做出一个共同的错误响应。我不想有不同的错误响应结构,也不想用 JSON 以外的任何东西进行响应。
  • 我呈现的结构是默认的JSON错误响应结构。如果您只需要 401 或 404,只需添加适当的异常而不是 MethodArgumentNotValidException 并更改方法主体。
【解决方案2】:

您可以添加与 web.xml 中的错误页面定义相关的自定义 ErrorPage 对象。 Spring Boot provides an example...

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
    return new MyCustomizer();
}

// ...

private static class MyCustomizer implements EmbeddedServletContainerCustomizer {

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized.html"));
        container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/not-found.html"));
    }

}

编辑:虽然我认为如果您将错误页面设置为休息控制器,上述方法将起作用,但更简单的方法是包含一个自定义 ErrorController 像下面...

@Bean
public ErrorController errorController(ErrorAttributes errorAttributes) {
    return new CustomErrorController(errorAttributes);
}

// ...

public class CustomErrorController extends BasicErrorController {

    public CustomErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

    @Override
    @RequestMapping(value = "${error.path:/error}")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        ResponseEntity<Map<String, Object>> error = super.error(request);
        HttpStatus statusCode = error.getStatusCode();

        switch (statusCode) {
        case NOT_FOUND:
            return getMyCustomNotFoundResponseEntity(request);
        case UNAUTHORIZED:
            return getMyCustomUnauthorizedResponseEntity(request);
        default:
            return error;
        }
    }
}

【讨论】:

  • 是的,但是在这种情况下,由于只使用了 REST 客户端,因此不适合出现 html 错误。
【解决方案3】:

404 错误由 DispatcherServlet 处理。有一个属性 throwExceptionIfNoHandlerFound,你可以覆盖它。

在 Application 类中你可以创建一个新的 bean:

@Bean
DispatcherServlet dispatcherServlet () {
    DispatcherServlet ds = new DispatcherServlet();
    ds.setThrowExceptionIfNoHandlerFound(true);
    return ds;
}

...然后在

中捕获NoHandlerFoundException异常
@EnableWebMvc
@ControllerAdvice
public class GlobalControllerExceptionHandler {
    @ExceptionHandler
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorMessageResponse requestHandlingNoHandlerFound(final NoHandlerFoundException ex) {
        doSomething(LOG.debug("text to log"));
    }
}

【讨论】:

  • 或者使用 spring.mvc.throw-exception-if-no-handler-found=true 属性而不是自定义 DispatcherServlet bean
  • 有什么方法可以不使用注解@EnableWebMvc我不想启用Web MVC
  • 不起作用。 java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.servlet.NoHandlerFoundException]
【解决方案4】:

我提供了有关如何覆盖 404 案例的响应的示例解决方案。解决方案非常简单,我发布了示例代码,但您可以在原始线程上找到更多详细信息:Spring Boot Rest - How to configure 404 - resource not found

首先:定义将处理错误情况并覆盖响应的控制器:

@ControllerAdvice
public class ExceptionHandlerController {

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(value= HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorResponse requestHandlingNoHandlerFound() {
        return new ErrorResponse("custom_404", "message for 404 error code");
    }
}

第二:你需要告诉Spring在404的情况下抛出异常(无法解析处理程序):

@SpringBootApplication
@EnableWebMvc
public class Application {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Application.class, args);

        DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
    }
}

【讨论】:

  • 有没有办法不使用注解@EnableWebMvc我不想启用Web MVC
  • 嗨@Reema!没有@EnableWebMvc,你能做到吗?没有它我也想这样做。
  • 不,我还没有任何解决方案!
【解决方案5】:

请参阅Spring Boot REST service exception handling。它展示了如何告诉 dispatcherservlet 为“找不到路由”发出异常,然后如何捕获这些异常。我们(我工作的地方)现在正在生产中使用它来提供我们的 REST 服务。

【讨论】:

    【解决方案6】:

    总结所有答案和评论,我认为最好的方法是-

    首先,告诉spring boot在application.properties找不到handler的情况下抛出异常

    spring.mvc.throw-exception-if-no-handler-found=true
    

    然后在您的应用程序中处理NoHandlerFoundException。我通过以下方式处理这个问题

    @ControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler(NoHandlerFoundException.class)
        public void handleNotFoundError(HttpServletResponse response, NoHandlerFoundException ex) {
            ErrorDto errorDto = Errors.URL_NOT_FOUND.getErrorDto();
            logger.error("URL not found exception: " + ex.getRequestURL());
            prepareErrorResponse(response, HttpStatus.NOT_FOUND, errorDto);
        }
    }
    

    【讨论】:

      【解决方案7】:

      我遇到了同样的问题,但使用不同的方法修复了它。 要在自定义响应中返回 404401 和其他状态,您现在可以将响应状态添加到自定义异常类并从您的异常处理程序中调用它。

      使用 spring 实用程序类 AnnotationUtils,您可以使用 findAnnotation 方法获取任何定义的自定义异常的状态,并且它将使用您为异常定义的任何注释返回适当的状态,包括未找到.

      这是我的@RestControllerAdvice

      @RestControllerAdvice
      public class MainExceptionHandler extends Throwable{
      
       @ExceptionHandler(BaseException.class)
       ResponseEntity<ExceptionErrorResponse> exceptionHandler(GeneralMainException  e)
       {
        ResponseStatus status = AnnotationUtils.findAnnotation(e.getClass(),ResponseStatus.class);
        if(status != null)
        {
          return new ResponseEntity<>(new ExceptionErrorResponse(e.getCode(),e.getMessage()),status.code());
        }
       }

      CustomParamsException 返回错误请求状态

      @ResponseStatus(value= HttpStatus.BAD_REQUEST)
      public class CustomParamsException extends BaseException {
          private static final String CODE = "400";
          public CustomParamsException(String message) {
              super(CODE, message);
          }
      }

      未找到返回未找到状态的详细信息

      @ResponseStatus(value= HttpStatus.NOT_FOUND)
      public class DetailsNotException extends BaseException {
          private static final String CODE = "400";
          public DetailsNotException(String message) {
              super(CODE, message);
          }
      }

      扩展异常的 GeneralMainException

      public class GeneralMainException extends Exception {
      private String code;
      private String message;
      
      public GeneralMainException (String message) {
          super(message);
      }
      
      public GeneralMainException (String code, String message) {
          this.code = code;
          this.message = message;
      }
      
      public String getCode() {
          return code;
      }
      
      @Override
      public String getMessage() {
          return message;
      }
      }

      您可以通过将其包含在控制器建议中来决定处理其他系统异常。

      @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
       @ExceptionHandler(Exception.class)
       ExceptionErrorResponse sysError(Exception e)
       {
        return new ExceptionErrorResponse(""1002", e.getMessage());
       }
      

      【讨论】:

        【解决方案8】:

        对于那些不想使用@EnableWebMvc的 Spring Boot 2 用户

        application.properties

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

        控制器建议

        @RestControllerAdvice
        public class ExceptionResolver {
        
            @ExceptionHandler(NoHandlerFoundException.class)
            @ResponseStatus(HttpStatus.NOT_FOUND)
            public HashMap<String, String> handleNoHandlerFound(NoHandlerFoundException e, WebRequest request) {
                HashMap<String, String> response = new HashMap<>();
                response.put("status", "fail");
                response.put("message", e.getLocalizedMessage());
                return response;
            }
        }
        

        Source

        【讨论】:

        • 不使用@EnableWebMvc 的原因是什么?
        • @ptrckdev 添加@EnableWebMvc 会禁用自动配置。见herehere
        • spring.resources.add-mappings=false 在我的服务中禁用了 swagger-ui。有一些解决方法吗?
        【解决方案9】:

        从 Spring 5 开始可以使用类 ResponseStatusException:

        @GetMapping("example")
        public ResponseEntity example() {
            try {
                throw new MyException();
            } catch (MyException e) {
                throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "My Exception", e);
            }
        }
        

        【讨论】:

          【解决方案10】:

          我希望在所有可能的错误场景中都具有相同的错误格式 (json) 结构,因此我刚刚注册了自己的 ErrorController,并重用了 AbstractErrorController 中的代码:

          @Controller
          @RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
          @ResponseBody
          public class ErrorController extends AbstractErrorController {
          
              public ErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
                  super(errorAttributes, errorViewResolvers.orderedStream().collect(Collectors.toUnmodifiableList()));
              }
          
              @RequestMapping
              public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
                  final var status = getStatus(request);
                  if (status == HttpStatus.NO_CONTENT) {
                      return new ResponseEntity<>(status);
                  }
                  return new ResponseEntity<>(getErrorAttributes(request, ErrorAttributeOptions.defaults()), status);
              }
          
              @Override
              public String getErrorPath() {
                  return null;
              }
          }
          

          这样你就不需要任何控制器建议了,所有的错误都默认进入错误方法

          【讨论】:

            【解决方案11】:

            您可以扩展 ResponseEntityExceptionHandler 类,其中包含 Spring Boot 项目中的许多常见异常。例如,如果您希望使用自定义处理程序来绑定异常,则可以使用以下内容,

            @ControllerAdvice
            public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
            
                @Override
                public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
                    String responseBody = "{\"key\":\"value\"}";
                    headers.add("Content-Type", "application/json;charset=utf-8");
                    return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_ACCEPTABLE, request);
                }
            }
            

            http 状态的另一个示例404-未找到

            @ControllerAdvice
            public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
            
                @Override
                public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
                    String responseBody = "{\"errormessage\":\"WHATEVER YOU LIKE\"}";
                    headers.add("Content-Type", "application/json;charset=utf-8");
                    return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_FOUND, request);
                }
            }
            

            关于 404 not found 异常,您应该将 DispatcherServlet 配置为在找不到任何处理程序时抛出异常,而不是默认行为。对于 404 的问题,你也可以阅读this question。

            【讨论】:

              【解决方案12】:

              如果 @RestControllerAdvice 带有弹簧启动,它对我有用

              spring.mvc.throw-exception-if-no-handler-found=true
              server.error.whitelabel.enabled=false
              spring.resources.add-mappings=false
              
              @RestControllerAdvice
              public class ErrorHandlerController {
              
              @ExceptionHandler(NoHandlerFoundException.class)
              @ResponseStatus(value = HttpStatus.NOT_FOUND )
              public String handleNotFoundError(NoHandlerFoundException ex) {
                  return "path does not exists";
              }
              }
              

              【讨论】:

                猜你喜欢
                • 2014-12-01
                • 2021-02-24
                • 2020-03-17
                • 2014-11-30
                • 2015-11-09
                • 2022-02-03
                • 2017-08-07
                • 2012-05-22
                • 2020-07-25
                相关资源
                最近更新 更多