在dubbo使用过程中,遇到一个异常的问题
场景:在service(dubbo的提供者)中new MyBussinessException
(自定义的业务异常),但是方法上throw的是Exception,controller(消费端)要根据异常的类型进行相应处理,如果是catch的是业务异常直接抛出,其他异常则将异常信息改为“系统异常”。
代码如下:
/**
*假设这是dubbo提供的一个service
*/
@Service
class Read implements Hobby{
@Override
public void read() throws Exception{
System.out.println("reading...");
throw new MyBussinessException("测试异常");
}
}
//消费端中的业务
String msg;
try {
read.read();
} catch (Exception e) {
if(e instanceof MyBussinessException){
msg=e.getMessage();
} else {
msg="系统异常";
}
}
结果发现,catch中的异常是Runtime异常,调试发现,dubbo会判断方法抛出的异常是否有在方名签名中声明,如果没有声明,则封装成Runtime异常再抛出。
dubbo源码如下:
public class ExceptionFilter implements Filter {
private final Logger logger;
public ExceptionFilter() {
this(LoggerFactory.getLogger(ExceptionFilter.class));
}
public ExceptionFilter(Logger logger) {
this.logger = logger;
}
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
Result result = invoker.invoke(invocation);
if (result.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = result.getException();
//这里抛出的是MyBusinessException,所以跳过
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}
// 这里判断方法抛出的异常是否有声明,由于这里不一致也跳过
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return result;
}
}
} catch (NoSuchMethodException e) {
return result;
}
// for the exception not found in method's signature, print ERROR message in server's log.
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
// directly throw if exception class and interface class are in the same jar file.
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return result;
}
// directly throw if it's JDK exception
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// directly throw if it's dubbo exception
if (exception instanceof RpcException) {
return result;
}
//否则,包装成RuntimeException
return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
return result;
}
}
return result;
} catch (RuntimeException e) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
throw e;
}
}
}
解决方案:确保想要throw出来的异常有声明