12

为了全局处理可能在控制器之外发生的错误(例如 HTTP 404),我的 web.xml 中有类似于以下内容的条目:

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

在我的 ErrorController 中,我有类似以下的相应方法:

@Controller
@RequestMapping("/errors")
public class ErrorController {

    @RequestMapping(value = "/404", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<ErrorResponse> error404() {

        ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");

        return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
    }
}

我面临的问题是ContentNegotiationManager我配置的和消息转换器在这种情况下没有被使用。我怀疑由于请求被重定向到错误页面,内容协商中使用的原始请求属性丢失了,这被视为一个完全独立的请求。(即对 /mycontroller/badresource.json 的原始请求 --> /errors/404(无文件扩展名))

像这样的错误处理程序中是否有任何方法可以确定和/或响应原始请求中请求的适当内容类型?

4

3 回答 3

4

Spring MVC 3.2 现在包含一个有用的注解,称为@ControllerAdvice. 您可以添加一个ExceptionHandler全局处理您定义的任何异常的方法。

对我来说,我只关心返回给客户端的两种可能的内容类型 -application/jsontext/html.

这是我将如何设置它 -

@ControllerAdvice
public class ExceptionControllerAdvice {

    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private static final MediaType JSON_MEDIA_TYPE = new MediaType("application", "json", DEFAULT_CHARSET);

    //I decided to handle all exceptions in this one method
    @ExceptionHandler(Throwable.class)
    public @ResponseBody String handleThrowable(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {

        ...

        if(supportsJsonResponse(request.getHeader("Accept"))) {

            //return response as JSON
            response.setStatus(statusCode);
            response.setContentType(JSON_MEDIA_TYPE.toString());

                    //TODO serialize your error in a JSON format
                    //return ...

        } else {

            //return as HTML
            response.setContentType("text/html");
            response.sendError(statusCode, exceptionMessage);
            return null;
        }
    }

    private boolean supportsJsonResponse(String acceptHeader) {

        List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);

        for(MediaType mediaType : mediaTypes) {
            if(JSON_MEDIA_TYPE.includes(mediaType)) {
                return true;
            }
        }

        return false;
    }

}
于 2013-06-19T18:42:52.170 回答
3

我为此想出了一些技巧,但它似乎有效。它基本上涉及错误处理中的额外转发以确定原始请求的文件扩展名。

在我的 web.xml 中,我将错误转发给了中间操作:

<error-page>
    <error-code>404</error-code>
    <location>/errors/redirect</location>
</error-page>

然后,在转发到将生成错误响应的操作之前,会检查原始请求是否有文件扩展名。如果有,它确保将其附加到转发 URI。HTTP 标头是自动转发的,因此如果您设置的内容协商仅涉及文件扩展名或 HTTP 标头,这将有效地允许“错误页面”以适当的内容类型返回错误。

@Controller
@RequestMapping("/errors")
public class ErrorController {

    @RequestMapping(value = "/redirect", method = RequestMethod.GET)
    public void errorRedirect(HttpServletRequest request, HttpServletResponse response) {

        // Get original request URI
        String uri = (String)request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE);

        // Try to determine file extension
        String filename = WebUtils.extractFullFilenameFromUrlPath(uri);
        String extension = StringUtils.getFilenameExtension(filename);
        extension = StringUtils.hasText(extension) ? "." + extension : "";

        // Forward request to appropriate handler with original request's file extension (i.e. /errors/404.json)
        String forwardUri = "/errors/404" + extension); 
        request.getRequestDispatcher(forwardUri).forward(request, response);
    }

    @RequestMapping(value = "/404", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<ErrorResponse> error404() {

        ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");

        return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
    }
}
于 2013-02-07T22:04:19.963 回答
2

是的,应用程序异常和 HTTP 响应错误代码是两个不同的东西。

您可以修改如下代码,以便您可以访问requestUri. 我想你可以根据它找到内容类型。我知道它很粗糙,但我认为我们没有替代解决方案:

@RequestMapping(value = "/404", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<ErrorResponse> error404(HttpServletRequest request) {

    ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");

    String requestUri = request.getRequestURI();

    return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
}

从示例中我假设您的应用程序是 REST 服务,那么您可能可以参考此链接了解如何在 REST 完整服务中处理 404 。

于 2013-02-06T11:43:25.423 回答