@SPI
public interface Filter {
/**
* Make sure call invoker.invoke() in your implementation.
*/
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
interface Listener {
void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation);
void onError(Throwable t, Invoker<?> invoker, Invocation invocation);
}
会在启动时ProtocolFilterWrapper中被组装成链式拦截器,在每次dubbo接生产者/消费者都执行这些拦截器
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 {
Result asyncResult;
try {
asyncResult = filter.invoke(next, invocation);
} catch (Exception e) {
if (filter instanceof ListenableFilter) {
ListenableFilter listenableFilter = ((ListenableFilter) filter);
try {
Filter.Listener listener = listenableFilter.listener(invocation);
if (listener != null) {
listener.onError(e, invoker, invocation);
}
} finally {
listenableFilter.removeListener(invocation);
}
} else if (filter instanceof Filter.Listener) {
Filter.Listener listener = (Filter.Listener) filter;
listener.onError(e, invoker, invocation);
}
throw e;
} finally {
}
return asyncResult.whenCompleteWithContext((r, t) -> {
if (filter instanceof ListenableFilter) {
ListenableFilter listenableFilter = ((ListenableFilter) filter);
Filter.Listener listener = listenableFilter.listener(invocation);
try {
if (listener != null) {
if (t == null) {
listener.onResponse(r, invoker, invocation);
} else {
listener.onError(t, invoker, invocation);
}
}
} finally {
listenableFilter.removeListener(invocation);
}
} else if (filter instanceof Filter.Listener) {
Filter.Listener listener = (Filter.Listener) filter;
if (t == null) {
listener.onResponse(r, invoker, invocation);
} else {
listener.onError(t, invoker, invocation);
}
}
});
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
首先通过 ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);获取生效的filter且匹配的Filter
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> activateExtensions = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
List<T> loadedExtensions = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(0, loadedExtensions);
loadedExtensions.clear();
}
} else {
loadedExtensions.add(getExtension(name));
}
}
}
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(loadedExtensions);
}
return activateExtensions;
}
调用逻辑为挨个执行filter的invoke方法,如果filter还继承了Listener接口,那么还会执行结果是否有异常分别调用onResponse或者onError方法
例如dubbo自带的异常处理filter
@Activate(group = CommonConstants.PROVIDER)
public class ExceptionFilter implements Filter, Filter.Listener {
private Logger logger = LoggerFactory.getLogger(ExceptionFilter.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();
// directly throw if it's checked exception
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return;
}
// directly throw if the exception appears in the signature
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException e) {
return;
}
// for the exception not found in method's signature, print ERROR message in server's log.
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);
// directly throw if exception class and interface class are in the same jar file.
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return;
}
// directly throw if it's JDK exception
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return;
}
// directly throw if it's dubbo exception
if (exception instanceof RpcException) {
return;
}
// otherwise, wrap with RuntimeException and throw back to the client
appResponse.setException(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);
}
}
}
@Override
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
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);
}
// For test purpose
public void setLogger(Logger logger) {
this.logger = logger;
}
}
利用dubbo的spi机制@Activate(group = CommonConstants.PROVIDER))表示在生产者端生效
在onResponse方法中对异常进行处理
针对特定异常,包装成RpcContentResponse,并且能够对错误日志做比较完整的输出
public class RpcContentResponse<T> implements Serializable {
private static final long serialVersionUID = 3302228036840460883L;
/**
* 业务状态码
*/
private int code;
/**
* 消息
*/
private String msg;
/**
* 业务数据
*/
private T data;
}
同样对一些异常直接抛出,如果异常和方法同属一个jar或者运行时异常进行处理。
/**
* {@link org.apache.dubbo.rpc.filter.ExceptionFilter}
*
* @program: output
* @description:
* @create: 2022-04-11 18:29
*/
@Slf4j
@Activate(group = {CommonConstants.PROVIDER}, order = 10000)
public class RPCExceptionFilter implements Filter {
private final WeChatWebhookConfig weChatWebhookConfig = ApplicationContextUtils.getBean(WeChatWebhookConfig.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result appResponse = invoker.invoke(invocation);
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
// directly throw if it's checked exception
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return appResponse;
}
// directly throw if the exception appears in the signature
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClasses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClasses) {
if (exception.getClass().equals(exceptionClass)) {
return appResponse;
}
}
} catch (NoSuchMethodException e) {
return appResponse;
}
// for the exception not found in method's signature, print ERROR message in server's log.
log.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);
// directly throw if exception class and interface class are in the same jar file.
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (DubboFilterUtils.isCustomizeException(exception)
|| serviceFile == null
|| exceptionFile == null
|| serviceFile.equals(exceptionFile)) {
return exceptionHandler(exception, invocation);
}
// directly throw if it's JDK exception
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return appResponse;
}
// directly throw if it's dubbo exception
if (exception instanceof RpcException) {
return appResponse;
}
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.unknownError(), invocation);
} catch (Throwable e) {
log.error("Fail to ExceptionFilter when called by "
+ RpcContext.getContext().getRemoteHost()
+ ". service: "
+ invoker.getInterface().getName()
+ ", method: "
+ invocation.getMethodName()
+ ", exception: "
+ e.getClass().getName()
+ ": "
+ e.getMessage(), e);
return appResponse;
}
}
return appResponse;
}
private Result exceptionHandler(Throwable e, Invocation invocation) {
log.error("错误信息:{}", e.getMessage(), e);
if (e instanceof IllegalOperationException) {
return AsyncRpcResult.newDefaultAsyncResult(
BaseResponse.construct(StatusCode.FORBIDDEN_OPERATION, e.getMessage()),
invocation
);
} else if (e instanceof InvalidParameterException) {
return AsyncRpcResult.newDefaultAsyncResult(
BaseResponse.construct(StatusCode.INVALID_PARAMETER, e.getMessage()),
invocation
);
} else if (e instanceof com.yiyouliao.rivers.content.api.exception.InvalidParameterException) {
return AsyncRpcResult.newDefaultAsyncResult(
BaseResponse.construct(StatusCode.INVALID_PARAMETER, e.getMessage()),
invocation
);
} else if (e instanceof ResourceNotFoundException) {
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.construct(StatusCode.RE_NOT_FOUND, e.getMessage()), invocation);
} else if (e instanceof ResourceAlreadyExistException) {
return AsyncRpcResult.newDefaultAsyncResult(
BaseResponse.construct(StatusCode.RE_ALREADY_EXIST, e.getMessage()),
invocation
);
} else if (e instanceof IllegalStateException) {
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.construct(StatusCode.ILLEGAL_STATE, e.getMessage()), invocation);
} else if (e instanceof UnsupportedEnumValueException) {
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.construct(StatusCode.UNKNOWN_ENUM, e.getMessage()), invocation);
} else if (e instanceof UnauthorizedException) {
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.construct(StatusCode.UNAUTHORIZED, e.getMessage()), invocation);
} else if (e instanceof InvalidTokenException) {
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.construct(StatusCode.INVALID_TOKEN, e.getMessage()), invocation);
} else if (e instanceof RemoteServiceException) {
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.construct(StatusCode.REMOTE_SERVICE, e.getMessage()), invocation);
} else {
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.unknownError(), invocation);
}
}
}
这样rpc调用时发生异常的情况下,不仅可以转换成Response对象返回给消费者,还可以打印出我们需要的日志。
配置在META-INFdubbo.org.apache.dubbo.rpc.Filter
rpcExceptionFilter = com.xxx.RPCExceptionFilter
dubbo:
provider:
filter: default,-exception,rpcExceptionFilter
-exception代表取消生效dubbo自带的ExceptionFilter,使用我们配置的rpcExceptionFilter。
具逻辑体在源码,入参values就是我们的配置org.apache.dubbo.common.extension.ExtensionLoader#getActivateExtension(org.apache.dubbo.common.URL, java.lang.String[], java.lang.String)中
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> activateExtensions = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
List<T> loadedExtensions = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(0, loadedExtensions);
loadedExtensions.clear();
}
} else {
loadedExtensions.add(getExtension(name));
}
}
}
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(loadedExtensions);
}
return activateExtensions;
}
dubbo rpc层面采用的是JSR303标准注解验证
例如
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
public class UserSaveArgs implements Serializable {
private static final long serialVersionUID = -5457103531947646047L;
@NotEmpty(
message = "用户名不能为空")
@Pattern(regexp = Constants.USERNAME_PATTERN, message = "用户名格式有误")
private String userName;
@NotEmpty(
message = "手机号不能为空")
@Pattern(regexp = Constants.PHONTE_NUMBER_PATTERN, message = "手机号编码格式有误")
private String phoneNumber;
@NotNull(
message = "年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
private Integer age;
//get set 省略
无需写校验代码,就能自动的对字段的进行校验
@Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = 10000)
public class ValidationFilter implements Filter {
private Validation validation;
/**
* Sets the validation instance for ValidationFilter
* @param validation Validation instance injected by dubbo framework based on "validation" attribute value.
*/
public void setValidation(Validation validation) {
this.validation = validation;
}
/**
* Perform the validation of before invoking the actual method based on validation attribute value.
* @param invoker service
* @param invocation invocation.
* @return Method invocation result
* @throws RpcException Throws RpcException if validation failed or any other runtime exception occurred.
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (validation != null && !invocation.getMethodName().startsWith("$")
&& ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), VALIDATION_KEY))) {
try {
Validator validator = validation.getValidator(invoker.getUrl());
if (validator != null) {
validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
}
} catch (RpcException e) {
throw e;
} catch (ValidationException e) {
// only use exception's message to avoid potential serialization issue
return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(e.getMessage()), invocation);
} catch (Throwable t) {
return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
}
}
return invoker.invoke(invocation);
}
}
默认在生产者和消费者都生效。但是ValidationFilter多出来了一个 value = VALIDATION_KEY
需要配置开启校验,拦截器才会生效
首先配置dobbo 的rpc 层校验
dubbo:
consumer:
validation: true
其中Validation是关键属性,是dubbo通过spi自动注入的。这里不研究。大概是能够根据接口入参定义的校验注解生成对应的校验。根据url进行匹配.
在默认拦截器的基础上,增加更多对的日志,对于一些特殊校验能够发送通知
@Slf4j
@Activate(group = {CommonConstants.PROVIDER}, value = FilterConstants.VALIDATION_KEY, order = 10000)
public class CustomValidationFilter implements Filter {
private Validation validation;
public void setValidation(Validation validation) {
this.validation = validation;
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
log.info("{}\t:请求参数:{}", invocation.getMethodName(),
JSONUtil.toJsonStr(invocation.getArguments()));
Validator validator = this.validation.getValidator(invoker.getUrl());
if (validator != null) {
validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
}
} catch (ConstraintViolationException e) {
log.error("错误信息 ConstraintViolationException:{}", e.getMessage(), e);
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
List<String> message = constraintViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
log.error("invocation:{}, serviceName:{}, methodName:{} errMsg:{}",
invocation,
invocation.getServiceName(),
invocation.getMethodName(),
message
);
//还可以自定义通知,发送邮件等形式
return AsyncRpcResult.newDefaultAsyncResult(BaseResponse.construct(StatusCode.INVALID_PARAMETER, message.toString()),
invocation
);
} catch (RpcException var4) {
log.error("错误信息 RpcException:{}", var4.getMessage(), var4);
throw var4;
} catch (Throwable t) {
log.error("错误信息 Throwable:{}", t.getMessage(), t);
return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
}
return invoker.invoke(invocation);
}
}
配置在META-INFdubbo.org.apache.dubbo.rpc.Filter
customValidationFilter = com.xxx.CustomValidationFilter
配置文件里指定
dubbo.provider.filter = default,-validation,-exception,rpcExceptionFilter