前言
在一般的项目中,我们总是会遇到各种各样的异常,如果每次都用try-catch就太麻烦了,所以我们的系统中需要一种统一的异常处理,幸好这种问题早就有了解决方案。下面来了解一下SpringMVC 和 dubbo的解决方案
SpringMVC 统一异常处理
这里使用最简单的@ControllerAdvice和@ExceptionHandler来实现
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResultBody ExceptionHandler(Exception e){
System.out.println("未知异常!" +e.getMessage());
return ResultBody.error(e.getMessage());
}
}
通过这两个注解我们就可以完成记录所有的异常,并且根据各种不同的异常进行不同的处理。
缺点:
- 这种统一的异常只能对controller生效,因为@ControllerAdvice是一个controller增强接口
但对于使用微服务的接口来说,这样是无法满足需求的。所以我们对于服务提供者需要使用dubbo的异常处理
dubbo统一异常处理
目标
我们期望的异常处理方式是,当provider发生异常时,我们期望将全部异常转化为特定的结果,方便调用方根据错误类型处理。
dubbo默认处理方式
dubbo其实有自己默认的异常处理机制,在Dubbo的 provider端 抛出异常(Throwable),则会被 provider端 的ExceptionFilter拦截到,执行以下invoke方法,里面有个实现Listener类,重写了onResponse。
这里面 dubbo的处理方式就很简单
-
不是RuntimeException类型的异常,并且是受检异常(继承Exception),直接抛出。
-
在方法签名上有声明,直接抛出。
-
如果异常类和接口类在同一个jar包中,直接抛出。
-
以java.或javax.开头的异常直接抛出。
-
dubbo自身的异常,直接抛出。
-
不是以上情况会做toString处理并被封装成RuntimeException抛出。
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
// directly throw if it's checked exception
//不是RuntimeException类型的异常,并且是受检异常(继承Exception),直接抛出。
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return;
}
// directly throw if the exception appears in the signature
//在方法签名上有声明,直接抛出。
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException e) {
return;
}
// 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);
//如果异常类和接口类在同一个jar包中,直接抛出。
// 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;
}
// directly throw if it's JDK exception
//以java.或javax.开头的异常直接抛出。
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return;
}
// directly throw if it's dubbo exception
if (exception instanceof RpcException) {
return;
}
// otherwise, wrap with RuntimeException and throw back to the client
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
return;
} 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;
}
}
}
可以看到在源码中 看到dubbo的异常处理方式都是将异常直接抛出,所以异常会传递到consumer这一端,那consumer如果要保证业务的正常处理,就需要将provider的异常进行处理,这样会造成不必要的代码量。
为了解决这个问题,我们需要重写这个类。
解决方案
新建自定义异常处理Filter
首先,我们认同 dubbo中异常处理类中,只处理运行时异常,对其他受检查,如io异常,则交由程序处理。
对于 :在方法签名上有声明,直接抛出。 也不做处理。
对于其余的异常,我们这里都进行处理。具体代码如下
@Activate(group = CommonConstants.PROVIDER)
public class MyDubboExceptionFilter extends ListenableFilter {
public MyDubboExceptionFilter() {
super.listener = new MyDubboExceptionFilter.ExceptionListener();
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
static class ExceptionListener implements Filter.Listener {
private Logger logger = LoggerFactory.getLogger(MyDubboExceptionFilter.ExceptionListener.class);
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
// directly throw if it's checked exception
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return;
}
// directly throw if the exception appears in the signature
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException e) {
return;
}
// 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 it's dubbo exception
if (exception instanceof RpcException) {
return;
}
//java底层异常 全部处理,转换MyResult类型 为底层异常
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
appResponse.setException(null);
appResponse.setValue(new MyResult(5001,"系统异常"));
return;
}
//自定义业务异常 全部处理,转换MyResult类型
if (exception instanceof BussinessException) {
appResponse.setException(null);
appResponse.setValue(new MyResult(5002,"业务异常"));
//appResponse.setValue("业务异常");
return;
}
//其余异常 也转为 MyResult类型
// otherwise, wrap with RuntimeException and throw back to the client
appResponse.setException(null);
appResponse.setValue(new MyResult(5002,"未知异常"));
return;
} 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;
}
}
}
@Override
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
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);
}
// For test purpose
public void setLogger(Logger logger) {
this.logger = logger;
}
}
}
配置spi
在META-INF.dubbo下新建com.alibaba.dubbo.rpc.Filter 并配置自定义异常类
dubboExceptionFilter=com.example.filter.MyDubboExceptionFilter
启用配置
启用自定义异常处理类,并且禁用其他异常处理类
dubbo:
provider:
filter: dubboExceptionFilter,-exception
dubbo总结:
这里只是简单的将异常转化为 自定义的返回类型(Myresult),所以在定义调用接口时,必须返回类型都是(Myresult),否则会调用失败,这点没做兼容,以后再弄吧