Spring MV 3.2异常响应映射
Spring MV 3.2 Exception Response Mapping
Spring 3.2.15,这里是基于 MVC 的 REST API( 不是 Spring 引导,遗憾!)。我正在尝试实施满足以下条件的异常 mapper/handler:
- 无论发生什么(成功或错误),Spring 应用总是 return 是
MyAppResponse
的响应实体(见下文);和
- 如果成功处理请求,return HTTP 状态 200(典型值);和
- 在处理请求时出现异常,我需要控制特定异常到特定HTTP状态码的映射
- Spring MVC 框架错误(例如
BlahException
)必须映射到 HTTP 422
- 自定义应用程序异常,例如我的
FizzBuzzException
有自己的状态映射方案:
FizzBuzzException
-> HTTP 401
FooBarException
-> HTTP 403
OmgException
-> HTTP 404
- 所有其他异常,即非 Spring 异常和非自定义应用程序异常(上面列出的 3 个),应生成 HTTP 500
MyAppResponse
对象所在的位置:
// Groovy pseudo-code
@Canonical
class MyAppResponse {
String detail
String randomNumber
}
它 看起来 像 ResponseEntityExceptionHandler
可能可以为我做到这一点,但我不是透过树木看到森林 w.r.t。它如何获得传递的参数。我希望我可以做类似的事情:
// Groovy-pseudo code
@ControllerAdvice
class MyAppExceptionMapper extends ResponseEntityExceptionHandler {
ResponseEntity<Object> handleFizzBuzzException(FizzBuzzException fbEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 401?
status = ???
new ResponseEntity(fbEx.message, headers, status)
}
ResponseEntity<Object> handleFooBarException(FooBarException fbEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 403?
status = ???
new ResponseEntity(fbEx.message, headers, status)
}
ResponseEntity<Object> handleOmgException(OmgException omgEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 404?
status = ???
new ResponseEntity(omgEx.message, headers, status)
}
// Now map all Spring-generated exceptions to 422
ResponseEntity<Object> handleAllSpringExceptions(SpringException springEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 422?
status = ???
new ResponseEntity(springEx.message, headers, status)
}
// Everything else is a 500...
ResponseEntity<Object> handleAllOtherExceptions(Exception ex, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 500?
status = ???
new ResponseEntity("Whoops, something happened. Lol.", headers, status)
}
}
知道如何完全实现此映射逻辑以及实体是 MyAppResponse
实例而不仅仅是字符串的要求吗?
然后,用 @ControllerAdvice
注释 class 是我配置 Spring 使用它唯一需要做的事情吗?
首先,错误/异常处理程序应该不担心成功响应。
因此,成功响应的责任应由控制器(普通控制器或 REST 控制器)方法承担 @RequestMapping
注释,如下所示
@RequestMapping(value = "/demo", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.OK)
public MyAppResponse doSomething() { .... }
要映射特定的 HTTP 响应代码与异常(s)简单地写一个 @ControllerAdvice
如下(不需要额外的配置)
@ControllerAdvice
public class CustomExceptionHandler {
// Handle FizzBuzzException with status code as 401
@ExceptionHandler(value = FizzBuzzException.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(FizzBuzzException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.UNAUTHORIZED);
}
// Handle FooBarException with status code as 403
@ExceptionHandler(value = FooBarException.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(FooBarException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.FORBIDDEN);
}
// Handle OmgException with status code as 404
@ExceptionHandler(value = OmgException.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(OmgException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.NOT_FOUND);
}
// handle Spring MVC specific exceptions with status code 422
@ExceptionHandler(value = {NoSuchRequestHandlingMethodException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, ServletRequestBindingException.class,
ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, MethodArgumentNotValidException.class,
MissingServletRequestPartException.class, BindException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class})
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.UNPROCESSABLE_ENTITY);
}
// Handle rest of the exception(s) with status code as 500
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.INTERNAL_SERVER_ERROR);
}
private MyAppResponse buildResponse(Throwable t) {
MyAppResponse response = new MyAppResponse();
// supply value to response object
return response;
}
}
如果需要任何进一步的信息,请在评论中告知。
P.S.:Spring MVC 异常列表 reference
要减少@bond-java-bond 答案你不需要自己构建ResponseEntity
:
- 对每个
handleSomeException
方法使用 @ResponseStatus
(例如 @ResponseStatus(HttpStatus.UNAUTHORIZED)
)
- Return 来自这些方法的自定义
MyAppResponse
但是如果每种异常都将以相同的方式处理(仅通过 HTTP 状态区分),我建议像这样减少 MyAppExceptionMapper
:
@ControllerAdvice
public class MyAppExceptionMapper {
private final Map<Class<?>, HttpStatus> map;
{
map = new HashMap<>();
map.put(FizzBuzzException.class, HttpStatus.UNAUTHORIZED);
map.put(FooBarException.class, HttpStatus.FORBIDDEN);
map.put(NoSuchRequestHandlingMethodException.class, HttpStatus.UNPROCESSABLE_ENTITY);
/* List Spring specific exceptions here as @bond-java-bond suggested */
map.put(Exception.class, HttpStatus.INTERNAL_SERVER_ERROR);
}
// Handle all exceptions
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception exception) {
MyAppResponse response = new MyAppResponse();
// Fill response with details
HttpStatus status = map.get(exception.getClass());
if (status == null) {
status = map.get(Exception.class);// By default
}
return new ResponseEntity<>(response, status);
}
}
优点:
- 很短。
- 没有代码重复。
- 稍微更有效。
- 易于扩展。
此外,您可以将映射配置移到外部并注入它。
如何配置 MVC Dispatcher Servlet
首先,检查 mvc-dispatcher-servlet.xml
(或来自 web.xml
的另一个 contextConfigLocation
)是否包含:
<context:component-scan base-package="base.package"/>
<mvc:annotation-driven/>
其次,查看注解为class的@ControllerAdvice和注解为class的@Controller是否属于base.package
.
的子包
有关详细信息,请参阅 Exception Handling in Spring MVC or Spring MVC @ExceptionHandler Example 中的完整示例。
Spring 3.2.15,这里是基于 MVC 的 REST API( 不是 Spring 引导,遗憾!)。我正在尝试实施满足以下条件的异常 mapper/handler:
- 无论发生什么(成功或错误),Spring 应用总是 return 是
MyAppResponse
的响应实体(见下文);和 - 如果成功处理请求,return HTTP 状态 200(典型值);和
- 在处理请求时出现异常,我需要控制特定异常到特定HTTP状态码的映射
- Spring MVC 框架错误(例如
BlahException
)必须映射到 HTTP 422 - 自定义应用程序异常,例如我的
FizzBuzzException
有自己的状态映射方案:FizzBuzzException
-> HTTP 401FooBarException
-> HTTP 403OmgException
-> HTTP 404
- 所有其他异常,即非 Spring 异常和非自定义应用程序异常(上面列出的 3 个),应生成 HTTP 500
- Spring MVC 框架错误(例如
MyAppResponse
对象所在的位置:
// Groovy pseudo-code
@Canonical
class MyAppResponse {
String detail
String randomNumber
}
它 看起来 像 ResponseEntityExceptionHandler
可能可以为我做到这一点,但我不是透过树木看到森林 w.r.t。它如何获得传递的参数。我希望我可以做类似的事情:
// Groovy-pseudo code
@ControllerAdvice
class MyAppExceptionMapper extends ResponseEntityExceptionHandler {
ResponseEntity<Object> handleFizzBuzzException(FizzBuzzException fbEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 401?
status = ???
new ResponseEntity(fbEx.message, headers, status)
}
ResponseEntity<Object> handleFooBarException(FooBarException fbEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 403?
status = ???
new ResponseEntity(fbEx.message, headers, status)
}
ResponseEntity<Object> handleOmgException(OmgException omgEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 404?
status = ???
new ResponseEntity(omgEx.message, headers, status)
}
// Now map all Spring-generated exceptions to 422
ResponseEntity<Object> handleAllSpringExceptions(SpringException springEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 422?
status = ???
new ResponseEntity(springEx.message, headers, status)
}
// Everything else is a 500...
ResponseEntity<Object> handleAllOtherExceptions(Exception ex, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 500?
status = ???
new ResponseEntity("Whoops, something happened. Lol.", headers, status)
}
}
知道如何完全实现此映射逻辑以及实体是 MyAppResponse
实例而不仅仅是字符串的要求吗?
然后,用 @ControllerAdvice
注释 class 是我配置 Spring 使用它唯一需要做的事情吗?
首先,错误/异常处理程序应该不担心成功响应。
因此,成功响应的责任应由控制器(普通控制器或 REST 控制器)方法承担 @RequestMapping
注释,如下所示
@RequestMapping(value = "/demo", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.OK)
public MyAppResponse doSomething() { .... }
要映射特定的 HTTP 响应代码与异常(s)简单地写一个 @ControllerAdvice
如下(不需要额外的配置)
@ControllerAdvice
public class CustomExceptionHandler {
// Handle FizzBuzzException with status code as 401
@ExceptionHandler(value = FizzBuzzException.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(FizzBuzzException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.UNAUTHORIZED);
}
// Handle FooBarException with status code as 403
@ExceptionHandler(value = FooBarException.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(FooBarException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.FORBIDDEN);
}
// Handle OmgException with status code as 404
@ExceptionHandler(value = OmgException.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(OmgException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.NOT_FOUND);
}
// handle Spring MVC specific exceptions with status code 422
@ExceptionHandler(value = {NoSuchRequestHandlingMethodException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, ServletRequestBindingException.class,
ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, MethodArgumentNotValidException.class,
MissingServletRequestPartException.class, BindException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class})
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.UNPROCESSABLE_ENTITY);
}
// Handle rest of the exception(s) with status code as 500
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.INTERNAL_SERVER_ERROR);
}
private MyAppResponse buildResponse(Throwable t) {
MyAppResponse response = new MyAppResponse();
// supply value to response object
return response;
}
}
如果需要任何进一步的信息,请在评论中告知。
P.S.:Spring MVC 异常列表 reference
要减少@bond-java-bond 答案你不需要自己构建ResponseEntity
:
- 对每个
handleSomeException
方法使用@ResponseStatus
(例如@ResponseStatus(HttpStatus.UNAUTHORIZED)
) - Return 来自这些方法的自定义
MyAppResponse
但是如果每种异常都将以相同的方式处理(仅通过 HTTP 状态区分),我建议像这样减少 MyAppExceptionMapper
:
@ControllerAdvice
public class MyAppExceptionMapper {
private final Map<Class<?>, HttpStatus> map;
{
map = new HashMap<>();
map.put(FizzBuzzException.class, HttpStatus.UNAUTHORIZED);
map.put(FooBarException.class, HttpStatus.FORBIDDEN);
map.put(NoSuchRequestHandlingMethodException.class, HttpStatus.UNPROCESSABLE_ENTITY);
/* List Spring specific exceptions here as @bond-java-bond suggested */
map.put(Exception.class, HttpStatus.INTERNAL_SERVER_ERROR);
}
// Handle all exceptions
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception exception) {
MyAppResponse response = new MyAppResponse();
// Fill response with details
HttpStatus status = map.get(exception.getClass());
if (status == null) {
status = map.get(Exception.class);// By default
}
return new ResponseEntity<>(response, status);
}
}
优点:
- 很短。
- 没有代码重复。
- 稍微更有效。
- 易于扩展。
此外,您可以将映射配置移到外部并注入它。
如何配置 MVC Dispatcher Servlet
首先,检查 mvc-dispatcher-servlet.xml
(或来自 web.xml
的另一个 contextConfigLocation
)是否包含:
<context:component-scan base-package="base.package"/>
<mvc:annotation-driven/>
其次,查看注解为class的@ControllerAdvice和注解为class的@Controller是否属于base.package
.
有关详细信息,请参阅 Exception Handling in Spring MVC or Spring MVC @ExceptionHandler Example 中的完整示例。