繁体   English   中英

如何针对HTML和JSON请求以不同方式处理Spring MVC中的异常

[英]How to handle exceptions in Spring MVC differently for HTML and JSON requests

我在Spring 4.0.3中使用以下异常处理程序拦截异常并向用户显示自定义错误页面:

@ControllerAdvice
public class ExceptionHandlerController
{
    @ExceptionHandler(value = Exception.class)
    public ModelAndView handleError(HttpServletRequest request, Exception e)
    {
        ModelAndView mav = new ModelAndView("/errors/500"));
        mav.addObject("exception", e);
        return mav;
    }
}

但是现在我想要对JSON请求进行不同的处理,以便在发生异常时获得此类请求的JSON错误响应。 目前上面的代码也是由JSON请求(使用Accept: application/json标头)触发的,而JavaScript客户端不喜欢HTML响应。

如何针对HTML和JSON请求以不同方式处理异常?

ControllerAdvice注释有一个名为basePackage的元素/属性,可以设置它来确定它应该为控制器扫描哪些包并应用建议。 因此,您可以做的是将处理正常请求的控制器和处理AJAX请求的控制器分离到不同的包中,然后使用适当的ControllerAdvice注释编写2个异常处理控制器。 例如:

@ControllerAdvice("com.acme.webapp.ajaxcontrollers")
public class AjaxExceptionHandlingController {
...
@ControllerAdvice("com.acme.webapp.controllers")
public class ExceptionHandlingController {

执行此操作的最佳方法(尤其是在servlet 3中)是使用容器注册错误页面,并使用它来调用Spring @Controller 这样您就可以在标准的Spring MVC方式中处理不同的响应类型(例如,对于您的机器客户端使用带有produce = ...的@RequestMapping )。

我从你的另一个问题看到你正在使用Spring Boot。 如果升级到快照(换句话说为1.1或更高版本),则会立即获得此行为(请参阅BasicErrorController )。 如果要覆盖它,只需要将/ error路径映射到您自己的@Controller

当您拥有HttpServletRequest时,您应该能够获得请求“Accept”标头。 然后,您可以基于它处理异常。

就像是:

String header = request.getHeader("Accept");
if(header != null && header.equals("application/json")) {
    // Process JSON exception
} else {
    ModelAndView mav = new ModelAndView("/errors/500"));
    mav.addObject("exception", e);
    return mav;
}

由于我没有找到任何解决方案,我写了一些代码,手动检查请求的accept标头,以确定格式。 然后我检查用户是否已登录并发送完整的堆栈跟踪(如果是)或短错误消息。

我使用ResponseEntity可以像这里一样返回JSON或HTML。
码:

@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleExceptions(Exception ex, HttpServletRequest request) throws Exception {

    final HttpHeaders headers = new HttpHeaders();
    Object answer; // String if HTML, any object if JSON
    if(jsonHasPriority(request.getHeader("accept"))) {
        logger.info("Returning exception to client as json object");
        headers.setContentType(MediaType.APPLICATION_JSON);
        answer = errorJson(ex, isUserLoggedIn());
    } else {
        logger.info("Returning exception to client as html page");
        headers.setContentType(MediaType.TEXT_HTML);
        answer = errorHtml(ex, isUserLoggedIn());
    }
    final HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
    return new ResponseEntity<>(answer, headers, status);
}

private String errorHtml(Exception e, boolean isUserLoggedIn) {
    String error = // html code with exception information here
    return error;
}

private Object errorJson(Exception e, boolean isUserLoggedIn) {
    // return error wrapper object which will be converted to json
    return null;
}

/**
 * @param acceptString - HTTP accept header field, format according to HTTP spec:
 *      "mime1;quality1,mime2;quality2,mime3,mime4,..." (quality is optional)
 * @return true only if json is the MIME type with highest quality of all specified MIME types.
 */
private boolean jsonHasPriority(String acceptString) {
    if (acceptString != null) {
        final String[] mimes = acceptString.split(",");
        Arrays.sort(mimes, new MimeQualityComparator());
        final String firstMime = mimes[0].split(";")[0];
        return firstMime.equals("application/json");
    }
    return false;
}

private static class MimeQualityComparator implements Comparator<String> {
    @Override
    public int compare(String mime1, String mime2) {
        final double m1Quality = getQualityofMime(mime1);
        final double m2Quality = getQualityofMime(mime2);
        return Double.compare(m1Quality, m2Quality) * -1;
    }
}

/**
 * @param mimeAndQuality - "mime;quality" pair from the accept header of a HTTP request,
 *      according to HTTP spec (missing mimeQuality means quality = 1).
 * @return quality of this pair according to HTTP spec.
 */
private static Double getQualityofMime(String mimeAndQuality) {
    //split off quality factor
    final String[] mime = mimeAndQuality.split(";");
    if (mime.length <= 1) {
        return 1.0;
    } else {
        final String quality = mime[1].split("=")[1];
        return Double.parseDouble(quality);
    }
}

诀窍是让REST控制器有两个映射,其中一个指定"text/html"并返回一个有效的HTML源代码。 以下示例在Spring Boot 2.0中进行了测试,假设存在一个名为"error.html"的单独模板。

@RestController
public class CustomErrorController implements ErrorController {

    @Autowired
    private ErrorAttributes errorAttributes;

    private Map<String,Object> getErrorAttributes( HttpServletRequest request ) {
        WebRequest webRequest = new ServletWebRequest(request);
        boolean includeStacktrace = false;
        return errorAttributes.getErrorAttributes(webRequest,includeStacktrace);
    }

    @GetMapping(value="/error", produces="text/html")
    ModelAndView errorHtml(HttpServletRequest request) {
        return new ModelAndView("error.html",getErrorAttributes(request));
    }

    @GetMapping(value="/error")
    Map<String,Object> error(HttpServletRequest request) {
        return getErrorAttributes(request);
    }

    @Override public String getErrorPath() { return "/error"; }

}

参考

从Spring 4开始,controlleradvice注释具有几个可以设置的属性。您可以定义应用不同规则的多个控制器建议。

一个属性是“注释。可能你可以在json请求映射上使用特定的注释,或者你可能会发现另一个属性更有用吗?

使用@ControllerAdvice让异常处理程序发送包含字段错误的DTO。

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
    BindingResult result = ex.getBindingResult();
    List<FieldError> fieldErrors = result.getFieldErrors();

    return processFieldErrors(fieldErrors);
}

此代码来自以下网站: http//www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/请查看更多信息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM