问题
由于Dubbo本身的设计理念导致, 消费者端抛出自定义异常后, 生产者捕获到的却是RuntimeException
解决
- 重写org.apache.dubbo.rpc.filter.ExceptionFilter类
- 在resource/META-INF/dubbo目录下新建文件org.apache.dubbo.rpc.Filter, 内容exceptionFilter=你的目录.ExceptionFilter
- 配置文件中忽略dubbo自带的异常过滤器
ExceptionFilter代码
关注105行代码即可
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xqt.common.dubbo.filter;
import com.xqt.common.core.exception.GdsException;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.service.GenericService;
import org.apache.dubbo.rpc.support.RpcUtils;
import java.lang.reflect.Method;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FILTER_VALIDATION_EXCEPTION;
/**
* ExceptionInvokerFilter
* <p>
* Functions:
* <ol>
* <li>意外的异常将记录在提供程序端的ERROR级别中。未检查意外异常
* 接口上未声明异常</li>
* <li>将API包中未引入的异常包装到RuntimeException中。框架将序列化外部异常,但字符串识别其原因,以避免客户端可能出现的序列化问题</li>
* </ol>
*/
@Activate(group = CommonConstants.PROVIDER)
public class DubboExceptionFilter implements Filter, Filter.Listener {
private ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DubboExceptionFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
// 如果检查异常则直接抛出
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return;
}
// 如果签名中出现异常,则直接抛出
try {
Method method = invoker.getInterface().getMethod(RpcUtils.getMethodName(invocation), invocation.getParameterTypes());
Class<?>[] exceptionClasses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClasses) {
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException e) {
return;
}
// 对于在方法的签名中找不到的异常,请在服务器的日志中打印ERROR消息。
logger.error(CONFIG_FILTER_VALIDATION_EXCEPTION, "", "",
"Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().getRemoteHost() +
". service: " + invoker.getInterface().getName() + ", method: " + RpcUtils.getMethodName(invocation) +
", 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;
}
// 如果是JDK异常,则直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.") || className.startsWith("jakarta.")) {
return;
}
// 如果是dubbo异常,则直接抛出
if (exception instanceof RpcException) {
return;
}
// 自定义异常直接返回
if (exception instanceof GdsException) {
return;
}
// 否则,请使用RuntimeException进行包装并返回到客户端
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn(CONFIG_FILTER_VALIDATION_EXCEPTION, "", "",
"Fail to ExceptionFilter when called by " + RpcContext.getServiceContext().getRemoteHost() +
". service: " + invoker.getInterface().getName() + ", method: " + RpcUtils.getMethodName(invocation) +
", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
}
}
@Override
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
logger.error(CONFIG_FILTER_VALIDATION_EXCEPTION, "", "",
"Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().getRemoteHost() +
". service: " + invoker.getInterface().getName() + ", method: " + RpcUtils.getMethodName(invocation) +
", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
// For test purpose
public void setLogger(ErrorTypeAwareLogger logger) {
this.logger = logger;
}
}
resources目录结构
org.apache.dubbo.rpc.Filter文件内容
exceptionFilter=com.xqt.common.dubbo.filter.ExceptionFilter
配置文件
dubbo:
provider:
# 去除dubbo默认自带的异常过滤器
filter: -exception
PS: dubbo2.x版本上面的方案就可以解决, 但如果是3.x版本, 还会出现其他问题
问题1: 反序列化问题
错误信息
org.apache.dubbo.common.serialize.SerializationException: java.lang.IllegalArgumentException: [Serialization Security] Serialized class com.xqt.common.core.exception.GdsException is not in allow list. Current mode is
STRICT
, will disallow to deserialize it by default. Please add it into security/serialize.allowlist or follow FAQ to configure it.
解决
在配置文件中修改序列化检查模式
dubbo在3.2以前默认的检查模式为WARN, 而3.2以后改为了STRICT
dubbo:
application:
# 检查模式
serializeCheckStatus: WARN
STRICT : 严格检查,禁止反序列化所有不在允许序列化列表(白名单)中的类
WARN : 仅禁止序列化所有在不允许序列化列表中(黑名单)的类,同时在反序列化不在允许序列化列表(白名单)中类的时候通过日志进行告警。
DISABLE 禁用, 不进行任何检查
问题2: 自定义异常可以正常抛出, 但是没有状态码/状态码描述等问题
解决
还是序列化问题, 自定义异常中的属性与父类中的属性名一致, 导致序列化出现了问题 ,更改属性名解决