如何针对HTML和JSON请求以不同方式处理Spring MVC中的exception
我在Spring 4.0.3中使用以下exception处理程序拦截exception并向用户显示自定义错误页面:
@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请求进行不同的处理,以便在发生exception时获得针对此类请求的JSON错误响应。 目前上述代码也是由JSON请求(使用Accept: application/json
标头)触发的,而JavaScript客户端不喜欢HTML响应。
如何针对HTML和JSON请求以不同方式处理exception?
ControllerAdvice注释有一个名为basePackage的元素/属性,可以设置它来确定它应该为控制器扫描哪些包并应用建议。 因此,您可以做的是将处理正常请求的控制器和处理AJAX请求的控制器分离到不同的包中,然后使用适当的ControllerAdvice注释编写2个exception处理控制器。 例如:
@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”标头。 然后,您可以基于它处理exception。
就像是:
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 { @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); } }
从Spring 4开始,controlleradvice注释具有几个可以设置的属性。您可以定义应用不同规则的多个控制器建议。
一个属性是“注释。可能你可以在json请求映射上使用特定的注释,或者你可能会发现另一个属性更有用吗?
使用@ControllerAdvice让exception处理程序发送包含字段错误的DTO。
@ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) { BindingResult result = ex.getBindingResult(); List fieldErrors = result.getFieldErrors(); return processFieldErrors(fieldErrors); }
此代码来自以下网站: http : //www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/请查看更多信息。
诀窍是让REST控制器有两个映射,其中一个指定"text/html"
并返回一个有效的HTML源代码。 以下示例在Spring Boot 2.0中进行了测试,假设存在一个名为"error.html"
的单独模板。
@RestController public class CustomErrorController implements ErrorController { @Autowired private ErrorAttributes errorAttributes; private Map 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 error(HttpServletRequest request) { return getErrorAttributes(request); } @Override public String getErrorPath() { return "/error"; } }
参考
- ModelAndView – 返回HTML的类型
- DefaultErrorAttributes – 用于呈现HTML模板的数据(和JSON响应)
- BasicErrorController.java – 从此示例派生的Spring Boot源
- 在java中使用jackson json反序列化时忽略缺少的属性
- Java Jackson:反序列化复杂的多态对象模型:JsonMappingException:意外的令牌(START_OBJECT),预期的VALUE_STRING
- 将Jersey / Jackson配置为不使用@XmlElement字段注释进行JSON字段命名
- 如何使用java中的XPath / JsonPath更改json文件中的值
- jackson将变量反序列化为Json字符串
- JsonMappingException:无法从START_OBJECT标记中反序列化java.lang.Integer的实例
- 如何使用jackson的TypeReference与generics?
- 使用Jackson将对象转储为String
- 从spring mvc controller返回一个简单的map结构到ajax