2. 统一异常处理
给当前的类上加 @ControllerAdvice
表示控制器通知类
给方法上添加 @ExceptionHandler(xxx.class)
,表示异常处理器,添加异常返回的业务代码
@RestController @RequestMapping("/user") public class UserController { @RequestMapping("/index") public String index() { int num = 10/0; return "Hello Index"; } }
在 config 包中,创建 MyExceptionAdvice
类
@RestControllerAdvice // 当前是针对 Controller 的通知类(增强类) public class MyExceptionAdvice { @ExceptionHandler(ArithmeticException.class) public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) { HashMap<String, Object> result = new HashMap<>(); result.put("state",-1); result.put("data",null); result.put("msg" , "算出异常:"+ e.getMessage()); return result; } }
也可以这样写,效果是一样的
@ControllerAdvice public class MyExceptionAdvice { @ExceptionHandler(ArithmeticException.class) @ResponseBody public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) { HashMap<String, Object> result = new HashMap<>(); result.put("state",-1); result.put("data",null); result.put("msg" , "算数异常:"+ e.getMessage()); return result; } }
如果再有一个空指针异常,那么上面的代码是不行的,还要写一个针对空指针异常处理器
@ExceptionHandler(NullPointerException.class) public HashMap<String,Object> nullPointerExceptionAdvice(NullPointerException e) { HashMap<String, Object> result = new HashMap<>(); result.put("state",-1); result.put("data",null); result.put("msg" , "空指针异常异常:"+ e.getMessage()); return result; } @RequestMapping("/index") public String index(HttpServletRequest request,String username, String password) { Object obj = null; System.out.println(obj.hashCode()); return "Hello Index"; }
但是需要考虑的一点是,如果每个异常都这样写,那么工作量是非常大的,并且还有自定义异常,所以上面这样写肯定是不好的,既然是异常直接写 Exception 就好了,它是所有异常的父类,如果遇到不是前面写的两种异常,那么就会直接匹配到 Exception
当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配
@ExceptionHandler(Exception.class) public HashMap<String,Object> exceptionAdvice(Exception e) { HashMap<String, Object> result = new HashMap<>(); result.put("state",-1); result.put("data",null); result.put("msg" , "异常:"+ e.getMessage()); return result; }
可以看到优先匹配的还是前面写的 空指针异常
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
3. 统一数据格式返回
3.1 统一数据格式返回的实现
1.给当前类添加 @ControllerAdvice
2.实现 ResponseBodyAdvice
重写其方法
supports
方法,此方法表示内容是否需要重写(通过此⽅法可以选择性部分控制器和方法进行重写),如果要重写返回 truebeforeBodyWrite
方法,方法返回之前调用此方法
@ControllerAdvice public class MyResponseAdvice implements ResponseBodyAdvice { // 返回一个 boolean 值,true 表示返回数据之前对数据进行重写,也就是会进入 beforeBodyWrite 方法 // 返回 false 表示对结果不进行任何处理,直接返回 @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } // 方法返回之前调用此方法 @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { HashMap<String,Object> result = new HashMap<>(); result.put("state",1); result.put("data",body); result.put("msg",""); return result; } } @RestController @RequestMapping("/user") public class UserController { @RequestMapping("/login") public boolean login(HttpServletRequest request,String username, String password) { boolean result = false; if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) { if(username.equals("admin") && password.equals("admin")) { HttpSession session = request.getSession(); session.setAttribute("userinfo","userinfo"); return true; } } return result; } @RequestMapping("/reg") public int reg() { return 1; } }
3.2 @ControllerAdvice 源码分析
通过对 @ControllerAdvice
源码的分析我们可以知道上面统一异常和统一数据返回的执行流程
(1)先看 @ControllerAdvice 源码
可以看到 @ControllerAdvice
派生于 @Component
组件而所有组件初始化都会调用 InitializingBean
接口
(2)下面查看 initializingBean 有哪些实现类
在查询过程中发现,其中 Spring MVC 中的实现子类是 RequestMappingHandlerAdapter
,它里面有一个方法 afterPropertiesSet()
方法,表示所有的参数设置完成之后执行的方法
(3)而这个方法中有一个 initControllerAdviceCache 方法,查询此方法
发现这个方法在执行时会查找使用所有的 @ControllerAdvice
类,发送某个事件时,调用相应的 Advice 方法,比如返回数据前调用统一数据封装,比如发生异常是调用异常的 Advice 方法实现的