java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > dubbo参数校验ValidationFilter

dubbo参数校验ValidationFilter使用与说明

作者:hello_zzw

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

org.apache.dubbo.rpc.Filter

核心功能

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

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

实现机制

Provider端Filter链在服务暴露时通过FilterChainBuilder#buildInvokerChain方法构建,基于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实现步骤

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;
    }
}

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

consumer=com.doudou.demo.filter.CustomFilter

ValidationFilter

Dubbo的ValidationFilter是基于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);
    }
}

总结

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

您可能感兴趣的文章:
阅读全文