自定义头部 -->

Dubbo的参数校验

Dubbo的参数校验

Dubbo 的参数校验功能建立在JSR303 的基础之上, 并通过声明 filter 来实现验证 [2]

参考: Dubbo用户文档>实例>参数验证

pom.xml 依赖配置

    <properties>
        <validation.api.version>2.0.1.Final</validation.api.version>
        <javax.el.version>3.0.1-b11</javax.el.version>
    </properties>
 <dependencyManagement>
        <dependencies>
            <!-- validation -->
            <dependency>
                <groupId>javax.validation</groupId>
                <artifactId>validation-api</artifactId>
                <version>${validation.api.version}</version>
            </dependency>
            <!-- EL 表达式 -->
            <dependency>
                <groupId>org.glassfish</groupId>
                <artifactId>javax.el</artifactId>
                <version>${javax.el.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

   <dependencies>
            <!-- validation -->
            <dependency>
                <groupId>javax.validation</groupId>
                <artifactId>validation-api</artifactId>
                <version>${validation.api.version}</version>
            </dependency>
            <!-- EL 表达式 -->
            <dependency>
                <groupId>org.glassfish</groupId>
                <artifactId>javax.el</artifactId>
                <version>${javax.el.version}</version>
            </dependency>
   </dependencies>

SpecificationDTO请求实体

package com.barm.archetypes.api.domain.dto.spec;

import com.barm.common.domain.dto.spec.PageSpec;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;

/**
 * @author Allen
 * @version 1.0.0
 * @description UserMainPageSpec
 * @create 2020/3/16 13:59
 * @e-mail allenalan@139.com
 * @copyright 版权所有 (C) 2020 allennote
 */
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserMainSpec extends PageSpec {

    private static final long serialVersionUID = 1L;

    /** 昵称*/
    private String nickname;
    /** 用户名*/
    @NotNull(message = "用户名不能为空")
    private String username;
    /** 修改时间*/
    private LocalDateTime gmtModify;

}

Provider接口

public interface UserMainProvider {
    PageInfo<UserMainDTO> page(UserMainSpec spec);
}

测试 Controller

@RestController
public class UserMainController {

    @Resource
    private UserMainService userMainService;

    @GetMapping("page")
    public ResultVO page(UserMainSpec spec){
        return new ResultVO(userMainService.page(spec));
    }

}
package com.barm.common.domain.enums;
/**
 * @description 返回结果枚举
 *              1000000000
 *              10---------> 1~ 2 位: 消息提示类型 e.g. 10 正常, 20 系统异常, 30 业务异常
 *                0000-----> 3~ 6 位: 服务类型 e.g. 0001 用户服务
 *                    0000-> 7~10 位: 错误类型 e.g. 5000 参数校验错误
 * @author Allen
 * @version 1.0.0
 * @create 2020/2/24 0:21
 * @e-mail allenalan@139.com
 * @copyright 版权所有 (C) 2020 allennote
 */
public enum ResultEnum {
    // 200 操作成功 500 操作失败
    SUCCESS(1000000000, "操作成功"),
    FAIL(2000000000, "操作失败"),
    INVALID_REQUEST_PARAM_ERROR(2000005000, "参数校验错误"),
    ;

    private Integer code;

    private String msg;

    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
package com.barm.common.domain.vo;

import com.barm.common.domain.enums.ResultEnum;
import lombok.Getter;
import lombok.Setter;

/**
 * @author Allen
 * @version 1.0.0
 * @description 返回结果VO
 * @create 2020/2/23 23:30
 * @e-mail allenalan@139.com
 * @copyright 版权所有 (C) 2020 allennote
 */
@Getter
@Setter
public class ResultVO<T> {

    /** 响应码 */
    private Integer code;
    /** 响应提示 */
    private String msg;
    /** 响应数据 */
    private T data;
    /** 返回结果枚举 */
    private ResultEnum resultEnum;
    /** 是否成 */
    private Boolean success;

    public ResultVO(ResultEnum resultEnum) {
        this.code = resultEnum.getCode();
        this.msg = resultEnum.getMsg();
        this.resultEnum = resultEnum;
    }

    public ResultVO(ResultEnum resultEnum, T data) {
        this.resultEnum = resultEnum;
        this.code = resultEnum.getCode();
        this.msg = resultEnum.getMsg();
        this.data = data;
    }

    public ResultVO(T data) {
        this.resultEnum = ResultEnum.SUCCESS;
        this.code = resultEnum.getCode();
        this.msg = resultEnum.getMsg();
        this.data = data;
    }

    public ResultVO(ResultEnum resultEnum, String msg) {
        this.resultEnum = resultEnum;
        this.code = resultEnum.getCode();
        this.msg = msg;
    }

    public Boolean getSuccess() {
        this.success = resultEnum.equals(ResultEnum.SUCCESS);
        return success;
    }

    public static ResultVO success(){
        return new ResultVO(ResultEnum.SUCCESS);
    }

    public static ResultVO fail(){
        return new ResultVO(ResultEnum.FAIL);
    }

}

在Provider端校验

yaml 配置添加这一段 provider的配置

dubbo:
  provider: # Dubbo 服务端配置
    cluster: failfast # 集群方式,可选: failover/failfast/failsafe/failback/forking
    retries: 0 # 远程服务调用重试次数, 不包括第一次调用, 不需要重试请设为0
    timeout: 600000 # 远程服务调用超时时间(毫秒)
    token: true # 令牌验证, 为空表示不开启, 如果为true, 表示随机生成动态令牌
    dynamic: true # 服务是否动态注册, 如果设为false, 注册后将显示后disable状态, 需人工启用, 并且服务提供者停止时, 也不会自动取消册, 需人工禁用. 
    delay: -1 # 延迟注册服务时间(毫秒)- , 设为-1时, 表示延迟到Spring容器初始化完成时暴露服务
    version: 1.0.0 # 服务版本
    validation: true # 是否启用JSR303标准注解验证, 如果启用, 将对方法参数上的注解进行校验
#    filter: -exception # 服务提供方远程调用过程拦截器名称, 多个名称用逗号分隔

测试结果

providerTest

Provider 日志

ProviderLog

可以看到这里Provider其实抛出了一个可以处理的异常, 但是consumer端没能正确的处理

从 Comsumer 端的的日志可以看出, hibernate-validator (SpringCloud 默认校验框架) 这里缺少可实例化的无参构造器.解决方案很多, 但这不是本片文章的讨论重点不做讨论.

Caused by: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl' could not be instantiated
    at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:316)
    at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:201)
    at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2818)
    at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2145)
    at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074)
    at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2118)
    at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074)
    at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:406)
    ... 41 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:312)
    ... 48 more
Caused by: java.lang.NullPointerException
    at org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.<init>(ConstraintDescriptorImpl.java:176)
    at org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.<init>(ConstraintDescriptorImpl.java:233)
    ... more

Consumer端校验

application.yaml 端添加 consumer 的配置

consumer: # Dubbo 消费端配置
  check: false
  validation: true # 是否启用JSR303标准注解验证, 如果启用, 将对方法参数上的注解进行校验
  version: 1.0.0 # 默认版本

异常统一处理

package com.barm.order.server;

import com.barm.common.domain.enums.ResultEnum;
import com.barm.common.domain.vo.ResultVO;
import com.barm.common.exceptions.ApplicationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

/**
 * @author Allen
 * @version 1.0.0
 * @description 异常处理类
 * @create 2020/2/23 23:43
 * @e-mail allenalan@139.com
 * @copyright 版权所有 (C) 2020 barm
 */
@Slf4j
@RestControllerAdvice
public class ExceptionHandlers {

    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResultVO constraintViolationExceptionHandler(ConstraintViolationException ex) {
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
            // 使用 , 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(",");
            }
            // 拼接内容到其中
            detailMessage.append(constraintViolation.getMessage());
        }
        return new ResultVO(ResultEnum.INVALID_REQUEST_PARAM_ERROR,ResultEnum.INVALID_REQUEST_PARAM_ERROR.getMsg() + ":" + detailMessage.toString());
    }
}

测试一下

test

注意

如果开启Comsumer 端校验, 如果校验不通过, 请求是不会发给 Provider的. 推荐校验在Consumer端实现.

总结

dubbo的校验存在两种方式

  • 在consumer端校验(推荐)
  • 在Provider端校验

是不是很简单, 欢迎关注, 转发, 评论, 点赞~

posted @ 2020-03-21 23:54  AllenAlan  阅读(5841)  评论(0编辑  收藏  举报