• 自定义SPI使用JDK动态代理遇到UndeclaredThrowableException异常排查


    前言

    上一篇文章我们聊了聊聊自定义SPI如何与sentinel整合实现熔断限流。在实现整合测试的过程,出现一个有趣的异常java.lang.reflect.UndeclaredThrowableException,当时在代码层做了一个全局异常捕获,示例如下

    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
    
        @ExceptionHandler(Exception.class)
        public AjaxResult handleException(Exception e) {
            String msg = e.getMessage();
            return AjaxResult.error(msg,500);
        }
    
    
        @ExceptionHandler(BlockException.class)
        public AjaxResult handleBlockException(BlockException e) {
            String msg = e.getMessage();
            return AjaxResult.error(msg,429);
        }
    
    }
    
    

    本来预期是触发限流时,就会捕获BlockException 异常,再封装一层渲染出去,没想到死活捕获不到BlockException 异常。

    问题排查

    通过debug发现,该问题是由于jdk动态代理引起,后面查找了一些资料,后面在官方的API文档查到这么一段话

    他的大意大概是如果代理实例的调用处理程序的 invoke 方法抛出一个经过检查的异常(不可分配给 RuntimeException 或 Error 的 Throwable),且该异常不可分配给该方法的throws子局声明的任何异常类,则由代理实例上的方法调用抛出UndeclaredThrowableException异常。

    这段话我们可以分析出如下场景

    1、真实实例方法上没有声明异常,代理实例调用时抛出了受检异常

    2、真实实例方法声明了非受检异常,代理实例调用时抛出了受检异常

    解决方案

    方案一:真实实例也声明受检异常

    示例:

    public class SqlServerDialect implements SqlDialect {
        @Override
        public String dialect() throws Exception{
            return "sqlserver";
        }
    
    

    方案二:jdk动态代理的invoke进行捕获,同时可以自定义异常抛出

    示例:

     @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            CircuitBreakerInvocation invocation = new CircuitBreakerInvocation(target,method,args);
    
            try {
    
                return new CircuitBreakerInvoker().proceed(invocation);
            } catch (Throwable e) {
                throw new CircuitBreakerException(429,"too many request");
            }
    
        }
    

    方案三:捕获InvocationTargetException异常,并抛出真正的异常

    为啥要InvocationTargetException,原因是因为我们自定义的异常是会被InvocationTargetException包裹

    示例

      @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            CircuitBreakerInvocation invocation = new CircuitBreakerInvocation(target,method,args);
    
            try {
    
                return new CircuitBreakerInvoker().proceed(invocation);
                //用InvocationTargetException包裹是java.lang.reflect.UndeclaredThrowableException问题
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
    
        }
    

    总结

    如果是我们自己实现的组件,推荐直接使用方案三,即捕获InvocationTargetException异常。

    如果是用第三方实现的组件,推荐方案一即在调用的实例方法声明异常,比如在使用springcloud alibaba sentinel熔断降级是有概率会出现UndeclaredThrowableException异常的,因为它也是基于动态代理,他抛出来的BlockException也是一个受检异常。示例如下

    public class SqlServerDialect implements SqlDialect {
        @Override
        public String dialect() throws BlockException{
            return "sqlserver";
        }
    
    

    如果使用第三方组件不想方案一,你也可以在第三方组件的基础上再加一层代理,或者对第三方组件进行切面拦截处理

    demo链接

    https://github.com/lyb-geek/springboot-learning/tree/master/springboot-spi-enhance/springboot-spi-framework-circuitbreaker

  • 相关阅读:
    知识经济中的贫富差距固定化
    分布式锁
    Activiti
    一种避免在scrollViewDidEndDragging中改变contentInset时闪动的解决方案
    一个封装好的iOS无限滚动组件HXInfiniteScrollView
    使用ReactiveCocoa限制UITextField只能输入正确的金额
    关于ReactiveCocoa的RACObserve的一些研究
    iOS使用masonry快速将一组view在superview中等宽排列
    使用AutoLayOut为UIScrollView添加约束图解及要点
    使用AutoLayOut技术告别UITableViewCell高度计算
  • 原文地址:https://www.cnblogs.com/linyb-geek/p/15666176.html
Copyright © 2020-2023  润新知