servlet在处理过程中出入出现异常,会返回一个500错误页面。在RESTful接口中,有些会采用特定的4xx,5xx,6xx的Status code,同时在消息body中给出一些说明。Spring提供了处理过程中出现异常的返回消息的处理方式。
@ResponseStatus:自定义异常的返回Status code
Spring通过@ResponseStatus,对特定异常指定应答的status code。小例子中,我们随意选择一个另一个status code(417)作为演示范例。在实际应用中,比较常用的场景有:
- 如果resource资源不存在(如/services/Rest/account/10不存在),通常会返回404。
- 如果突破限制(调用sevice方法时抛出ConstraintViolationException),通常会返回400。
//自定义个一个异常,当controller的处理中出现这个异常,将发送HttpStatus.EXPECTATION_FAILED(417)消息
@ResponseStatus(HttpStatus.EXPECTATION_FAILED)
public class MyTestException extends RuntimeException{
private static final long serialVersionUID = 1L;
}
我们在一个controller中验证一下:
@RequestMapping(value = "abc",method = {RequestMethod.GET})
public void abc(){
throw new MyTestException();
}
@ExceptionHandler:处理Exception的方法
@ResponseStatus仍不方便,一来我们不可能将所有可能的Exception都一一列出,另一方面,在RESTful接口中,通常需要在响应的body中给出json格式或者xml格式,而不是标准容器的HTML格式页面。我们可以通过具体处理@ExceptionHandler来标记具体如何处理Exception。
@ExceptionHandler({ ConstraintViolationException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ResultData handleBeanValidationError(ConstraintViolationException e) {
return new ResultData(-1,"param is error (" + e.getMessage() + ")");
}
//同样,我们提供一个简单的测试例子:
@RequestMapping(value = "account/{id}",method = {RequestMethod.GET})
@ResponseBody
public ResultData test(@PathVariable("id") long id) {
accountService.getAccount(id); //在service的方法中,要求@Min(value = 1) long id
return new ResultData(id,"OK");
}
@ControllerAdvice
@ExceptionHandler的方式仍有不便。这种方式将需要在每个Controller中编写。当然我们可以写一个BaseController,然后继承它,但这导致无法集成其他的(如果需要)。Spring通过@ControllerAdvice来解决这个问题,@ControllerAdvice相当于被所有标记@Controller的建议,即均有效。
在@ControllerAdvice标记的类中,我们可以对方法使用@ExceptionHandler、@InitBinder和@ModelAttribute。对所有的controller有效。
定义专用于RESTful接口的ControllerAdvice标记
对于web和REST共存的项目,可以定义一个继承@ControllerAdvice,专用于REST的标记
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
public @interface RestEndpointAdvice {
String value() default "";
}
在Root上下文中,应该不扫描该标记
@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
@ComponentScan( basePackages = "cn.wei.chapter17.site",
excludeFilters = @ComponentScan.Filter({Controller.class,ControllerAdvice.class}))
public class RootContextConfiguration implements AsyncConfigurer,SchedulingConfigurer
在REST上下文中,要扫描该标记
@Configuration
@EnableWebMvc
@ComponentScan( basePackages = "cn.wei.chapter17.site",
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter({RestEndpoint.class,RestEndpointAdvice.class}))
public class RestServletContextConfiguration extends WebMvcConfigurerAdapter
最佳异常匹配
//【1】@ControllerAdvice或其继承者的标记。Spring采用最佳匹配的方式,进行异常匹配。
@RestEndpointAdvice
public class RestExceptionHandler {
//【例子1】处理特定的Exception,响应采用ResponseEntity<T>的方式。
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handle(ConstraintViolationException e){
ErrorResponse errors = new ErrorResponse();
for(ConstraintViolation<?> violation : e.getConstraintViolations()){
ErrorItem error = new ErrorItem();
error.setCode(violation.getMessageTemplate());
if(violation.getPropertyPath() == null)
error.setMessage(violation.getMessage());
else
error.setMessage(violation.getPropertyPath() + ": " + violation.getMessage());
errors.addError(error);
}
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
//【例子2】匹配多个Exception。
// ResourceNotFoundException是我们自定义的异常,用于处理资源没有找到,如果我们在@ControllerAdvice对这个异常进行了处理,将优先于在ResourceNotFoundException中定义的@ResponseStatus。
// NoHandlerFoundException是没有找到url匹配的情况,这缺省是自动处理的,但我们希望采用自定义的message body,需要在Bootstrap中设置:dispatcher.setInitParameter("throwExceptionIfNoHandlerFound", "true");将这个异常抛出,由我们的代码处理。
@ExceptionHandler({NoHandlerFoundException.class,ResourceNotFoundException.class})
public ResponseEntity<ErrorResponse> handleNotFound(Exception e){
ErrorResponse errors = new ErrorResponse();
ErrorItem error = new ErrorItem();
error.setCode(e.getClass().getSimpleName());
error.setMessage(e.getMessage());
errors.addError(error);
return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
}
//【例子3】缺省的处理。Spring采用最佳匹配的方式,我们可以设置一个Exception,作为缺省的处理
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handle(Exception e){
ErrorResponse errors = new ErrorResponse();
ErrorItem error = new ErrorItem();
error.setCode("Exception");
error.setMessage(e.getMessage());
errors.addError(error);
return new ResponseEntity<>(errors, HttpStatus.INTERNAL_SERVER_ERROR);
}
public static class ErrorItem{
private String code;
private String message;
//.... ....
}
@XmlRootElement(name = "errors")
public static class ErrorResponse{
private List<ErrorItem> errors = new ArrayList<>();
// ... ...
}
}
相关链接:
我的Professional Java for Web Applications相关文章