Spring MVC 3.2 - 错误页面的内容协商?

11
为了全局处理错误(例如HTTP 404),这些错误可能发生在Controller之外,我在web.xml中有类似以下的条目:

为了全局处理错误(例如HTTP 404),这些错误可能发生在Controller之外,我在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(没有文件扩展名))
在这种错误处理程序中有没有办法确定并/或以原始请求所请求的适当内容类型进行响应?
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;
    }

}

返回ResponseEntity而不是String更好吗?使用这种方法,我应该在web.xml中设置404错误页面吗?请查看我的问题:http://stackoverflow.com/questions/17322855/how-to-handle-404-exception-invoked-by-ajax-spring-mvc-3-2-controlleradvice - Alex

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

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


谢谢你的回复。我看过那篇文章,但除非我漏掉了什么,它只处理来自控制器而不是一般的servlet错误的异常。此外,在这种情况下request.getRequestURI()会给出错误页面的URI。看起来您必须使用req.getAttribute(“javax.servlet.error.request_uri”)来获取原始请求的URI。我在想是否有一种方式可以覆盖ContentNegotiationManager使用的信息,并将其设置为原始请求的信息,而不是错误页面的信息? - WayneC

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