原因:使用 dubbo 的过程中遇到 provider 返回的自定义异常被包装, 在使用泛化调用的过程中,异常被包装成了GenericException
(之前也在开发的过程中也遇到了,还不是太懂,没有去解决)
文章源码及部分讲解源自《深入理解 Apache Dubbo 实战》dubbo version:2.7.1
在各种百度的过程中,和我预期的不太一样。找到了一篇看着还行的文章,先留着,看其他的文章时发现都是一模一样的,而且版本停留的比较前,不过关于 dubbo 的泛化调用也一直没怎么变。
Filter 简单描述
过滤器链初始化的实现原理
- 扩展点的初始化(SPI)
-
过滤器链组装:
在服务暴露的过程中会使用 Protocol 层,ProtocolFilterWrapper
实现组装。在暴露与引用的过程中,会使用ProtocolFilterWrapper#buildInvokerChain
方法组装整个过滤器服务暴露与引用的过程
// 1- 服务暴露时会调用 buildInvokerChain
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {return protocol.export(invoker);
}
// Constants.PROVIDER-> 根据 Constants.PROVIDER 标识自己是提供者类型的调用链(group)
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
// 引用远程服务的时候也会调用 buildInvokerChain
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {return protocol.refer(type, url);
}
// Constants.CONSUMER-> 根据 Constants.CONSUMER 标识消费者类型的调用链(group)
return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
#### 构造调用链源码
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;// 保存引用,后续用于把真正的调用者保存在过滤器的最后
// 获得所有的过滤器
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
// 对过滤器做倒排遍历,即从尾到头。在构造的过程中会从一直往上构造,所以调用顺序还是头为第一个节点
for (int i = filters.size() - 1; i >= 0; i--) {final Filter filter = filters.get(i);
// 把 last 节点变成 next 节点,并放到 Filter 链的 next 中
final Invoker<T> next = last;
last = new Invoker<T>() {
···省略
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 设置过滤器链的下一个节点,不断循环形成过滤链
Result result = filter.invoke(next, invocation);
// 异步调用和同步调用的处理
if (result instanceof AsyncRpcResult) {AsyncRpcResult asyncResult = (AsyncRpcResult) result;
asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
return asyncResult;
} else {return filter.onResponse(result, invoker, invocation);
}
}
@Override
public void destroy() {invoker.destroy();
}
@Override
public String toString() {return invoker.toString();
}
};
}
}
return last;
}
GenericFilter
用于服务提供者端,实现泛化调用,实现序列化的检查和处理
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
if (result.hasException()
&& !(result.getException() instanceof GenericException)) {return new RpcResult(new GenericException(result.getException()));
}
上述代码是实现 Filter#invoke 方法,在执行完后会判断 Result 中是否有异常,若不是 GenericException 会包装为 GenericException 异常再返回。
我在提供者做了自定义异常的处理,但是被包装了,并不能直接返回到前端被捕获。
下面我加入了判断:
if (result.hasException()
&& !(result.getException() instanceof GenericException)) {
// 处理自定义异常
String className = result.getException().getClass().getName();
if (className.startsWith("com.changyuan.education.commons.exception")) {
// 打印堆栈
result.getException().printStackTrace();
// 返回异常信息
return new RpcResult(new ResultBean().failure(result.getException().toString()));
}
return new RpcResult(new GenericException(result.getException()));
}
- new ResultBean() 自定义的返回前端的对象,包含 code,msg,data
- result.getException().toString() 获取我抛出的自定义异常的内容,为了与 ResultBean 元素一样包含 code,msg
// 重写的 toString,转为 json 对象
@Override
public String toString() {return "{\"code\":" + code + ",\"msg\":\"" + msg + "\"}";
}
- resultBean()中接受 json 并解析, 最后打印完异常抛出给前端
public ResultBean<T> failure(String upExcetionJson) {JSONObject jsonObject = JSON.parseObject(upExcetionJson);
this.code = (int) jsonObject.get("code");
this.msg = jsonObject.get("msg");
return this;
}
- 对于其他的 java 的异常先放着,后续解决,先把已知的交互异常处理
Dubbo2GenericFilter 自定义的 SPI
- 先创建这么一个过滤器
@Activate(group = Constants.PROVIDER, order = -20000)
public class Dubbo2GenericFilter implements Filter {}
-
在 resources 中创建文件夹
META-INF.dubbo.internal
, 创建文件org.apache.dubbo.rpc.Filter
,写入一下内容generic=com.changyuan.education.exam.filter.Dubbo2GenericFilter
扩展点的配置和规范
Dubbo SPI 和 Java SPI 类似,需要在 META-INF/dubbo/ 下放置对应的 SPI 配置文件,文件名称必须命名为接口的全路径名。配置文件的内容为 key= 扩展点实现类的全路径名,多个实现用换行符隔开。其中 key 会被作为 Dubbo SPI 注解中传入的参数。Dubbo 会默认扫描三个文件夹 META-INF/dubbo/、META-INF/services/、META-INF/dubbo/internal/