关于SpringBoot如何处理全局异常的思考

背景

日常开发中经常会遇到各种系统异常(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

    );

}

2.异常处理器注册器: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);

    }

}

3.预定义异常处理器,这里用到了基于枚举的单例模式

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);

    }

}

4.异常处理器:GlobalExceptionAdvice

@RestControllerAdvice

public 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状态码返回,也可能是这个项目不够大,还用不着其他状态码,总之,这基础上目前是够用的,以及将来如果要扩展自定义状态码也和前端预定好方案了,前后端架构层面已经预留有扩展空间。

上面列出各种看似牛哄哄的名词各位不要太在意哈,我只是实现完这个处理策略后,这个区归纳的它,错误还请斧正!!!

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

BindException: Spring MVC 绑定数据时通过@NotBlank相关注解引发的表单验证错误;

ConstraintViolationException: 这个还是参数验证错误;

MissingServletRequestParameterException:这个是请求参数里缺少某些参数;

MethodArgumentNotValidException:这个是被@RequestBody和@Valid注解的验证错误

TransientObjectException:这个是hibernate级联保存的异常(我们项目用得是jpa),一个实体在保存时所依赖的其他实体应该被持久化,否则就抛这个异常。

EntityNotFoundException:顾名思义,实体不存在,还是jpa的异常;

SQLSyntaxErrorException:sql 语法错误;

SQLIntegrityConstraintViolationException:外键关联错误;

MySQLTransactionRollbackException:顾名思义,MySQL事务回滚错误;

SQLException:不解释

MalformedURLException:URL 格式错误,网络请求时引发。

最后:

上面都是自己整理好的!我就把资料贡献出来给有需要的人!顺便求一波关注,哈哈~各位小伙伴关注我后私信【Java】就可以免费领取哒

作者:蜜汁微笑

链接:https://juejin.im/post/5e54b698f265da57663fd41d

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容

  • 自己备忘,随便写 android网络框架源码解析及对比 android常用网络框架对比 Volley: 特点 基于...
    幻海流心阅读 1,460评论 0 4
  • 我的项目没有用到maven 而是用到了一个叫gradle的管理工具 Gradle是一个基于JVM的构建工具,是一款...
    setsun阅读 3,493评论 0 0
  • RPC框架远程调用的实现方式在原理上是比较简单的,即将调用的方法(接口名、方法名、参数类型、参数)序列化之后发送到...
    谜碌小孩阅读 3,103评论 0 13
  • 一. Java基础部分.................................................
    wy_sure阅读 3,811评论 0 11
  • 做了一个上拉的效果 然后想想是不是可以完了再往下拉。。。。然后发现是可以的。。。。 完成的效果也就是。。。。往上一...
    切糕大魔王阅读 1,448评论 0 2