servlet在处理过程中出入出现异常,会返回一个500错误页面。在RESTful接口中,有些会采用特定的4xx,5xx,6xx的Status code,同时在消息body中给出一些说明。Spring提供了处理过程中出现异常的返回消息的处理方式。

@ResponseStatus:自定义异常的返回Status code

Spring通过@ResponseStatus,对特定异常指定应答的status code。小例子中,我们随意选择一个另一个status code(417)作为演示范例。在实际应用中,比较常用的场景有:

  1. 如果resource资源不存在(如/services/Rest/account/10不存在),通常会返回404。
  2. 如果突破限制(调用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();
}

Java for Web学习笔记(八二):RESTful(2)返回错误信息_REST

@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");
}

Java for Web学习笔记(八二):RESTful(2)返回错误信息_自定义_02

@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相关文章