美文网首页Java架构技术进阶
关于Spring Boot如何处理全局异常?我来了不一样的“灵感

关于Spring Boot如何处理全局异常?我来了不一样的“灵感

作者: 代码小当家 | 来源:发表于2020-02-25 22:27 被阅读0次

推荐阅读:

47天时间,洒热血复习,我成功“挤进”了字节跳动(附Java面试题+学习笔记+算法刷题)​zhuanlan.zhihu.com

图标 面试“阿里云”居然一面就惨被吊打?幸终得内推机会,4面喜提华为offer​zhuanlan.zhihu.com 图标

背景

日常开发中经常会遇到各种系统异常(Exception),但是将异常堆栈直接返回给页面却是很不友好的一种做法,于是在我们日常的项目开发中,个人思考总结了一种自认为还不错的全局异常处理方案义工参考,欢迎指点意见和支持。

基本思路

这个方案主要用到了深度优先的递归循环、策略模式和基于枚举的单例模式,针对每种不同类型异常单独实现异常处理逻辑,实际中大多也是如此。

异常在抛出的时候有两种情况:一是没有子异常(getCause() == null),另一种是有子异常。深度优先指的是优先以抛出异常的源头为准进行处理,比如项目内部抛出了一个NullPointerException,然后外层catch块捕获后重新包装成IllegalArgumentException,那么NPE就是“异常的源头”,那么最终就是处理NPE后返回给前端。

策略模式只的是针对每种异常单独注册处理逻辑,放到HashMap里,处理的时候直接用getClass()获取对应的逻辑处理并返回即可。

具体实现

  1. 首先定义一个异常处理器接口用来标记处理Exception:
public interface ExceptionMessageConverter<T extends Throwable> {    /**     * 对返回值类型不需要什么要求     * 返回 ModelAndView 或者普通 POJO 都可以     * SpringMVC 有完善的处理机制,返回 json 或 View     */    Object onThrow(        HttpServletRequest request,        HttpServletResponse response,        T exception    );}
  1. 异常处理器注册器:ExceptionRegistry
public final class ExceptionRegistry {    // 这是项目启动就会写入的,没必要用 ConcurrentHashMap    final statis Map<Class, ExceptionMessageConverter> cached        = new HashMap<>();        public static void registry(        Class<? extends Throwable> type,         ExceptionMessageConverter converter    ) { cached.put(type, converter); }        public static ExceptionMessageConverter get(Class type){        return cached.get(type);    }复制代码
  1. 预定义异常处理器,这里用到了基于枚举的单例模式
public enum PredefinitionExceptionConverterEnum     implements ExceptionMessageConverter {        onBindException(BindException.class) {        @Override        public Object onThrow(            HttpServletRequest request,            HttpServletResponse response,            Throwable exception        ) {            // ... 自定义处理逻辑            return new ResponseEntity("长度至少为 6", HttpStatus.BAD_REQUEST);        }    },    onConstraintViolationException(ConstraintViolationException.class) {        // 省略实现...    },    /*     ... 自定义更多处理逻辑     */    ;        PredefinitionExceptionConverterEnum(Class type) {        // 在构造器里面向 ExceptionRegistry 注册        ExceptionRegistry.registry(type, this);    }}
  1. 异常处理器:GlobalExceptionAdvice
@RestControllerAdvicepublic class GlobalExceptionAdvice {        public static Object doThrowable(        HttpServletRequest request,         HttpServletResponse response,        Throwable root,        Throwable ex    ) {        Throwable current = ex, cause;        ExceptionMessageConverter converter = null;        if ((cause = current.getCause()) != null) {            // 如果有子异常优先以子异常为准            Object result = onThrowable(request, response, root, cause);            if (result != null) {                return result            }        }        if ((converter = ExceptionRegistry.get(current.getClass())) != null) {            // 如果子异常不存在处理器,就处理当前异常            return handler.onThrowable(request, response, current);        }        return null;    }        /**     * 捕获并交给 doThrowable 处理异常     * 注意这里的返回值是 ResponseEntity,     * 这是我们项目的实现,效果类似 @ResponseBody     * 返回的 content-type 是 application/json     */    @ExceptionHandler(Throwable.class)    public ResponseEntity onThrowable(        HttpServletRequest, request,         HttpServletResponse response,        Throwable ex    ) throws Throwable {        Object result = doThrowable(ex, ex);        if (result == null) {            // 如果一个匹配的处理器都没有,走这个            return new ResponseEntity("系统内部异常",                HttpStauts.INTERNAL_SERVER_ERROR);        }        return result;    }}

后记

至此,一个自定义全局异常处理就完成了,但是还有些想要说明的地方

  • ExceptionRegistry 及所有方法之所以定义成public是为了在PredefinitionExceptionConverterEnum之外仍然可以自定义和注册其他处理器;
  • 我们项目没有用到其他项目通常都会定义的ResultBody里面包括data、status、errorCode等返回体,因为我们项目都尽可能用http状态码返回,也可能是这个项目不够大,还用不着其他状态码,总之,这基础上目前是够用的,以及将来如果要扩展自定义状态码也和前端预定好方案了,前后端架构层面已经预留有扩展空间。
  • 上面列出各种看似牛哄哄的名词各位不要太在意哈,我只是实现完这个处理策略后,这个区归纳的它,错误还请斧正!!!

罗列一部分常见的异常类型:

  1. BindException: Spring MVC 绑定数据时通过@NotBlank相关注解引发的表单验证错误;
  2. ConstraintViolationException: 这个还是参数验证错误;
  3. MissingServletRequestParameterException:这个是请求参数里缺少某些参数;
  4. MethodArgumentNotValidException:这个是被@RequestBody和@Valid注解的验证错误
  5. TransientObjectException:这个是hibernate级联保存的异常(我们项目用得是jpa),一个实体在保存时所依赖的其他实体应该被持久化,否则就抛这个异常。
  6. EntityNotFoundException:顾名思义,实体不存在,还是jpa的异常;
  7. SQLSyntaxErrorException:sql 语法错误;
  8. SQLIntegrityConstraintViolationException:外键关联错误;
  9. MySQLTransactionRollbackException:顾名思义,MySQL事务回滚错误;
  10. SQLException:不解释
  11. MalformedURLException:URL 格式错误,网络请求时引发。

作者:蜜汁微笑
链接:https://juejin.im/post/5e54b698f265da57663fd41d
来源:掘金

相关文章

网友评论

    本文标题:关于Spring Boot如何处理全局异常?我来了不一样的“灵感

    本文链接:https://www.haomeiwen.com/subject/myxcchtx.html