dubbo参数校验ValidationFilter使用与说明
作者: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-api和hibernate-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);
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
