理解dubbofilter的执行顺序
对dubbo filter执行顺序可以看下这篇。
背景
在处理网关泛化调用的异常时,需要在provider端将可读异常的message返回给调用方,方便展示。
现有工程中有处理异常的filter:
@Activate(group = Constants.PROVIDER, order=Integer.MIN_VALUE)
@Slf4j
public class ExceptionHandler implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result = invoker.invoke(invocation);
if (result.hasException() && GenericService.class != invoker.getInterface()) {
Throwable exception = result.getException();
log.info("接口调用异常 class:{} method:{}, arg:{} exception:", invoker.getInterface().getName(), invocation.getMethodName(), invocation.getArguments(), exception);
if (exception instanceof BizException) {
return new RpcResult(JSON.toJSON(ResultUtil.error(((BizException) exception).getCode(), exception.getMessage())));
}
if (exception instanceof IllegalArgumentException || exception instanceof IllegalStateException) {
return new RpcResult(JSON.toJSON(ResultUtil.error(400, exception.getMessage())));
}
if (exception instanceof NullPointerException) {
return new RpcResult(JSON.toJSON(ResultUtil.error(500, StringUtils.isEmpty(exception.getMessage()) ? "服务器异常" : exception.getMessage())));
}
return new RpcResult(JSON.toJSON(ResultUtil.error(500, "服务器异常")));
}
return result;
}
}
看上去没有问题,对rpcResult中的exception拿出来做异常类的判断,包括参数错误、服务器异常等常见错误。
但是在调用的时候却发现最后都会被包装成兜底的服务器异常。
原因及解决
debug发现,因为网关是泛化调用,所以走到此filter的异常都是GenericException,其中包了对应的业务异常。
public class GenericException extends RuntimeException {
private static final long serialVersionUID = -1182299763306599962L;
private String exceptionClass;
private String exceptionMessage;
}
可以想到这里是因为Genericfilter。
GenericFilter会调用invoke.invoke,并且根据rpcResult包装对应的异常。
那么就是想到order设置问题。但是按照之前理解(一种错误的理解)order越小应该越
先执行,这里明明设置了MIN_VALUE,而GenericFilter的order是-20000,应该自定义filter先执行啊,为什么拿到的还是GenericException?
其实这种理解是错误的,order越小越先执行的结论是没错的,但是Dubbo filter链调用是通过调用包装过的invoker对象的next来实现的。来看ProtocolFilterWrapper中构建执行链的逻辑:
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);
final Invoker<T> next = last;
last = new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
当获取到有序的Activate的Filter,为当前的invoker的invoker方法封装上循环的filter。
来看一个自定义的Filter在export之后经过filter链封装的模型:
helloFilter模拟了上边的ExceptionFilter,order是MIN_VALUE,在服务端启动的时候export流程中拿到的activate的Filter实现(已排好序的)
可以看到因为HelloFilter的order最小,所以排在了第一个。而看到上边ProtocolFilterWrapper中,从最后一个ExceptionFilter开始为invoker封装filter逻辑,这里new Invoker中实现的invoker方法就是按照order从大到小包装invoker。包装后的类型为ProtocolFilterWrapper$1内部类类型,其中有 filter类型的实例、真正的invoker实例(获取url等方法还是直接调用的原始的invoker)、还有next引用(一个ProtocolFilterWrapper$1实例)。
所以理解错误关键的点在这:order越小的Filter是越先被执行,但是在调用其invoke方法时,会调用next.invoke方法,也就是下一个filter的invoke方法。
回到上面的问题,当我们自定义异常过滤器的order比GenericFilter的order小,所以过滤链为:
{
"filter":"ExceptionFilter",
"invoker":invoker实例,
"next":{
filter:"GenericFilter",
"invoker":invoker实例,
"next":...
}
}
也就是调用ExceptionFilter的invoke方法时,会调用到GenericFilter的invoke方法,而GenericFilter处理异常的逻辑就是包装GenericException。
@Activate(group = Constants.PROVIDER, order = -20000)
public class GenericFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
if (inv.getMethodName().equals(Constants.$INVOKE)
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !ProtocolUtils.isGeneric(invoker.getUrl().getParameter(Constants.GENERIC_KEY))) {
// 这里的判断是泛化调用的判断
// 参数长度为3 是因为泛化调用的参数为接口、参数类型、参数实例
// 省略代码
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
if (result.hasException()
&& !(result.getException() instanceof GenericException)) {
return new RpcResult(new GenericException(result.getException()));
}
if (ProtocolUtils.isJavaGenericSerialization(generic)) {
try {
UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
.serialize(null, os).writeObject(result.getValue());
return new RpcResult(os.toByteArray());
} catch (IOException e) {
throw new RpcException("Serialize result failed.", e);
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
return new RpcResult(JavaBeanSerializeUtil.serialize(result.getValue(), JavaBeanAccessor.METHOD));
} else {
return new RpcResult(PojoUtils.generalize(result.getValue()));
}
} catch (NoSuchMethodException e) {
throw new RpcException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new RpcException(e.getMessage(), e);
}
}
return invoker.invoke(inv);
}
}
这也解释了为什么在自定义的ExceptionFilter拿到的是GenericException,不好去解析可读异常。
解决办法也很简单:
- 在自定义异常过滤器中对GenericException做拆分操作,判断真正的业务异常和可读message。
- 将自定义异常过滤器的order调整比GenericFilter大,这样当完成异常拦截之后,返回的RpcResult中无异常信息,GenericFilter也不会去封装异常,则此次泛化调用只是调用了下,没有实质操作。