【问题标题】:Spring MVC REST Handing Bad Url (404) by returning JSONSpring MVC REST 通过返回 JSON 处理错误 URL(404)
【发布时间】:2014-04-05 03:30:12
【问题描述】:

我正在使用 SpringMVC 开发一个 REST 服务,我在类和方法级别有 @RequestMapping。

此应用程序当前配置为返回 web.xml 中配置的错误页面 jsp。

<error-page>
    <error-code>404</error-code>
    <location>/resourceNotFound</location>
</error-page>

然而,我想返回自定义 JSON 而不是这个错误页面。

通过在控制器中编写它,我能够处理异常并为其他异常返回 json,但不确定当 url 根本不存在时如何以及在何处编写返回 JSON 的逻辑。

    @ExceptionHandler(TypeMismatchException.class)
        @ResponseStatus(value=HttpStatus.NOT_FOUND)
        @ResponseBody
        public ResponseEntity<String> handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {

            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "application/json; charset=utf-8");
            Locale locale = LocaleContextHolder.getLocale();
            String errorMessage = messageSource.getMessage("error.patient.bad.request", null, locale);

            errorMessage += ex.getValue();
            String errorURL = req.getRequestURL().toString();

            ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
            return new ResponseEntity<String>(errorInfo.toJson(), headers, HttpStatus.BAD_REQUEST);

        }

我试过@ControllerAdvice,它适用于其他异常情况,但在映射不可用时不起作用,

@ControllerAdvice
public class RestExceptionProcessor {

    @Autowired
    private MessageSource messageSource;

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    @ResponseBody
    public ResponseEntity<String> requestMethodNotSupported(HttpServletRequest req, HttpRequestMethodNotSupportedException ex) {
        Locale locale = LocaleContextHolder.getLocale();
        String errorMessage = messageSource.getMessage("error.patient.bad.id", null, locale);

        String errorURL = req.getRequestURL().toString();

        ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
        return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(NoSuchRequestHandlingMethodException.class)
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    @ResponseBody
    public ResponseEntity<String> requestHandlingMethodNotSupported(HttpServletRequest req, NoSuchRequestHandlingMethodException ex) {
        Locale locale = LocaleContextHolder.getLocale();
        String errorMessage = messageSource.getMessage("error.patient.bad.id", null, locale);

        String errorURL = req.getRequestURL().toString();

        ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
        return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
    }


}

【问题讨论】:

    标签: json rest spring-mvc


    【解决方案1】:

    在 SpringFramework 中挖掘 DispatcherServlet 和 HttpServletBean.init() 之后,我发现它在 Spring 4 中是可能的。

    org.springframework.web.servlet.DispatcherServlet

    /** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
    private boolean throwExceptionIfNoHandlerFound = false;
    
    protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (pageNotFoundLogger.isWarnEnabled()) {
            String requestUri = urlPathHelper.getRequestUri(request);
            pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
                    "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        if(throwExceptionIfNoHandlerFound) {
            ServletServerHttpRequest req = new ServletServerHttpRequest(request);
            throw new NoHandlerFoundException(req.getMethod().name(),
                    req.getServletRequest().getRequestURI(),req.getHeaders());
        } else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }
    

    throwExceptionIfNoHandlerFound 默认为 false,我们应该在 web.xml

    中启用它
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>throwExceptionIfNoHandlerFound</param-name>
                <param-value>true</param-value>
            </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    

    然后您可以使用此方法在带有 @ControllerAdvice 注释的类中捕获它。

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    @ResponseBody
    public ResponseEntity<String> requestHandlingNoHandlerFound(HttpServletRequest req, NoHandlerFoundException ex) {
        Locale locale = LocaleContextHolder.getLocale();
        String errorMessage = messageSource.getMessage("error.bad.url", null, locale);
    
        String errorURL = req.getRequestURL().toString();
    
        ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
        return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
    }
    

    这允许我为不存在映射的错误 URL 返回 JSON 响应,而不是重定向到 JSP 页面:)

    {"message":"URL does not exist","url":"http://localhost:8080/service/patientssd"}
    

    【讨论】:

    • 优秀的答案,同样值得注意的是,你不能使用 DefaultServletHandlerConfigurer ,因为它会在到达 DispatcherServlet 之前消耗 404
    • 我已经尝试过这种方式,我希望实现 HandlerExceptionResolver 的解析器能够捕获此异常,但事实并非如此。
    • 对于基于java的配置,需要覆盖AbstractAnnotationConfigDispatcherServletInitializercustomizeRegistration。见this answer
    • throwExceptionIfNoHandlerFound in web.xml 为我解决了这个问题。 Dispatcher servlet 现在会抛出我的@ControllerAdvice 所需的错误以优雅地处理它
    • 使用上面的方法,我在RestExceptionProcessor类中仍然遇到NoHandlerFoundException。
    【解决方案2】:

    如果您使用的是 Spring Boot,请同时设置这两个属性:

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

    现在您的@ControllerAdvice 注释类可以处理“NoHandlerFoundException”,如下所示。

    @ControllerAdvice
    @RequestMapping(produces = "application/json")
    @ResponseBody
    public class RestControllerAdvice {
    
        @ExceptionHandler(NoHandlerFoundException.class)
        public ResponseEntity<Map<String, Object>> unhandledPath(final NoHandlerFoundException e) {
            Map<String, Object> errorInfo = new LinkedHashMap<>();
            errorInfo.put("timestamp", new Date());
            errorInfo.put("httpCode", HttpStatus.NOT_FOUND.value());
            errorInfo.put("httpStatus", HttpStatus.NOT_FOUND.getReasonPhrase());
            errorInfo.put("errorMessage", e.getMessage());
            return new ResponseEntity<Map<String, Object>>(errorInfo, HttpStatus.NOT_FOUND);
        }
    
    }
    

    请注意,仅指定此属性是不够的:

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

    ,因为默认情况下 Spring 将未知 url 映射到 /**,所以实际上永远不会“找不到处理程序”。

    要禁用到 /** 的未知 url 映射,您需要

    spring.resources.add-mappings=false ,
    

    这就是这两个属性一起产生所需行为的原因。

    【讨论】:

      【解决方案3】:

      如果您使用的是 spring 3.2 或更高版本,您可以使用控制器建议 (@ControllerAdvice) 来处理映射错误 (404) 等问题。你可以找到documentation here。请看第 17.11 节。例如,您可以使用它来提供更详细的日志记录,说明为什么您的请求绑定没有与特定 url 匹配,或者只是返回比通用 404 更具体的响应。

      【讨论】:

      • 你使用的是什么 Spring MVC 版本?
      • 我听从了你的建议,并使用 HttpRequestMethodNotSupportedException 和 NoSuchRequestHandlingMethodException 实现了@Controller Advice,当我期待 JSON 时,我仍然被重定向到 jsp。然而,控制器建议适用于其他情况。更新了我的代码。我正在使用 Spring 3.2,我可以升级。
      【解决方案4】:

      您可以在下面的位置返回json,即/handle/404

      <error-page>
          <error-code>404</error-code>
          <location>/handle/404</location>
      </error-page>
      

      在 web.xml 中配置后,404 错误将重定向到 /handle/404,您可以使用此映射创建控制器并返回 json 结果。例如。

      @RestController
      @RequestMapping(value = "handle")
      public class HttpErrorController {
         @RequestMapping(value = "404")
         public String handle404() {
            return "404 error";
         }
      }
      

      【讨论】:

        猜你喜欢
        • 2019-03-10
        • 2016-06-05
        • 1970-01-01
        • 1970-01-01
        • 2015-07-26
        • 2015-06-23
        • 2015-04-24
        • 2018-12-08
        • 2015-05-06
        相关资源
        最近更新 更多