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 # 服務提供方遠程調用過程攔截器名稱, 多個名稱用逗號分隔
測試結果
Provider 日志
可以看到這里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());
}
}
測試一下
注意
如果開啟Comsumer 端校驗, 如果校驗不通過, 請求是不會發給 Provider的. 推薦校驗在Consumer端實現.
總結
dubbo的校驗存在兩種方式
- 在consumer端校驗(推薦)
- 在Provider端校驗
是不是很簡單, 歡迎關注, 轉發, 評論, 點贊~