前言上一篇我们讲了自定义SPI如何与sentinel集成实现熔断和限流。在实现集成测试的过程中,出现了一个有趣的异常java.lang.reflect.UndeclaredThrowableException。当时在代码层做了一个全局的异常捕获。示例如下返回AjaxResult.error(msg,500);}@ExceptionHandler(BlockException.class)publicAjaxResulthandleBlockException(BlockExceptione){Stringmsg=e.getMessage();返回AjaxResult。错误(消息,429);}}本来期望在触发限流的时候,会捕获到BlockException,然后封装渲染出来。出乎意料的是,无法捕获BlockException。排查问题通过调试,发现问题出在jdk动态代理上。后来找了一些资料,后来在API官方文档中找到了这么一段话。他的主要想法是,如果代理实例的调用处理程序的invoke方法抛出一个已检查异常(throwablenotassignabletoRuntimeExceptionorError),该异常不可分配给方法的throws子目录中声明的任何异常类,则由方法调用抛出代理实例。从这段话,我们可以分析出以下几种场景:1.真实实例方法上没有声明异常,调用代理实例时抛出checked异常。2、在真实实例方法上声明非检查异常,调用代理实例时抛出检查异常。异常解决方案一:真实实例也声明了检查异常示例:publicclassSqlServerDialectimplementsSqlDialect{@OverridepublicStringdialect()throwsException{return"sqlserver";}方案二:捕获jdk动态代理的invoke,同时可以自定义异常抛出示例:@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{CircuitBreakerInvocationinvocation=新的CircuitBreakerInvocation(目标、方法、参数);try{returnnewCircuitBreakerInvoker().proceed(调用);}catch(Throwablee){thrownewCircuitBreakerException(429,"请求太多");}}方案三:捕获InvocationTargetException并抛出一个真正的异常为什么需要InvocationTargetException因为我们自定义异常会被InvocationTargetException包裹示例@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{CircuitBreakerInvocationinvocation=新的CircuitBreakerInvocation(目标,方法,参数);尝试{返回新的CircuitBreakerInvoker().proceed(invocation);//用InvocationTargetException包裹的是java.lang.reflect.UndeclaredThrowableException问题}catch(InvocationTargetExceptione){throwe.getTargetException();是我们自己实现的一个组件。推荐直接使用第三种方案,即捕获InvocationTargetException异常。如果是第三方实现的组件,推荐的解决方案是在被调用的实例方法中声明异常。出现UndeclaredThrowableException是因为它也是基于动态代理的,它抛出的BlockException也是checkedexception。示例如下:publicclassSqlServerDialectimplementsSqlDialect{@OverridepublicStringdialect()throwsBlockException{return"sqlserver";}如果不想使用第三方组件,可以在第三方组件之上加一层proxy,或者拦截第三方组件的处理demo链接https://github.com/lyb-geek/springboot-learning/tree/master/springboot-spi-enhance/springboot-spi-framework-circuitbreaker
