Preface
In the previous article, we talked about about how the custom SPI integrates with sentinel to realize the fusing current limit . In the process of implementing the integration test, an interesting exception java.lang.reflect.UndeclaredThrowableException appeared. At that time, a global exception capture was made at the code level. The example is as follows
@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);
}
}
The original expectation was that when the current limit was triggered, the BlockException would be caught, and then encapsulated and rendered by another layer, but I did not expect that the BlockException would not be caught in life and death.
Troubleshooting
Through debug, it was found that the problem was caused by jdk dynamic proxy. I searched for some information later, and I found such a paragraph in the official API document.
His general idea is that if the invoke method of the call handler of the proxy instance throws a checked exception (not assignable to RuntimeException or Error Throwable), and the exception cannot be assigned to any exception class declared by the throws sub-bureau of the method , The UndeclaredThrowableException is thrown by the method call on the proxy instance.
In this passage, we can analyze the following scenarios
1. There is no declared exception on the real instance method, and a checked exception is thrown when the proxy instance is called
2. The real instance method declares an unchecked exception, and a checked exception is thrown when the proxy instance is called
solution
Option 1: The real instance also declares the checked exception
Example:
public class SqlServerDialect implements SqlDialect {
@Override
public String dialect() throws Exception{
return "sqlserver";
}
Option 2: The invoke of the jdk dynamic proxy is captured, and exceptions can be customized to be thrown at the same time
Example:
@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");
}
}
Option 3: Catch InvocationTargetException and throw a real exception
Why InvocationTargetException, the reason is because our custom exception will be wrapped by InvocationTargetException
Example
@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();
}
}
Summarize
If it is a component implemented by ourselves, it is recommended to directly use the third scheme, which is to catch the InvocationTargetException.
If it is a component implemented by a third party, the recommended solution is to declare an exception in the called instance method. For example, when using springcloud alibaba sentinel fuse downgrade, there is a probability that UndeclaredThrowableException will occur, because it is also based on a dynamic proxy, and the BlockException thrown by him It is also a checked exception. The example is as follows
public class SqlServerDialect implements SqlDialect {
@Override
public String dialect() throws BlockException{
return "sqlserver";
}
If you don’t want to use third-party components, you can also add a layer of proxy on the basis of third-party components, or perform aspect interception of third-party components
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。