Dubbo接口参数校验的正确姿势

Dubbo接口参数校验的正确姿势

1.前言

  由于之前的文章分享了如下这篇文章:

  @Validated或@Valid参数注解校验、自定义手机号注解检验及优雅统一异常处理

https://mp.weixin.qq.com/s/o3SqNXO8BFEq2YsrlcEixA
https://blog.csdn.net/qq_34905631/article/details/137821646?spm=1001.2014.3001.5501

  后面在项目中使用dubbo接口,就想能不能让dubbo接口也能参数校验,那么就不用在代码中写很多的if/else的参数校验了,直接dubbo的参数校验就给做了,那这种接口代码是不是又优雅、干净整洁了很多,于是乎我就上网开始看了一个些千篇一律的文章,没有一篇可以的,然后就经过不断的尝试之后,dubbo接口参数校验的正确姿势还是被我搞出来了,下面就分享给大家。

2.代码示例

2.1版本

  dubbo版本2.7.13,至于项目中如何整合dubbo之前的文章中都有分享或者可以去dubbo官网查看.

  在dubbo2.7.x版本duboo开始进入Apache,所以之前下面自定义的注解filter的名字是如下的名字:

org.apache.dubbo.rpc.Filter

  不在是之前alibaba那种命名了,这里需要特别注意。

2.2校验依赖

  在dubbo的api接口的项目pom中引入校验依赖跟上面之前分享的文章的依赖可以说是一样

        方式一;
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
        方式二;
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.2.0.Final</version>
        </dependency>

  方式一和方式二任选一种引入依赖即可。

2.3自定义DubboValidationFilter

  在resources下新建一个目录:META-INF.dubbo下面新建一个如下文件;

org.apache.dubbo.rpc.Filter

image-20240518185304186

  文件内容如下;

dubboValidationFilter=xx.xx.xxxx.config.DubboValidationFilter

  DubboValidationFilter类如下:

package xxxx.config;

import cn.hutool.core.collection.CollectionUtil;
import xxxx.RestResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.rpc.AsyncRpcResult;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.validation.Validation;
import org.apache.dubbo.validation.Validator;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
import static org.apache.dubbo.common.constants.FilterConstants.VALIDATION_KEY;

/**
 * zlf
 */
@Slf4j
@Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = -1)
public class DubboValidationFilter implements Filter {

    private Validation validation;

    /**
     * Sets the validation instance for DubboValidationFilter
     *
     * @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) {
                log.error("DubboValidationFilter.RpcException:{}", e.getMessage());
                throw e;
            } catch (ValidationException e) {
                // only use exception's message to avoid potential serialization issue
                String message = e.getMessage();
                if (e instanceof ConstraintViolationException) {
                    ConstraintViolationException ex = (ConstraintViolationException) e;
                    // 可能存在多个验证结果 因此组装返回
                    List<String> messageArray = new ArrayList<String>();
                    Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
                    if (CollectionUtil.isNotEmpty(constraintViolations)) {
                        constraintViolations.forEach(constraintViolation -> {
                            messageArray.add(constraintViolation.getMessage());
                        });
                    }
                    message = messageArray.size() == 1 ? messageArray.get(0) : String.join(",", messageArray);
                }
                //return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(message), invocation);
                //这里的RestResponse是业务自己定义的一个响应的类,可以根据自己的需求去新建一个RestResponse类或这个是使用上面注释的代码放开即可,直接抛接口异常给调用方也是可以的
                return AsyncRpcResult.newDefaultAsyncResult(RestResponse.fail(message), invocation);
            } catch (Throwable t) {
                log.error("DubboValidationFilter.Throwable:{}", t.getMessage());
                return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
            }
        }
        return invoker.invoke(invocation);
    }

}

2.4分组校验

  ValidationGroups类如下:

public class ValidationGroups {

    public interface Update {
    }

    public interface Insert {
    }

    public interface Detail {

    }

    public interface Delete{

    }

}

  dubbo接口api如下,dubbo接口参数分组校验需要加@MethodValidated注解,注解的value是一个数组里面可以写多个参数,使用@MethodValidated注解就可做分组校验,多个接口的参数实体可以公用一个类

import org.apache.dubbo.validation.MethodValidated;

import javax.validation.constraints.NotNull;

public interface xxxApi {

    //新增接口只校验新增接口的参数
    @MethodValidated(ValidationGroups.Insert.class)
    RestResponse add(@NotNull(message = "参数不为空!") xxxDto dto);
    
    //编辑接口校验插入接口的参数和编辑接口的参数
    @MethodValidated({ValidationGroups.Insert.class, ValidationGroups.Update.class})
    RestResponse edit(@NotNull(message = "参数不为空!") xxxDto dto);


}

  xxxDto类如下:

package xxx.dto;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

@Data
public class xxxDto implements Serializable {

    private static final long serialVersionUID = 2328308931670082723L;

    /**
     * 主键
     */
    @NotNull(message = "id不为空", groups = {ValidationGroups.Update.class})
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

    /**
     * 登录账号
     */
    @NotBlank(message = "账号不能为空", groups = {ValidationGroups.Insert.class, ValidationGroups.Update.class})
    private String account;


}

  dubbo接口实现类:

package xxx.service.dubbo;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Value;

import java.util.Objects;

@Slf4j
@DubboService(version = "${dubbo.application.version}", timeout = 3000, delay = -1, retries = 0, validation = "true", filter = "-validation,dubboValidationFilter")
@RequiredArgsConstructor
public class xxxApiImpl implements xxxApi {


    @Override
    public RestResponse add(xxxDto dto) {
        ,,,,,,,,,,,,,,,,,,,,,,
        return RestResponse.success();
    }

    @Override
    public RestResponse edit(xxxDto dto) {
        ,,,,,,,,,,,,,,,,,,,,,,,,
        return RestResponse.success();
    }

}

2.5嵌套校验

  嵌套校验需要在xxxDto类的某个字段上加上@Valid注解,该注解的特性就是支持嵌套校验

@Valid
@NotEmpty(message = "List不为空", groups = {ValidationGroups.Insert.class, ValidationGroups.Update.class})
private List<xxxx2Dto> xDtos;

  xxxx2Dto类的字段上也是加参数校验的注解的,实体上的字段都是可以使用validation的校验注解的,根据自己的需求来使用即可。

2.6校验生效的方式

2.6.1yaml配置

dubbo:
  provider:
    filter: -validation #排除自带的那个validationFilter,-表示排除
    validation: true  # 服务提供者开启校验
  consumer:
    check: false
    validation: true # 消费者开启校验
  cloud:
    subscribed-services: ''
  scan:
    base-packages: xxx.service.dubbo
  protocol:
    name: dubbo
    port: -1
  registry:
    address: spring-cloud://localhost
  application:
    version: 1.0.0

2.6.2服务提供者接口配置

  xxxApiImpl类上的配置:配置了validation = “true”, filter = “-validation,dubboValidationFilter”,排除自带的validationFilter和指定了上面自定义的dubboValidationFilter

@DubboService(version = "${dubbo.application.version}", timeout = 3000, delay = -1, retries = 0, validation = "true", filter = "-validation,dubboValidationFilter")
@RequiredArgsConstructor
public class xxxApiImpl implements xxxApi {
}

2.6.3消费调用接口设置

  @DubboReference里面设置validation等于true,开启注解校验

@DubboReference(version = "${dubbo.application.version}", validation = "true")
private XxxxApi xxxxApi;

  上面的几种方式推荐使用2.6.1yaml配置总体配置之后就不用在dubbo接口实现类的注解上加参数配置了,或者是单独在服务提供者接口实现类上加,或者是在接口消费调用的地方设置,2.6.2服务提供者接口配置和2.6.3消费调用接口设置这两种方式就是使用起来会有点麻烦,有dubbo接口实现类就要写几次,有几个调用的地方就要写几次,所以还是yaml全局设置后就不用在服务提供者或服务消费者调用的时候去加了。

3.总结

  本文都是经过我亲测有效的,不像网上很多的坑文,千篇一律的,没有一个可以行的,使用这种方式就可以让接口优雅、干净、整洁、美观、工整,养成良好的编程习惯和代码风格,会减少很多bug的产生,更加专注于业务,而不至于由于参数校验有问题导致bug的出现,希望我的分享能给你有启发和帮助,请一键三连,么么么哒!

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值