关于java:自定义SPI使用JDK动态代理遇到UndeclaredThrowableException异常排查

9次阅读

共计 2281 个字符,预计需要花费 6 分钟才能阅读完成。

前言

上一篇文章咱们聊了聊聊自定义 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

正文完
 0