思想很重要

统一异常处理实现方式:使用注解@RestContrllerAdvice,@ExceptionHandler

先想明白一个问题:定义统一异常处理类之后,是不是在Contrller中就不用捕获异常了。如果出现异常,都会被统一异常处理类处理掉。还是统一异常处理类,只是Controer捕获异常补充,Contrller捕获不到了,它来填补漏洞。

下面转载的一篇文章很不错

http://blog.csdn.net/kinginblue/article/details/70186586

关键词:业务异常,未知异常,参数异常(注解方式),Controller分离(统一处理Controller只负责处理异常,原来Controller负责处理业务逻辑)

另外关于统一异常处理的其他思路:

1,http://blog.csdn.net/nmgrd/article/details/57080248  定义统一处理异常类,普通Controller用所有普通Controller继承统一异常处理类。

2,Aop实现异常处理  

https://www.cnblogs.com/ssslinppp/p/7606038.html

@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

2017-04-15 20:12             7794人阅读             评论(6)             收藏              举报        

零、前言

对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚。

如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端。但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护,特别是还需要对 Service 层的不同异常进行不同处理的时候。例如以下 Controller 方法代码(非常难看且冗余):

  1. /**
  2. * 手动处理 Service 层异常和数据校验异常的示例
  3. * @param dog
  4. * @param errors
  5. * @return
  6. */
  7. @PostMapping(value = "")
  8. AppResponse add(@Validated(Add.class) @RequestBody Dog dog, Errors errors){
  9. AppResponse resp = new AppResponse();
  10. try {
  11. // 数据校验
  12. BSUtil.controllerValidate(errors);
  13. // 执行业务
  14. Dog newDog = dogService.save(dog);
  15. // 返回数据
  16. resp.setData(newDog);
  17. }catch (BusinessException e){
  18. LOGGER.error(e.getMessage(), e);
  19. resp.setFail(e.getMessage());
  20. }catch (Exception e){
  21. LOGGER.error(e.getMessage(), e);
  22. resp.setFail("操作失败!");
  23. }
  24. return resp;
  25. }

本文讲解使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,只要设计得当,就再也不用在 Controller 层进行 try-catch 了!而且,@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了!

一、优缺点

  • 优点:将 Controller 层的异常和数据校验的异常进行统一处理,减少模板代码,减少编码量,提升扩展性和可维护性。
  • 缺点:只能处理 Controller 层未捕获(往外抛)的异常,对于 Interceptor(拦截器)层的异常,Spring 框架层的异常,就无能为力了。

二、基本使用示例

2.1 @ControllerAdvice 注解定义全局异常处理类

  1. @ControllerAdvice
  2. public class GlobalExceptionHandler {
  3. }
  • 1
  • 2
  • 3

请确保此 GlobalExceptionHandler 类能被扫描到并装载进 Spring 容器中。

2.2 @ExceptionHandler 注解声明异常处理方法

  1. @ControllerAdvice
  2. public class GlobalExceptionHandler {
  3. @ExceptionHandler(Exception.class)
  4. @ResponseBody
  5. String handleException(){
  6. return "Exception Deal!";
  7. }
  8. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

方法 handleException() 就会处理所有 Controller 层抛出的 Exception 及其子类的异常,这是最基本的用法了。

@ExceptionHandler 注解的方法的参数列表里,还可以声明很多种类型的参数,详见文档。其原型如下:

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface ExceptionHandler {
  5. /**
  6. * Exceptions handled by the annotated method. If empty, will default to any
  7. * exceptions listed in the method argument list.
  8. */
  9. Class<? extends Throwable>[] value() default {};
  10. }

如果 @ExceptionHandler 注解中未声明要处理的异常类型,则默认为参数列表中的异常类型。所以上面的写法,还可以写成这样:

  1. @ControllerAdvice
  2. public class GlobalExceptionHandler {
  3. @ExceptionHandler()
  4. @ResponseBody
  5. String handleException(Exception e){
  6. return "Exception Deal! " + e.getMessage();
  7. }
  8. }

参数对象就是 Controller 层抛出的异常对象!

三、处理 Service 层上抛的业务异常

有时我们会在复杂的带有数据库事务的业务中,当出现不和预期的数据时,直接抛出封装后的业务级运行时异常,进行数据库事务回滚,并希望该异常信息能被返回显示给用户。

3.1 代码示例

封装的业务异常类:

  1. public class BusinessException extends RuntimeException {
  2. public BusinessException(String message){
  3. super(message);
  4. }
  5. }

Service 实现类:

  1. @Service
  2. public class DogService {
  3. @Transactional
  4. public Dog update(Dog dog){
  5. // some database options
  6. // 模拟狗狗新名字与其他狗狗的名字冲突
  7. BSUtil.isTrue(false, "狗狗名字已经被使用了...");
  8. // update database dog info
  9. return dog;
  10. }
  11. }

其中辅助工具类 BSUtil

  1. public static void isTrue(boolean expression, String error){
  2. if(!expression) {
  3. throw new BusinessException(error);
  4. }
  5. }

那么,我们应该在 GlobalExceptionHandler 类中声明该业务异常类,并进行相应的处理,然后返回给用户。更贴近真实项目的代码,应该长这样子:

  1. /**
  2. * Created by kinginblue on 2017/4/10.
  3. * @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
  4. */
  5. @ControllerAdvice
  6. public class GlobalExceptionHandler {
  7. private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
  8. /**
  9. * 处理所有不可知的异常
  10. * @param e
  11. * @return
  12. */
  13. @ExceptionHandler(Exception.class)
  14. @ResponseBody
  15. AppResponse handleException(Exception e){
  16. LOGGER.error(e.getMessage(), e);
  17. AppResponse response = new AppResponse();
  18. response.setFail("操作失败!");
  19. return response;
  20. }
  21. /**
  22. * 处理所有业务异常
  23. * @param e
  24. * @return
  25. */
  26. @ExceptionHandler(BusinessException.class)
  27. @ResponseBody
  28. AppResponse handleBusinessException(BusinessException e){
  29. LOGGER.error(e.getMessage(), e);
  30. AppResponse response = new AppResponse();
  31. response.setFail(e.getMessage());
  32. return response;
  33. }
  34. }

Controller 层的代码,就不需要进行异常处理了:

  1. @RestController
  2. @RequestMapping(value = "/dogs", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
  3. public class DogController {
  4. @Autowired
  5. private DogService dogService;
  6. @PatchMapping(value = "")
  7. Dog update(@Validated(Update.class) @RequestBody Dog dog){
  8. return dogService.update(dog);
  9. }
  10. }

3.2 代码说明

Logger 进行所有的异常日志记录。

@ExceptionHandler(BusinessException.class) 声明了对 BusinessException 业务异常的处理,并获取该业务异常中的错误提示,构造后返回给客户端。

@ExceptionHandler(Exception.class) 声明了对 Exception 异常的处理,起到兜底作用,不管 Controller 层执行的代码出现了什么未能考虑到的异常,都返回统一的错误提示给客户端。

备注:以上 GlobalExceptionHandler 只是返回 Json 给客户端,更大的发挥空间需要按需求情况来做。

四、处理 Controller 数据绑定、数据校验的异常

在 Dog 类中的字段上的注解数据校验规则:

  1. @Data
  2. public class Dog {
  3. @NotNull(message = "{Dog.id.non}", groups = {Update.class})
  4. @Min(value = 1, message = "{Dog.age.lt1}", groups = {Update.class})
  5. private Long id;
  6. @NotBlank(message = "{Dog.name.non}", groups = {Add.class, Update.class})
  7. private String name;
  8. @Min(value = 1, message = "{Dog.age.lt1}", groups = {Add.class, Update.class})
  9. private Integer age;
  10. }
  1. 说明:@NotNull@Min@NotBlank 这些注解的使用方法,不在本文范围内。如果不熟悉,请查找资料学习即可。
  2. 其他说明:
  3. @Data 注解是 **Lombok** 项目的注解,可以使我们不用再在代码里手动加 getter & setter
  4. Eclipse IntelliJ IDEA 中使用时,还需要安装相关插件,这个步骤自行Google/Baidu 吧!

Lombok 使用方法见:Java奇淫巧技之Lombok

SpringMVC 中对于 RESTFUL 的 Json 接口来说,数据绑定和校验,是这样的:

  1. /**
  2. * 使用 GlobalExceptionHandler 全局处理 Controller 层异常的示例
  3. * @param dog
  4. * @return
  5. */
  6. @PatchMapping(value = "")
  7. AppResponse update(@Validated(Update.class) @RequestBody Dog dog){
  8. AppResponse resp = new AppResponse();
  9. // 执行业务
  10. Dog newDog = dogService.update(dog);
  11. // 返回数据
  12. resp.setData(newDog);
  13. return resp;
  14. }

使用 @Validated + @RequestBody 注解实现。

当使用了 @Validated + @RequestBody 注解但是没有在绑定的数据对象后面跟上 Errors 类型的参数声明的话,Spring MVC 框架会抛出 MethodArgumentNotValidException 异常。

所以,在 GlobalExceptionHandler 中加上对 MethodArgumentNotValidException 异常的声明和处理,就可以全局处理数据校验的异常了!加完后的代码如下:

  1. /**
  2. * Created by kinginblue on 2017/4/10.
  3. * @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
  4. */
  5. @ControllerAdvice
  6. public class GlobalExceptionHandler {
  7. private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
  8. /**
  9. * 处理所有不可知的异常
  10. * @param e
  11. * @return
  12. */
  13. @ExceptionHandler(Exception.class)
  14. @ResponseBody
  15. AppResponse handleException(Exception e){
  16. LOGGER.error(e.getMessage(), e);
  17. AppResponse response = new AppResponse();
  18. response.setFail("操作失败!");
  19. return response;
  20. }
  21. /**
  22. * 处理所有业务异常
  23. * @param e
  24. * @return
  25. */
  26. @ExceptionHandler(BusinessException.class)
  27. @ResponseBody
  28. AppResponse handleBusinessException(BusinessException e){
  29. LOGGER.error(e.getMessage(), e);
  30. AppResponse response = new AppResponse();
  31. response.setFail(e.getMessage());
  32. return response;
  33. }
  34. /**
  35. * 处理所有接口数据验证异常
  36. * @param e
  37. * @return
  38. */
  39. @ExceptionHandler(MethodArgumentNotValidException.class)
  40. @ResponseBody
  41. AppResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
  42. LOGGER.error(e.getMessage(), e);
  43. AppResponse response = new AppResponse();
  44. response.setFail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
  45. return response;
  46. }
  47. }

注意到了吗,所有的 Controller 层的异常的日志记录,都是在这个 GlobalExceptionHandler 中进行记录。也就是说,Controller 层也不需要在手动记录错误日志了。

五、总结

本文主要讲 @ControllerAdvice + @ExceptionHandler 组合进行的 Controller 层上抛的异常全局统一处理。

其实,被 @ExceptionHandler 注解的方法还可以声明很多参数,详见文档。

@ControllerAdvice 也还可以结合 @InitBinder、@ModelAttribute 等注解一起使用,应用在所有被 @RequestMapping 注解的方法上,详见搜索引擎。

六、附录

本文示例代码已放到 Github

统一异常处理@RestContrllerAdvice,@ExceptionHandler(转载)的更多相关文章

  1. 使用Spring MVC统一异常处理实战(转载)

    原文地址:http://blog.csdn.net/ufo2910628/article/details/40399539 种方式: (1)使用Spring MVC提供的简单异常处理器SimpleMa ...

  2. 【统一异常处理】@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

    1.利用springmvc注解对Controller层异常全局处理 对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service ...

  3. Spring Boot统一异常处理实践

    摘要: SpringBoot异常处理. 原文:Spring MVC/Boot 统一异常处理最佳实践 作者:赵俊 前言 在 Web 开发中, 我们经常会需要处理各种异常, 这是一件棘手的事情, 对于很多 ...

  4. Struts2、Spring MVC4 框架下的ajax统一异常处理

    本文算是struts2 异常处理3板斧.spring mvc4:异常处理 后续篇章,普通页面出错后可以跳到统一的错误处理页面,但是ajax就不行了,ajax的本意就是不让当前页面发生跳转,仅局部刷新, ...

  5. 160926、Java-SpringMVC统一异常处理

    从零开始学 Java - Spring MVC 统一异常处理 看到 Exception 这个单词都心慌 如果有一天你发现好久没有看到Exception这个单词了,那你会不会想念她?我是不会的.她如女孩 ...

  6. 使用Spring MVC统一异常处理实战

    1 描述 在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的.不可预知的异常需要处理.每个过程都单独处理异常,系统的代码耦合 ...

  7. Spring MVC 统一异常处理

    Spring MVC 统一异常处理 看到 Exception 这个单词都心慌 如果有一天你发现好久没有看到Exception这个单词了,那你会不会想念她?我是不会的.她如女孩一样的令人心动又心慌,又或 ...

  8. spring boot / cloud (二) 规范响应格式以及统一异常处理

    spring boot / cloud (二) 规范响应格式以及统一异常处理 前言 为什么规范响应格式? 我认为,采用预先约定好的数据格式,将返回数据(无论是正常的还是异常的)规范起来,有助于提高团队 ...

  9. springMVC统一异常处理

    Spring MVC处理异常有3种方式: 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver: 实现Spring的异常处理接口HandlerExc ...

随机推荐

  1. MarkChanges: Jmeter

    1. 20180627 调整启动的内存set HEAP=-Xms1024m -Xmx1024m2. 20180627 调整输出格式为xml #jmeter.save.saveservice.outpu ...

  2. Java8 新特性之默认接口方法

    摘要: 从java8开始,接口不只是一个只能声明方法的地方,我们还可以在声明方法时,给方法一个默认的实现,我们称之为默认接口方法,这样所有实现该接口的子类都可以持有该方法的默认实现. · 待定 一. ...

  3. robot脚本编写规范

    一个robot脚本主要有四部分组成: ***settings*** 设置 ***keywords*** 关键词 ***variables*** 变量 ***test cases*** 测试用例 一般, ...

  4. 手把手教你开发jquery插件(三)

    First, i want to add options to Tabs constructor like this: var tabs = $("div.tabs").tabs( ...

  5. 16 Managing Undo

    16 Managing Undo 从Oracle11g开始,在默认安装中oracle会自动管理undo, 典型安装中不需要DBA介入配置,然而,如果选择了flash back特性,你就需要进行一些un ...

  6. English trip V1 - 4.Do you have it? Teacher:Patrick Key: have - has doesn't have

    In this lesson you will learn to describe what you have. STARTER Do you have a ...?  # 你有...吗? car b ...

  7. 12月15日 session:Ruby on Rails Security Guide//从第3节开始没有学习//关于find_by 和where的区别用法思考。

    http://guides.rubyonrails.org/security.html#user-management 2.session笔记见13日的随笔. http://www.cnblogs.c ...

  8. 3-18 关于namespace,双冒号::的用法; SelfYield.

    关于namespace,双冒号::的用法. 防止引用多个模块在一个文件/类中,有重名的对象.::可以调用类的类方法,和常量. class Foo   BAR = "hello"   ...

  9. Perfect Groups CodeForces - 980D

    链接 题目大意: 定义一个问题: 求集合$S$的最小划分数,使得每个划分内任意两个元素积均为完全平方数. 给定$n$元素序列$a$, 对$a$的所有子区间, 求出上述问题的结果, 最后要求输出所有结果 ...

  10. HDOJ1001

    #include<iostream> using namespace std; int main() { long long n; while(cin >> n) { cout ...