dubbo参数校验ValidationFilter使用与说明

 更新时间:2025年10月20日 10:08:31   作者:hello_zzw  
本文介绍了Dubbo框架中Filter的核心功能和实现机制,Filter用于拦截RPC调用流程,支持自定义拦截逻辑,并通过SPI扩展机制动态加载,形成链式调用结构,文中还说明了内置和自定义Filter的实现方式及ValidationFilter参数校验功能

org.apache.dubbo.rpc.Filter

核心功能

  • 拦截RPC调用流程

Filter是Dubbo框架中实现拦截逻辑的核心接口,作用于服务消费者和提供者的作业链路,支持在方法调用前后插入自定义逻辑。如参数校验、异常处理、日志记录等。

  • 扩展性机制

Dubbo通过SPI扩展机制动态加载Filter实现类,构建链式调用结构,每个Filter通过Invoke方案传递调用上下文,最终执行目标方法。

实现机制

  • 责任链模式

Provider端Filter链在服务暴露时通过FilterChainBuilder#buildInvokerChain方法构建,基于SPI配置按优先级排序,形成多层拦截逻辑。

  • SPI加载规则

Filter实现类需要在META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter文件中声明,并通过@Activate注解配置激活条件(如服务端/消费端)

  • 动态加载

Filter链在服务初始化阶段动态生成,通过ExtensionLoader加载所有激活的Filter实例,并按顺序包装成调用链。

常见内置Filter实现

Filter名称功能描述适用端
ExceptionFilter统一处理服务端异常,将非受检异常封装为RuntimeException返回客户端Provider
ValidationFilter基于JSR303标准校验接口参数合法性Both
AccessLogFilter记录服务调用日志,指定输出到指定文件Provider
TimeoutFilter监控方法执行超时,触发超时中断逻辑Provider
GenericFilter处理泛化调用的序列化与反序列化Both

自定义Filter实现步骤

  • 实现Filter接口
import com.alibaba.fastjson2.JSON;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// 使用@Activate注解指定Filter生效场景
// order属性控制执行顺序,值越小,优先级越高
@Activate(group = {CommonConstants.CONSUMER, CommonConstants.PROVIDER}, order = 10001)
public class CustomFilter implements Filter {
    private Logger logger = LoggerFactory.getLogger(CustomFilter.class);
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        logger.info("invoker invoked method {} {} {} {}",
                invocation.getMethodName(),
                JSON.toJSONString(invocation.getObjectAttachments()),
                invocation.getAttributes(),
                JSON.toJSONString(invocation.getArguments()));
        Result result = invoker.invoke(invocation);
        logger.info("invoker invoked result {}", JSON.toJSONString(result));
        return result;
    }
}
  • ‌声明SPI扩展‌

在resources/META-INF/dubbo目录下创建配置文件org.apache.dubbo.rpc.Filter,添加自定义Filter类路径:

consumer=com.doudou.demo.filter.CustomFilter

ValidationFilter

Dubbo的ValidationFilter是基于JSR303标准实现的参数校验组件,主要用于服务消费者和服务提供者两端,确保接口调用时参数的合法性。

核心特性

作用机制

  • 通过@Activate注解激活,默认作用于消费者和服务者两端,执行顺序为10000。
  • 在请求处理前拦截参数,利用JSR303标准的注解进行校验,校验失败时抛出异常中断流程。

依赖配置

需要引入validation-apihibernate-validator依赖包

 <!-- Bean Validation API -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<!-- Hibernate Validator实现 -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.5.Final</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId> <!-- 适配EL表达式 -->
    <version>5.0.0-M1</version>
</dependency>

使用

API

@Setter
@Getter
public class UserDTO implements Serializable {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Min(value = 18, message = "年龄必须大于18岁")
    private Integer age;

    @Email(message = "邮箱格式不合法")
    private String email;
}
public class BaseResult<T> {
    // 处理是否正确结束
    private boolean success;
    // 异常编码
    private Integer errorCode;
    // 异常描述
    private String errorMsg;
    // dubbo接口返回的结果
    private T data;
}    
public interface UserService {
    BaseResult<String> registerUser(UserDTO userDTO);
}

服务提供者

@DubboService(validation = "true")
public class UserServiceImpl implements UserService {

    @Override
    public BaseResult<String> registerUser(UserDTO userDTO) {
        return BaseResult.success("用户注册成功:" + userDTO.getUsername());
    }
}
public class ParameterVerificationResultFilter implements Filter {
    private Logger logger = LoggerFactory.getLogger(ParameterVerificationResultFilter.class);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Result result = invoker.invoke(invocation);
        // 处理出现异常
        if (result.hasException()) {
            Throwable exception = result.getException();
            // 是由参数校验失败抛出的异常
            if (exception instanceof ConstraintViolationException) {
                List<String> errors = ((ConstraintViolationException) exception).getConstraintViolations().stream()
                        .map(v -> v.getPropertyPath() + ": " + v.getMessage())
                        .collect(Collectors.toList());
                logger.info("---------------2---------------");
                logger.error(errors.toString());
                logger.info("---------------3---------------");
                // 将错误信息封装到返回结果中
                return AsyncRpcResult.newDefaultAsyncResult(BaseResult.fail(400, errors.toString()), invocation);
            }
        }
        return result;
    }
}

META-INF/dubbo/org.apache.dubbo.rpc.Filter

# 参数校验过滤器
validation=org.apache.dubbo.validation.filter.ValidationFilter
# 校验结果处理过滤器
parameterVerification=com.doudou.filter.ParameterVerificationResultFilter

application.yml

dubbo:
	provider:
		filter: validation,parameterVerification

服务消费方

@RestController
public class UserServiceController {
    @DubboReference(validation = "true")
    private UserService userService;

    @PostMapping("/test")
    public BaseResult<String> test(@RequestBody UserDTO userDTO) {
        return userService.registerUser(userDTO);
    }
}
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理RpcException异常
    @ExceptionHandler(RpcException.class)
    public ResponseEntity<BaseResult> handleValidationException(RpcException rpcException) {
        return ResponseEntity.badRequest().body(BaseResult.fail(403, rpcException.getLocalizedMessage()));
    }
}

源码解析

org.apache.dubbo.validation.filter.ValidationFilter

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
   // 判断是否需要进行参数验证
    if (needValidate(invoker.getUrl(), invocation.getMethodName())) {
        try {
            // 通过url中的validation属性值获取验证器
            Validator validator = validation.getValidator(invoker.getUrl());
            if (validator != null) {
                // 获取到验证器时,进行参数验证
                validator.validate(
                        invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
            }
        } catch (RpcException e) {
            // RpcException 异常直接抛出
            throw e;
        } catch (Throwable t) {
            // 非RpcException,封装到结果中返回
            return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
        }
    }
    // 执行下一个过滤器的处理
    return invoker.invoke(invocation);
}

 /**
  * 是否需要进行参数验证
  */
 private boolean needValidate(URL url, String methodName) {
     return validation != null
             && !methodName.startsWith("$")
             && ConfigUtils.isNotEmpty(url.getMethodParameter(methodName, VALIDATION_KEY))
             && !"false".equalsIgnoreCase(url.getParameter(VALIDATION_KEY));
 }

org.apache.dubbo.validation.support.AbstractValidation

@Override
public Validator getValidator(URL url) {
    // 使用url作为存储验证器map集合的的key
    String key = url.toFullString();
    // 从容器中获取验证器
    Validator validator = validators.get(key);
    // 判断验证器是否已经存在
    if (validator == null) {
        // 如果不存在,则创建
        validators.put(key, createValidator(url));
        validator = validators.get(key);
    }
    return validator;
}

org.apache.dubbo.validation.support.jvalidation.JValidation

@Activate(onClass = "javax.validation.Validation")
public class JValidation extends AbstractValidation {

    /**
     * Return new instance of {@link JValidator}
     * @param url Valid URL instance
     * @return Instance of JValidator
     */
    @Override
    protected Validator createValidator(URL url) {
        // 创建一个Dubbo框架默认的校验器
        return new JValidator(url);
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java JSch远程执行Shell命令的方法

    Java JSch远程执行Shell命令的方法

    本文主要介绍了Java JSch远程执行Shell命令,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • SpringBoot修改内置tomcat版本的操作步骤

    SpringBoot修改内置tomcat版本的操作步骤

    生产环境使用的外部部署Tomcat还是内置Tomcat由于版本安全漏洞,往往需要升级到指定的安全版本,本文演示一下SpringBoot升级内置的Tomcat版本,感兴趣的小伙伴跟着小编一起来看看吧
    2024-07-07
  • Spring Framework常用面试题及答案汇总

    Spring Framework常用面试题及答案汇总

    这篇文章主要介绍了Spring Framework常用面试题及答案汇总,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • java根据当前时间获取yyyy-MM-dd HH:mm:ss标准格式的时间代码示例

    java根据当前时间获取yyyy-MM-dd HH:mm:ss标准格式的时间代码示例

    在Java中可以使用java.time包中的LocalDateTime类和DateTimeFormatter类来获取并格式化当前时间为yyyy-MM-dd HH:mm:ss的格式,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-10-10
  • 使用IntelliJ IDEA调式Stream流的方法步骤

    使用IntelliJ IDEA调式Stream流的方法步骤

    本文主要介绍了使用IntelliJ IDEA调式Stream流的方法步骤,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • 关于Android触摸事件分发的原理详析

    关于Android触摸事件分发的原理详析

    触摸事件分发机制一直以来都是Android中比较重要的一大块,自定义view,各种复杂的自定义手势交互都与触摸事件分发机制关系密,下面这篇文章主要给大家介绍了关于Android触摸事件分发原理的相关资料,需要的朋友可以参考下
    2022-01-01
  • IDEA 通过脚本配置终端提示符样式的方法

    IDEA 通过脚本配置终端提示符样式的方法

    这篇文章给大家介绍IDEA通过脚本配置终端提示符样式的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-08-08
  • SpringBoot整合kafka遇到的版本不对应问题及解决

    SpringBoot整合kafka遇到的版本不对应问题及解决

    这篇文章主要介绍了SpringBoot整合kafka遇到的版本不对应问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java的四种引用方式

    Java的四种引用方式

    这篇文章主要介绍了Java的四种引用方式,Java的引用方式主要包括强引用、软引用、弱引用、虚引用;下面文章便来详细介绍这四种引用方式,需要的朋友可以参考一下
    2021-10-10
  • Flink流处理引擎零基础速通之数据的抽取篇

    Flink流处理引擎零基础速通之数据的抽取篇

    今天不分享基础概念知识了,来分享一个马上工作需要的场景,要做数据的抽取,不用kettle,想用flink。实际就是flink的sql、table层级的api
    2022-05-05

最新评论