前言

上一篇文章咱们聊了聊聊自定义SPI如何与sentinel整合实现熔断限流。在实现整合测试的过程,呈现一个乏味的异样java.lang.reflect.UndeclaredThrowableException,过后在代码层做了一个全局异样捕捉,示例如下

@RestControllerAdvicepublic 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