在spring中手写全局异常拦截器
为什么要重复造*
你可能会问,spring已经自带了全局异常拦截,为什么还要重复造*呢?
这是个好问题,我觉得有以下几个原因
- 装逼
- spring的全局异常拦截只是针对于spring mvc的接口,对于你的rpc接口就无能为力了
- 无法定制化
- 除了写业务代码,我们其实还能干点别的事
我觉得上述理由已经比较充分的解答了为什么要重复造*,接下来就来看一下怎么造*
造个什么样的*?
我觉得全局异常拦截应该有如下特性
- 使用方便,最好和spring原生的使用方式一致,降低学习成本
- 能够支持所有接口
- 调用异常处理器可预期,比如说定义了runtimeexception的处理器和exception的处理器,如果这个时候抛出nullpointexception,这时候要能没有歧义的选择预期的处理器
如何造*?
由于现在的应用基本上都是基于spring的,因此我也是基于springaop来实现全局异常拦截
首先先定义几个注解
@target(elementtype.type) @retention(retentionpolicy.runtime) @documented @component public @interface exceptionadvice { } @target(elementtype.method) @retention(retentionpolicy.runtime) @documented public @interface exceptionhandler { class<? extends throwable>[] value(); } @target(elementtype.method) @retention(retentionpolicy.runtime) @documented public @interface exceptionintercept { }
@exceptionadvice 的作用是标志定义异常处理器的类,方便找到异常处理器
@exceptionhandler 的作用是标记某个方法是处理异常的,里面的值是能够处理的异常类型
@exceptionintercept 的作用是标记需要异常拦截的方法
接下来定义统一返回格式,以便出现错误的时候统一返回
@data public class baseresponse<t> { private integer code; private string message; private t data; public baseresponse(integer code, string message) { this.code = code; this.message = message; } }
然后定义一个收集异常处理器的类
public class exceptionmethodpool { private list<exceptionmethod> methods; private object excutor; public exceptionmethodpool(object excutor) { this.methods = new arraylist<exceptionmethod>(); this.excutor = excutor; } public object getexcutor() { return excutor; } public void add(class<? extends throwable> clazz, method method) { methods.add(new exceptionmethod(clazz, method)); } //按序查找能够处理该异常的处理器 public method obtainmethod(throwable throwable) { return methods .stream() .filter(e -> e.getclazz().isassignablefrom(throwable.getclass())) .findfirst() .orelsethrow(() ->new runtimeexception("没有找到对应的异常处理器")) .getmethod(); } @allargsconstructor @getter class exceptionmethod { private class<? extends throwable> clazz; private method method; } }
exceptionmethod 里面有两个属性
- clazz:这个代表着能够处理的异常
- method:代表着处理异常调用的方法
exceptionmethodpool 里面按序存放所有异常处理器,excutor是执行这些异常处理器的对象
接下来把所有定义的异常处理器收集起来
@component public class exceptionbeanpostprocessor implements beanpostprocessor { private exceptionmethodpool exceptionmethodpool; @autowired private configurableapplicationcontext context; @override public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { class<?> clazz = bean.getclass(); exceptionadvice advice = clazz.getannotation(exceptionadvice.class); if (advice == null) return bean; if (exceptionmethodpool != null) throw new runtimeexception("不允许有两个异常定义类"); exceptionmethodpool = new exceptionmethodpool(bean); //保持处理异常方法顺序 arrays.stream(clazz.getdeclaredmethods()) .filter(method -> method.getannotation(exceptionhandler.class) != null) .foreach(method -> { exceptionhandler exceptionhandler = method.getannotation(exceptionhandler.class); arrays.stream(exceptionhandler.value()).foreach(c -> exceptionmethodpool.add(c,method)); }); //注册进spring容器 context.getbeanfactory().registersingleton("exceptionmethodpool",exceptionmethodpool); return bean; } }
exceptionbeanpostprocessor 通过实现beanpostprocessor 接口,在bean初始化之前,把所有异常处理器塞进 exceptionmethodpool,并把其注册进spring容器
然后定义异常处理器
@component public class exceptionprocessor { @autowired private exceptionmethodpool exceptionmethodpool; public baseresponse process(throwable e) { return (baseresponse) functionutil.computeorgetdefault(() ->{ method method = exceptionmethodpool.obtainmethod(e); method.setaccessible(true); return method.invoke(exceptionmethodpool.getexcutor(),e); },new baseresponse(0,"未知错误")); } }
这里应用了我自己通过函数式编程封装的一些语法糖,有兴趣的可以看下
最后通过aop进行拦截
@aspect @component public class exceptioninterceptaop { @autowired private exceptionprocessor exceptionprocessor; @pointcut("@annotation(com.example.exception.intercept.exceptionintercept)") public void pointcut() { } @around("pointcut()") public object around(proceedingjoinpoint point) { return computeanddealexception(() -> point.proceed(), e -> exceptionprocessor.process(e)); } public static <r> r computeanddealexception(throwexceptionsupplier<r> supplier, function<throwable, r> dealfunc) { try { return supplier.get(); } catch (throwable e) { return dealfunc.apply(e); } } @functionalinterface public interface throwexceptionsupplier<t> { t get() throws throwable; } }
到这里代码部分就已经完成了,我们来看下如何使用
@exceptionadvice public class exceptionconfig { @exceptionhandler(value = nullpointerexception.class) public baseresponse process(nullpointerexception e){ return new baseresponse(0,"npe"); } @exceptionhandler(value = exception.class) public baseresponse process(exception e){ return new baseresponse(0,"ex"); } } @restcontroller public class testcontroler { @requestmapping("/test") @exceptionintercept public baseresponse test(@requestparam("a") integer a){ if (a == 1){ return new baseresponse(1,a+""); } else if (a == 2){ throw new nullpointerexception(); } else throw new runtimeexception(); } }
我们通过@exceptionadvice标志定义异常处理器的类,然后通过@exceptionhandler标注处理异常的方法,方便收集
最后在需要异常拦截的方法上面通过@exceptionintercept进行异常拦截
我没有使用spring那种匹配最近父类的方式寻找匹配的异常处理器,我觉得这种设计是一个败笔,理由如下
- 代码复杂
- 不能一眼看出要去调用哪个异常处理器,尤其是定义的异常处理器非常多的时候,要是弄多个定义类就更不好找了,可能要把所有的处理器看完才知道应该调用哪个
出于以上考虑,我只保留了一个异常处理器定义类,并且匹配顺序和方法定义顺序一致,从上到下依次匹配,这样只要找到一个能够处理的处理器,那么就知道了会如何调用
原创不易,如果觉得对你有帮助,麻烦点个赞!
我会不定期分享一些有意思的技术,点个关注不迷路-。 -
以上就是在spring中手写全局异常拦截器的详细内容,更多关于spring 全局异常拦截的资料请关注其它相关文章!
推荐阅读
-
在spring中手写全局异常拦截器
-
在Spring Cloud中配置Feign的拦截器 增加自定义的请求信息进去 以及演示加入Authorization到请求Headers
-
在spring-boot工程中添加spring mvc拦截器
-
在 ASP.NET Core 中为 gRPC 服务添加全局异常处理
-
在 ASP.NET Core 中为 gRPC 服务添加全局异常处理
-
在SpringBoot框架中,异常错误全局拦截以及判断手动抛出异常的代码格式
-
在 ASP.NET Core 中为 gRPC 服务添加全局异常处理
-
在spring中手写全局异常拦截器
-
SpringBoot中添加拦截器,在拦截器中注入其他类的时候出现空指针异常解决办法
-
SpringBoot中添加拦截器,在拦截器中注入其他类的时候出现空指针异常解决办法