自定义dubbo异常过滤器
- 一、前置问题介绍:
- 问题一
- 问题二
- 二、Dubbo的异常过滤器源码如下:
- 三、实现方案 - 重写Dubbo的Filter异常过滤器
- 至此,Dubbo自定义异常过滤器已完结!
一、前置问题介绍:
问题一
在dubbo框架中,由于一些 interface
接口未显示的声明抛出异常,导致dubbo在捕获异常时发现抛出的异常为非声明的异常,其也不属于jdk的异常,则dubbo框架会自动封装成 new RuntimeException(StringUtils.toString(exception)))
异常抛出;
问题二
针对一些自定义的异常,不希望被Dubbo框架捕获,只希望逐级向上抛出,直到业务的最顶层在捕获异常的code和message,并返回给前端显示code(这种情况适用于系统内部有自己定义的一套异常码,通过前端显示的code值能清晰的知道异常的问题是什么)
二、Dubbo的异常过滤器源码如下:
package com.alibaba.dubbo.rpc.filter;
import java.lang.reflect.Method;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcResult;
import com.alibaba.dubbo.rpc.service.GenericService;
/**
* ExceptionInvokerFilter
* <p>
* 功能:
* <ol>
* <li>不期望的异常打ERROR日志(Provider端)<br>
* 不期望的日志即是,没有的接口上声明的Unchecked异常。
* <li>异常不在API包中,则Wrap一层RuntimeException。<br>
* RPC对于第一层异常会直接序列化传输(Cause异常会String化),避免异常在Client出不能反序列化问题。
* </ol>
*
*/
@Activate(group = Constants.PROVIDER)
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();
// 如果是checked异常,直接抛出
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;
}
// 未在方法签名上定义的异常,在服务器端打印ERROR日志
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包里,直接抛出
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
return result;
}
// 是JDK自带的异常,直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// 是Dubbo本身的异常,直接抛出
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;
}
}
}
三、实现方案 - 重写Dubbo的Filter异常过滤器
1、将上述 Dubbo
源码中 ExceptionFilter
复制到我们的项目改名为 DubboExceptionFilter
可直接粘贴本代码片段到IDEA中,只需将内部
start - end
中间的代码替换成自己实际的业务异常处理逻辑;
import java.lang.reflect.Method;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.service.GenericService;
/**
* @author : gaogao
* @version 1.0
* @date : 2024-03-14 10:08
*
* 由于dubbo异常过滤器源码的问题,如果自定义的异常未在方法上声明,导致异常无法正常抛出,而是被过滤器包装成RuntimeException抛给客户端
* 此工具类为重写dubbo异常拦截器,支持抛出自定义的异常!!!
*
* 如果使用此工具类需要注意几点:
* 1、拦截器只拦截provider端,consumer端不需要拦截器
* 2、需要在resources目录下创建 META-INF/dubbo目录,并创建 com.alibaba.dubbo.rpc.Filter 文件(注意文件层级:resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter),
* 文件内容为:dubboExceptionFilter=com.bosssoft.cloud.coreservice.base.config.DubboExceptionFilter
* 解释说明:dubboExceptionFilter:即你给这个过滤器取的名称,com.bosssoft.cloud.coreservice.base.config.DubboExceptionFilter:表示过滤器对应的完整路径
*
* 3、在ProviderConfig【此处为java代码示例,若为.xml配置文件可使用<dubbo:provider filter="dubboExceptionFilter,-exception"/>】 的配置中设置 :
* providerConfig.setFilter("dubboExceptionFilter,-exception");
* 解释说明:dubboExceptionFilter为自己定义的过滤器名称;额外追加的 -exception ,是因为如果不加上 -exception 我们自己重写的过滤器确实生效了,但Dubbo自身默认的 ExceptionFilte 任然在工作
* (一个异常抛出会依次进入两个过滤器)所以我们需要 禁用 掉Dubbo默认的过滤器;同理,如果想禁用其它的过滤器,都可以通过 -过滤器名称来实现;
*
* 如果想更加深入了解dubbo的过滤器源码,可翻看阿里巴巴的com.alibaba.dubbo.rpc.filter.ExceptionFilter
*
*/
@Activate(group = Constants.PROVIDER)
public class DubboExceptionFilter implements Filter {
private final Logger logger;
public DubboExceptionFilter() {
this(LoggerFactory.getLogger(DubboExceptionFilter.class));
}
public DubboExceptionFilter(Logger logger) {
this.logger = logger;
}
@Override
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();
// 如果是checked异常,直接抛出
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}
String className = exception.getClass().getName();
//-----------------------------start--------------------------------------------
// 在此可针对捕获到的异常,实现自己本身系统内部的异常业务逻辑
// 由于自身业务系统的业务,在此捕获自定义的异常直接返回,向上抛出;避免后续包装成RuntimeException抛给客户端
if (className.contains("com.cloud.component.exception.XXXXXXException")
|| className.contains("com.cloud.component.exception.XXXXXXException")) {
return result;
}
//-------------------------------end--------------------------------------------
// 在方法签名上有声明,直接抛出
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;
}
// 未在方法签名上定义的异常,在服务器端打印ERROR日志
logger.error("未在方法签名上定义的异常: " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: "
+ invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
// 异常类和接口类在同一jar包里,直接抛出
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return result;
}
// 是JDK自带的异常,直接抛出
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// 是Dubbo本身的异常,直接抛出
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;
}
}
}
2、在resources目录下添加纯文本文件 META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
并在文件中添加如下内容:
dubboExceptionFilter=com.cloud.util.dubbo.DubboExceptionFilter
请将 = 后边的内容换成
DubboExceptionFilter
自定义过滤器的实际类路径
如下图:
3、修改dubbo 的配置文件,将 DubboExceptionFilter
加载进去并且去掉自身的 ExceptionFilter
1)若采用 .xml配置文件,则补充如下内容:
<dubbo:providerfilter="dubboExceptionFilter,-exception"/>
2)若采用 java代码,使用@Bean装配时,请在 DubboProviderConfig
dubbo的提供者中补充如下内容:
@Bean
public ProviderConfig providerConfig() {
ProviderConfig providerConfig = new ProviderConfig();
providerConfig.setId("proload");
providerConfig.setPayload(52428800);
providerConfig.setFilter("dubboExceptionFilter,-exception");
return providerConfig;
}
至此,Dubbo自定义异常过滤器已完结!
如果流程中哪里不懂,请仔细认真阅读 3.1章节 中的
DubboExceptionFilter类注释
~~~