关于java:自定义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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理