在spring中手寫全局異常攔截器
為什么要重復(fù)造輪子
你可能會問,Spring已經(jīng)自帶了全局異常攔截,為什么還要重復(fù)造輪子呢?
這是個(gè)好問題,我覺得有以下幾個(gè)原因
- 裝逼
- Spring的全局異常攔截只是針對于Spring MVC的接口,對于你的RPC接口就無能為力了
- 無法定制化
- 除了寫業(yè)務(wù)代碼,我們其實(shí)還能干點(diǎn)別的事
我覺得上述理由已經(jīng)比較充分的解答了為什么要重復(fù)造輪子,接下來就來看一下怎么造輪子
造個(gè)什么樣的輪子?
我覺得全局異常攔截應(yīng)該有如下特性
- 使用方便,最好和spring原生的使用方式一致,降低學(xué)習(xí)成本
- 能夠支持所有接口
- 調(diào)用異常處理器可預(yù)期,比如說定義了RuntimeException的處理器和Exception的處理器,如果這個(gè)時(shí)候拋出NullPointException,這時(shí)候要能沒有歧義的選擇預(yù)期的處理器
如何造輪子?
由于現(xiàn)在的應(yīng)用基本上都是基于spring的,因此我也是基于SpringAop來實(shí)現(xiàn)全局異常攔截
首先先定義幾個(gè)注解
@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 的作用是標(biāo)志定義異常處理器的類,方便找到異常處理器
@ExceptionHandler 的作用是標(biāo)記某個(gè)方法是處理異常的,里面的值是能夠處理的異常類型
@ExceptionIntercept 的作用是標(biāo)記需要異常攔截的方法
接下來定義統(tǒng)一返回格式,以便出現(xiàn)錯誤的時(shí)候統(tǒng)一返回
@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; } }
然后定義一個(gè)收集異常處理器的類
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("沒有找到對應(yīng)的異常處理器")) .getMethod(); } @AllArgsConstructor @Getter class ExceptionMethod { private Class<? extends Throwable> clazz; private Method method; } }
ExceptionMethod 里面有兩個(gè)屬性
- clazz:這個(gè)代表著能夠處理的異常
- method:代表著處理異常調(diào)用的方法
ExceptionMethodPool 里面按序存放所有異常處理器,excutor是執(zhí)行這些異常處理器的對象
接下來把所有定義的異常處理器收集起來
@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("不允許有兩個(gè)異常定義類"); 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)); }); //注冊進(jìn)spring容器 context.getBeanFactory().registerSingleton("exceptionMethodPool",exceptionMethodPool); return bean; } }
ExceptionBeanPostProcessor 通過實(shí)現(xiàn)BeanPostProcessor 接口,在bean初始化之前,把所有異常處理器塞進(jìn) ExceptionMethodPool,并把其注冊進(jìn)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,"未知錯誤")); } }
這里應(yīng)用了我自己通過函數(shù)式編程封裝的一些語法糖,有興趣的可以看下
最后通過AOP進(jìn)行攔截
@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; } }
到這里代碼部分就已經(jīng)完成了,我們來看下如何使用
@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標(biāo)志定義異常處理器的類,然后通過@ExceptionHandler標(biāo)注處理異常的方法,方便收集
最后在需要異常攔截的方法上面通過@ExceptionIntercept進(jìn)行異常攔截
我沒有使用Spring那種匹配最近父類的方式尋找匹配的異常處理器,我覺得這種設(shè)計(jì)是一個(gè)敗筆,理由如下
- 代碼復(fù)雜
- 不能一眼看出要去調(diào)用哪個(gè)異常處理器,尤其是定義的異常處理器非常多的時(shí)候,要是弄多個(gè)定義類就更不好找了,可能要把所有的處理器看完才知道應(yīng)該調(diào)用哪個(gè)
出于以上考慮,我只保留了一個(gè)異常處理器定義類,并且匹配順序和方法定義順序一致,從上到下依次匹配,這樣只要找到一個(gè)能夠處理的處理器,那么就知道了會如何調(diào)用
原創(chuàng)不易,如果覺得對你有幫助,麻煩點(diǎn)個(gè)贊!
我會不定期分享一些有意思的技術(shù),點(diǎn)個(gè)關(guān)注不迷路-。 -
以上就是在spring中手寫全局異常攔截器的詳細(xì)內(nèi)容,更多關(guān)于spring 全局異常攔截的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringMVC 攔截器的使用示例
- SpringBoot登錄用戶權(quán)限攔截器
- SpringBoot 攔截器和自定義注解判斷請求是否合法
- spring boot攔截器注入不了java bean的原因
- SpringBoot配置攔截器的示例
- SpringBoot之HandlerInterceptor攔截器的使用詳解
- Spring boot如何基于攔截器實(shí)現(xiàn)訪問權(quán)限限制
- Springmvc異常處理器及攔截器實(shí)現(xiàn)代碼
- 解決Springboot @WebFilter攔截器未生效問題
- springboot攔截器過濾token,并返回結(jié)果及異常處理操作
- 詳解Spring 攔截器流程及多個(gè)攔截器的執(zhí)行順序
相關(guān)文章
spring cloud gateway跨域全局CORS配置方式
這篇文章主要介紹了spring cloud gateway跨域全局CORS配置方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07java使用poi讀取excel內(nèi)容方法實(shí)例
本文介紹java使用poi讀取excel內(nèi)容的實(shí)例,大家參考使用吧2014-01-01詳解json在SpringBoot中的格式轉(zhuǎn)換
這篇文章主要介紹了詳解json在SpringBoot中的格式轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Java通過值查找對應(yīng)的枚舉的實(shí)現(xiàn)
本文主要介紹了Java通過值查找對應(yīng)的枚舉的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02Java中消息隊(duì)列任務(wù)的平滑關(guān)閉詳解
對于消息隊(duì)列的監(jiān)聽,我們一般使用Java寫一個(gè)獨(dú)立的程序,在Linux服務(wù)器上運(yùn)行。程序啟動后,通過消息隊(duì)列客戶端接收消息,放入一個(gè)線程池進(jìn)行異步處理,并發(fā)的快速處理。這篇文章主要給大家介紹了關(guān)于Java中消息隊(duì)列任務(wù)的平滑關(guān)閉的相關(guān)資料,需要的朋友可以參考下。2017-11-11