Spring MVC 注解之 @ControllerAdvice 实现全局异常处理

通常会在 @Controller 注解作用的类的方法上使用@ExceptionHandler,@ModelAttribute,@InitBinder来处理异常或初始化绑定,这三个注解对所用使用了 @RequestMapping 注解的控制器内的方法有效。

如果希望此类方法在全局范围内(跨控制器)应用,则可以在带有 @ControllerAdvice@RestControllerAdvice 注解的类中声明它们这三个注解。

@ControllerAdvice用于声明一个 控制器 建言,作用在类上,可以将控制器的全局配置统一放置在该注解作用的类里,结合在方法上使用 @ExceptionHandler 注解,就能实现全局的异常控制。

Controller Advice

@ControllerAdvice

@ControllerAdvice 组合了 @Component 注解,会自动将注释的类注册为 Spring 的 Bean。

默认情况下,@ControllerAdvice 方法对所有请求起效(即所有控制器),但可以通过属性设置指定控制器。如下示例

1
2
3
4
5
6
7
8
9
10
11
//所有使用了 @RestController 注解的控制器
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

//指定包里的所有控制器
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

//指定具体类型的控制器
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

注:上面的示例中的选择器在运行时进行评估,如果广泛使用,可能会对性能产生负面影响。

@RestControllerAdvice

此注解包含了 @ControllerAdvice@ResponseBody ,作用在 RestController 类上,@ExceptionHandler 注解的方法采用 @ResponseBody 语义,即通过消息转换将内容呈现给响应主体。

@ModelAttribute

本来作用是绑定键值对到 Model,在 @ControllerAdvice 注解的类的方法中使用,是将键值对添加到全局,所用使用@RequestMapping注解的方法都能获取到该键值对。

@InitBinder

用来设置 WebDataBinder, WebDataBinder 用来自动绑定前台请求参数到Model中。

@ExceptionHandler

@Controller and @ControllerAdvice 注解的类可以使用 @ExceptionHandler 注解来处理异常,@ExceptionHandler 注解作用在方法上。

@ExceptionHandler 只有一个属性值,是继承自顶级异常类 Throwable 的异常类 Class 对象数组。

1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {

/**
* 如果值为空,则默认为匹配所有异常
*/
Class<? extends Throwable>[] value() default {};

}

使用:

1
2
3
4
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}

在响应主体中使用错误详细信息实现全局异常处理的应用程序应考虑扩展 ResponseEntityExceptionHandler,它提供了Spring MVC 引发的异常的处理并提供了自定义响应主体的钩子。

要使用此功能,创建 ResponseEntityExceptionHandler 的子类,并使用 @ControllerAdvice 对其进行注释,重写必要的方法,并将其声明为 Spring bean。

全局异常使用

@ControllerAdvice 示例

  1. 定义控制器 Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Controller
    @RequestMapping("/user")
    public class UserController {

    @RequestMapping("/get")
    public String getUser(Integer id) {
    throw new ClassNotFoundException("没有找到类");
    }
    }
  2. 控制器全局处理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
    * @ControllerAdvice:声明一个控制建言,该注解组合了@Component注解,自动注册为Spring的Bean
    * @ExceptionHandler:定义全局处理,根据value属性过滤拦截条件,下例拦截所有的Exception
    * @ModelAttribute:将model健值添加到全局,所有@RequestMapping注解的方法可获得此值
    * @InitBinder:定制WebDataBinder
    *
    */

    @ControllerAdvice
    public class ControllerAdviceConfig {

    @ExceptionHandler(value = Exception.class)
    public ModelAndView exception(Exception exception, WebRequest request) {
    ModelAndView mv = new ModelAndView("error");
    mv.addObject("errorMessage", exception.getMessage());
    return mv;
    }

    @ModelAttribute
    public void addAttributes(Model model) {
    model.addAttribute("msg", "错误信息");
    }

    /**
    * 忽略request参数的id
    * @param webDataBinder
    */
    @InitBinder
    public void initBinder(WebDataBinder webDataBinder) {
    webDataBinder.setDisallowedFields("id");
    }
    }

    @RestControllerAdvice 示例

  3. 定义控制器 Controller,同上。

  4. 定义全局处理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RestControllerAdvice
    public class GlobalException {

    private static final Logger logger = LoggerFactory.getLogger(GlobalException.class);

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(value = Exception.class)
    public ResponseVO globalException(Exception ex) {
    logger.error("系统异常,", ex);
    return new ResponseVO(-1, null != ex.getMessage() ? ex.getMessage() : ex.toString());
    }
    }

    :上例中的 ResponseVO 类是一个自定义的响应体,通常包含 msg,code,state,data 属性。

  5. 自定义业务异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class BSException extends RuntimeException {
    private static final long serialVersionUID = -1243366730551644726L;

    public BSException(String error) {
    super(error);
    }

    public BSException(String error, Throwable e) {
    super(error, e);
    }
    }

参考资料

  1. Spring Framework Documentation 5.2.1 Release
  2. Spring Web MVC -> Controller Advice

Spring MVC 注解之 @ControllerAdvice 实现全局异常处理

http://blog.gxitsky.com/2018/03/31/SpringMVC-01-controllerAdvice/

作者

光星

发布于

2018-03-31

更新于

2022-06-17

许可协议

评论