Dubbo的参数校验
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端校验
是不是很简单, 欢迎关注, 转发, 评论, 点赞~