Dubbo过滤器 - ExceptionFilter

概述

ExceptionFilter 是服务端的过滤器,它不是一个统一处理异常的过滤器,关注点不在于捕获异常,而是为了找到那些返回的自定义异常,会把异常包装成 RuntimeException 。 因为异常类可能不存在于消费端,避免消费端出现不能反序列化异常的情况。

ExceptionFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@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;
}

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
// 服务调用
Result result = invoker.invoke(invocation);

// 1. 泛化调用直接抛出,非泛化调用分类处理
if (result.hasException() && GenericService.class != invoker.getInterface()) {
try {
// 获取异常对象
Throwable exception = result.getException();

// 2. 如果是 checked 异常,则直接抛出
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}

// 3. 在方法签名上有声明,直接抛出
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;
}

// 未在方法签名上定义的异常,在服务端打印 错误日志
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);

// 4. 异常类和接口类在同一个 jar 包里,直接抛出
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return result;
}

// 5. 是JDK自带的异常,直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}

// 6. 是Dubbo中定义异常,直接抛出
if (exception instanceof RpcException) {
return result;
}

// 7. 否则,包装成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 内部的异常处理类 ExceptionFilter 的处理逻辑如上面代码所示,其中 1-6 种情况客户端都能反序列化成功,第 7 步是一个兜底操作,为了防止客户端反序列化异常失败。

异常处理

Dubbo 官方推荐异常处理方式如下:

1
2
3
4
5
6
7
* 建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。

* 如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace() 方法为空方法,使其不拷贝栈信息。

* 查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch,并且不能进行有效处理。

* 服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。

自定义异常

通过 ExceptionFilter 源码可知,自定义的异常如果不符合前面的 6 种情况,那么最后返回给客户端的异常会被包装成 RuntimeException ,自定义的异常也就无效了。既然如此,能不能剔除掉 ExceptionFilter 过滤器,这样就不会将自定义的异常自动包装了。但是这样的话又回到了最开始的痛点,没有 ExceptionFilter 过滤器处理客户端可能无法正确反序列化自定义的异常。因此,我们就可以自定义一个 Filter 来统一处理这种情况。

BizException

一般自定义异常都是unchecked 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class BizException extends RuntimeException {
private static final long serialVersionUID = 8056580241015398148L;
/**
* 异常业务编码
*/
private String code;

/**
* 默认异常构造器.
*/
public BizException() {
super();
}

/**
* 根据异常信息和原生异常构造对象.
*
* @param code 错误码
* @param msg 异常信息.
* @param cause 原生异常.
*/
public BizException(final String code, final String msg, final Throwable cause) {
super(msg, cause);
this.code = code;
}

/**
* 根据异常构造业务对象,设置 编码及 消息
*
* @param code
* @param msg
*/
public BizException(final String code, final String msg) {
super(msg);
this.code = code;
}

/**
* 根据异常信息和原生异常构造对象.
*
* @param msg 异常信息.
* @param cause 原生异常.
*/
public BizException(final String msg, final Throwable cause) {
super(msg, cause);
}

/**
* 根据异常信息构造对象.
*
* @param msg 异常信息.
*/
public BizException(final String msg) {
super(msg);
}

/**
* 根据异常构造业务对象,设置 编码及 消息
*
* @param resultCode
*/
public BizException(final ResultCode resultCode) {
super(resultCode.getMsg());
this.code = resultCode.getCode();
}

/**
* 根据异常构造业务对象,设置 编码及 消息
*
* @param resultCode
*/
public BizException(final ResultCode resultCode, final Throwable cause) {
super(resultCode.getMsg(), cause);
this.code = resultCode.getCode();
}

public String getCode() {
return code;
}

public void setCode(String code) {
this.code = code;
}

/**
* 根据原生异常构造对象.
*
* @param cause 原生异常.
*/
public BizException(final Throwable cause) {
super(cause);
}

@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}

自定义 Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Activate(group = {CommonConstants.PROVIDER}, order = 999999)
public class ProviderExceptionFilter implements Filter {
private Environment environment;

public Environment getEnvironment() {
return environment;
}

public void setEnvironment(Environment environment) {
this.environment = environment;
}


@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
JsonKvFormat kvFormat = makeJsonKvFormat(invoker, invocation);
Result result = null;
try {
long startTime = System.currentTimeMillis();
// invoke
result = invoker.invoke(invocation);
long endTime = System.currentTimeMillis();
long timeOut = Long.parseLong(environment.getProperty("time.out"));
if ((endTime - startTime) > timeOut) {
recordTimeOutLog(kvFormat, endTime - startTime);
}
// 判断调用是否有异常
if (result.hasException() && GenericService.class != invoker.getInterface()) {
Throwable exception = result.getException();
// 是否是自定义异常
if (exception instanceof BizException) {
String code = ((BizException) exception).getCode();
recordBizLog(kvFormat, result);
result.setValue(com.yunhu.rpc.result.Result.failResult(code, exception.getMessage()));
// 是否是不记录日志的自定义异常
} else if (exception instanceof NoRecordException) {
String code = ((NoRecordException) exception).getCode();
result.setValue(com.yunhu.rpc.result.Result.failResult(code, exception.getMessage()));

// 非自定义异常统一处理
} else {
recordErrorLog(kvFormat, exception);
result.setValue(com.yunhu.rpc.result.Result.failResult(BizExceptionEnum.SYSTEM_ERROR.getCode(), BizExceptionEnum.SYSTEM_ERROR.getMessage()));
}

//清除异常,否则调用方会以运行时异常报错!
result.setException(null);
return result;
}
return result;

} catch (Exception e) {
recordErrorLog(kvFormat, e);
result.setException(null);
result.setValue(com.yunhu.rpc.result.Result.failResult(BizExceptionEnum.SYSTEM_ERROR.getCode(), BizExceptionEnum.SYSTEM_ERROR.getMessage()));
}
return result;
}

说明:

  1. 这里自定义 Filter 的前提是返回结果是以状态码表示的,异常的处理最终也是通过状态码返回。
  2. 自定义的 Filter 的优先级相比较 Dubbo 内置的 ExceptionFilter 的优先级要高。即使调用出现异常,自定义 Filter 会清除异常信息,到 ExceptionFilter 时就不会走异常处理流程了。

小结

本篇文章对 Dubbo 框架的异常处理过滤器进行了介绍,并实现了自定义的异常处理。