Dubbo实战-自定义拦截器

Filter接口介绍

@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

默认异常处理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方法中对异常进行处理

  1. 如果不是运行时异常,直接抛出
  2. 如果,方法了异常,直接抛出
  3. 如果,异常时方法没有声明的,打印错误日志
  4. 如果是jdk或者,dubbo自带异常,直接抛出
  5. 如果异常和方法同属一个jar包,直接抛出
  6. 其他情况,转换成RuntimeException抛出

需求

针对特定异常,包装成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 <b>validation</b> 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 层校验

  • 一: 在 @org.apache.dubbo.config.annotation.Service指定:
  • @Service(version = “1.0.0”, protocol = “dubbo”, validation =“true”); 具体实现由ValidationFilter实现
  • 然后同时在consumer的@Reference中指定,
  • @Reference(version = “1.0.0”, protocol = “dubbo”, validation = “true”)';
  • 二: 全局指定消费者验证,在yml中指定’dubbo.consumer. validation’
dubbo:
  consumer:
    validation: true 

其中Validation是关键属性,是dubbo通过spi自动注入的。这里不研究。大概是能够根据接口入参定义的校验注解生成对应的校验。根据url进行匹配.
image.png

需求

在默认拦截器的基础上,增加更多对的日志,对于一些特殊校验能够发送通知

自定义拦截器

@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

猜你喜欢

转载自blog.csdn.net/qq_37436172/article/details/131341141