Java 如何在 Spring MVC 中针对 HTML 和 JSON 请求以不同方式处理异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23582534/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to handle exceptions in Spring MVC differently for HTML and JSON requests
提问by kayahr
I'm using the following exception handler in Spring 4.0.3 to intercept exceptions and display a custom error page to the user:
我在 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;
}
}
But now I want a different handling for JSON requests so I get JSON error responses for this kind of requests when an exception occurred. Currently the above code is also triggered by JSON requests (Using an Accept: application/json
header) and the JavaScript client doesn't like the HTML response.
但是现在我想要对 JSON 请求进行不同的处理,以便在发生异常时收到此类请求的 JSON 错误响应。目前,上面的代码也是由 JSON 请求触发的(使用Accept: application/json
标头)并且 JavaScript 客户端不喜欢 HTML 响应。
How can I handle exceptions differently for HTML and JSON requests?
如何以不同方式处理 HTML 和 JSON 请求的异常?
回答by Martin Frey
The controlleradvice annotation has several properties that can be set, since spring 4. You can define multiple controller advices applying different rules.
从 spring 4 开始,controlleradvice 注释有几个可以设置的属性。您可以定义多个应用不同规则的控制器通知。
One property is "annotations. Probably you can use a specific annotation on the json request mapping or you might find another property more usefull?
一个属性是“注释。也许您可以在 json 请求映射上使用特定的注释,或者您可能会发现另一个更有用的属性?
回答by Dave Syer
The best way to do this (especially in servlet 3) is to register an error page with the container, and use that to call a Spring @Controller
. That way you get to handle different response types in a standard Spring MVC way (e.g. using @RequestMapping
with produces=... for your machine clients).
执行此操作的最佳方法(尤其是在 servlet 3 中)是向容器注册一个错误页面,并使用它来调用 Spring @Controller
。这样您就可以以标准的 Spring MVC 方式处理不同的响应类型(例如@RequestMapping
,为您的机器客户端使用生产=...)。
I see from your other question that you are using Spring Boot. If you upgrade to a snapshot (1.1 or better in other words) you get this behaviour out of the box (see BasicErrorController
). If you want to override it you just need to map the /error path to your own @Controller
.
我从您的另一个问题中看到您正在使用 Spring Boot。如果您升级到快照(换句话说,1.1 或更高版本),您将立即获得此行为(请参阅 参考资料BasicErrorController
)。如果你想覆盖它,你只需要将 /error 路径映射到你自己的@Controller
.
回答by Michiel
Use @ControllerAdvice Let the exception handler send a DTO containing the field errors.
使用 @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);
}
This code is of this website:http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/Look there for more info.
此代码来自此网站:http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/ 在那里查看更多信息。
回答by Daniel
As you have the HttpServletRequest, you should be able to get the request "Accept" header. Then you could process the exception based on it.
由于您有 HttpServletRequest,您应该能够获得请求“Accept”标头。然后你可以根据它处理异常。
Something like:
就像是:
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;
}
回答by dnang
The ControllerAdvice annotation has an element/attribute called basePackage which can be set to determine which packages it should scan for Controllers and apply the advices. So, what you can do is to separate those Controllers handling normal requests and those handling AJAX requests into different packages then write 2 Exception Handling Controllers with appropriate ControllerAdvice annotations. For example:
ControllerAdvice 注释有一个名为 basePackage 的元素/属性,可以设置它以确定它应该扫描哪些包以查找控制器并应用建议。所以,你可以做的是将那些处理普通请求的控制器和那些处理 AJAX 请求的控制器分开到不同的包中,然后用适当的 ControllerAdvice 注释编写 2 个异常处理控制器。例如:
@ControllerAdvice("com.acme.webapp.ajaxcontrollers")
public class AjaxExceptionHandlingController {
...
@ControllerAdvice("com.acme.webapp.controllers")
public class ExceptionHandlingController {
回答by Katharsas
Since i didn't find any solution for this, i wrote some code that manually checks the accept
header of the request to determine the format. I then check if the user is logged in and either send the complete stacktrace if he is or a short error message.
由于我没有找到任何解决方案,因此我编写了一些代码来手动检查accept
请求的标头以确定格式。然后我检查用户是否登录并发送完整的堆栈跟踪(如果他是)或一条简短的错误消息。
I use ResponseEntity to be able to return both JSON or HTML like here.
Code:
我使用 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);
}
}
回答by nobar
The trick is to have a REST controller with two mappings, one of which specifies "text/html"
and returns a valid HTML source. The example below, which was tested in Spring Boot 2.0, assumes the existence of a separate template named "error.html"
.
诀窍是让 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"; }
}
References
参考
- ModelAndView-- return type for HTML
- DefaultErrorAttributes-- data used to render HTML template (and JSON response)
- BasicErrorController.java-- Spring Boot source from which this example was derived
- ModelAndView-- HTML 的返回类型
- DefaultErrorAttributes-- 用于呈现 HTML 模板(和 JSON 响应)的数据
- BasicErrorController.java-- 派生此示例的 Spring Boot 源