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端校驗

是不是很簡單, 歡迎關注, 轉發, 評論, 點贊~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM