原因:使用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/
发表回复