今天就跟大家聊聊有关Java中怎么实现接口数据校验,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
Controller接口层数据绑定校验
实际上在Java开发中目前普通使用的Bean数据校验工具是"hibernate-validator",它是一个hibernete独立的jar包,所以使用这个jar包并不需要一定要集成Hibernete框架。该jar包主要实现并扩展了javax.validation(是一个基于JSR-303标准开发出来的Bean校验规范)接口。
由于Spring Boot在内部默认集成了"hibernate-validator",所以使用Spring Boot构建的Java工程可以直接使用相关注解来实现Bean的数据校验。例如我们最常编写的Controller层接口参数对象,可以在定义Bean类时直接编写这样的代码:
@Data public class CreateOrderDTO { @NotNull(message = "订单号不能为空") private String orderId; @NotNull(message = "订单金额不能为空") @Min(value = 1, message = "订单金额不能小于0") private Integer amount; @Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "用户手机号不合法") private String mobileNo; private String orderType; private String status; }
如上所示代码,我们可以使用@NotNull注解来约束该字段必须不能为空,也可以使用@Min注解来约束字段的最小取值,或者还可以通过@Pattern注解来使用正则表达式来约束字段的格式(如手机号格式)等等。
以上这些注解都是“hibernate-validator”依赖包默认提供的,更多常用的注解还有很多,例如:
利用这些约束注解,我们就可以很轻松的搞定接口数据校验,而不需要在业务逻辑中编写大量的if-else来进行数据合法性校验。而定义好Bean参数对象并使用相关注解实现参数值约束后,在Controller层接口定义中只需要使用@Validated注解就可以实现在接收参数后自动进行数据绑定校验了,具体代码如下:
@PostMapping("/createOrder") public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) { return orderServiceImpl.createOrder(createOrderDTO); }
如上所示,在Controller层中通过Spring提供的@Validated注解可以自动实现数据Bean的绑定校验,如果数据异常则会统一抛出校验异常!
约束性注解扩展
在“hibernate-validator”依赖jar包中,虽然提供了很多很方便的约束注解,但是也有不满足某些实际需要的情况,例如我们想针对参数中的某个值约定其值的枚举范围,如orderType订单类型只允许传“pay”、“refund”两种值,那么现有的约束注解可能就没有特别适用的了。此外,如果对这样的枚举值,我们还想在约束定义中直接匹配代码中的枚举定义,以更好地统一接口参数与业务逻辑的枚举定义。那么这种情况下,我们还可以自己扩展定义相应地约束注解逻辑。
接下来我们定义新的约束注解@EnumValue,来实现上面我们所说的效果,具体代码如下:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = {EnumValueValidator.class}) public @interface EnumValue { //默认错误消息 String message() default "必须为指定值"; //支持string数组验证 String[] strValues() default {}; //支持int数组验证 int[] intValues() default {}; //支持枚举列表验证 Class<?>[] enumValues() default {}; //分组 Class<?>[] groups() default {}; //负载 Class<? extends Payload>[] payload() default {}; //指定多个时使用 @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { EnumValue[] value(); } /** * 校验类逻辑定义 */ class EnumValueValidator implements ConstraintValidator<EnumValue, Object> { //字符串类型数组 private String[] strValues; //int类型数组 private int[] intValues; //枚举类 private Class<?>[] enumValues; /** * 初始化方法 * * @param constraintAnnotation */ @Override public void initialize(EnumValue constraintAnnotation) { strValues = constraintAnnotation.strValues(); intValues = constraintAnnotation.intValues(); enumValues = constraintAnnotation.enumValues(); } /** * 校验方法 * * @param value * @param context * @return */ @SneakyThrows @Override public boolean isValid(Object value, ConstraintValidatorContext context) { //针对字符串数组的校验匹配 if (strValues != null && strValues.length > 0) { if (value instanceof String) { for (String s : strValues) {//判断值类型是否为Integer类型 if (s.equals(value)) { return true; } } } } //针对整型数组的校验匹配 if (intValues != null && intValues.length > 0) { if (value instanceof Integer) {//判断值类型是否为Integer类型 for (Integer s : intValues) { if (s == value) { return true; } } } } //针对枚举类型的校验匹配 if (enumValues != null && enumValues.length > 0) { for (Class<?> cl : enumValues) { if (cl.isEnum()) { //枚举类验证 Object[] objs = cl.getEnumConstants(); //这里需要注意,定义枚举时,枚举值名称统一用value表示 Method method = cl.getMethod("getValue"); for (Object obj : objs) { Object code = method.invoke(obj, null); if (value.equals(code.toString())) { return true; } } } } } return false; } } }
如上所示的@EnumValue约束注解,是一个非常实用的扩展,通过该注解我们可以实现对参数取值范围(不是大小范围)的约束,它支持对int、string以及enum三种数据类型的约束,具体使用方式如下:
/** * 定制化注解,支持参数值与指定类型数组列表值进行匹配(缺点是需要将枚举值写死在字段定义的注解中) */ @EnumValue(strValues = {"pay", "refund"}, message = "订单类型错误") private String orderType; /** * 定制化注解,实现参数值与枚举列表的自动匹配校验(能更好地与实际业务开发匹配) */ @EnumValue(enumValues = Status.class, message = "状态值不在指定范围") private String status;
如上所示代码,该扩展注解既可以使用strValues或intValues属性来编程列举取值范围,也可以直接通过enumValues来绑定枚举定义。但是需要注意,处于通用考虑,具体枚举定义的属性的名称要统一匹配为value、desc,例如Status枚举定义如下:
public enum Status { PROCESSING(1, "处理中"), SUCCESS(2, "订单已完成"); Integer value; String desc; Status(Integer value, String desc) { this.value = value; this.desc = desc; } public Integer getValue() { return value; } public String getDesc() { return desc; } }
通过注解扩展,就能实现更多方便的约束性注解!
更加灵活的数据校验工具类封装
除了上面直接在Controller层使用@Validated进行绑定数据校验外,在有些情况,例如你的参数对象中的某个字段是一个复合对象,或者业务层的某个方法所定义的入参对象也需要进行数据合法性校验,那么这种情况下如何实现像Controller层一样的校验效果呢?
需要说明在这种情况下@Validated已经无法直接使用了,因为@Validated注解发挥作用主要是Spring MVC在接收参数的过程中实现了自动数据绑定校验,而在普通的业务方法或者复合参数对象中是没有办法直接绑定校验的。这种情况下,我们可以通过定义ValidateUtils工具类来实现一样的校验效果,具体代码如下:
public class ValidatorUtils { private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); /** * bean整体校验,有不合规范,抛出第1个违规异常 */ public static void validate(Object obj, Class<?>... groups) { Set<ConstraintViolation<Object>> resultSet = validator.validate(obj, groups); if (resultSet.size() > 0) { //如果存在错误结果,则将其解析并进行拼凑后异常抛出 List<String> errorMessageList = resultSet.stream().map(o -> o.getMessage()).collect(Collectors.toList()); StringBuilder errorMessage = new StringBuilder(); errorMessageList.stream().forEach(o -> errorMessage.append(o + ";")); throw new IllegalArgumentException(errorMessage.toString()); } } }
如上所示,我们定义了一个基于"javax.validation"接口的工具类实现,这样就可以在非@Validated直接绑定校验的场景中通过校验工具类来实现对Bean对象约束注解的校验处理,具体使用代码如下:
public boolean orderCheck(OrderCheckBO orderCheckBO) { //对参数对象进行数据校验 ValidatorUtils.validate(orderCheckBO); return true; }
而方法入参对象则还是可以继续使用前面我们介绍的约束性注解进行约定,例如上述方法的入参对象定义如下:
@Data @Builder public class OrderCheckBO { @NotNull(message = "订单号不能为空") private String orderId; @Min(value = 1, message = "订单金额不能小于0") private Integer orderAmount; @NotNull(message = "创建人不能为空") private String operator; @NotNull(message = "操作时间不能为空") private String operatorTime; }
这样在编程体验上就可以整体上保持一致!
数据合法性校验结果异常统一处理
通过前面我们所讲的各种约束注解,我们实现了对Controller层接口以及业务方法参数对象的统一数据校验。而为了保持校验异常处理的统一处理和错误报文统一输出,我们还可以定义通用的异常处理机制,来保证各类数据校验错误都能以统一错误格式反馈给调用方。具体代码如下:
@Slf4j @ControllerAdvice public class GlobalExceptionHandler { /** * 统一处理参数校验错误异常(非Spring接口数据绑定验证) * * @param response * @param e * @return */ @ExceptionHandler(BindException.class) @ResponseBody public ResponseResult<?> processValidException(HttpServletResponse response, BindException e) { response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); //获取校验错误结果信息,并将信息组装 List<String> errorStringList = e.getBindingResult().getAllErrors() .stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList()); String errorMessage = String.join("; ", errorStringList); response.setContentType("application/json;charset=UTF-8"); log.error(e.toString() + "_" + e.getMessage(), e); return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), errorMessage); } /** * 统一处理参数校验错误异常 * * @param response * @param e * @return */ @ExceptionHandler(IllegalArgumentException.class) @ResponseBody public ResponseResult<?> processValidException(HttpServletResponse response, IllegalArgumentException e) { response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); String errorMessage = String.join("; ", e.getMessage()); response.setContentType("application/json;charset=UTF-8"); log.error(e.toString() + "_" + e.getMessage(), e); return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), errorMessage); } ... }
如上所示,我们定义了针对前面两种数据校验方式的统一异常处理机制,这样数据校验的错误信息就能通过统一的报文格式反馈给调用端,从而实现接口数据报文的统一返回!
其中通用的接口参数对象ResponseResult的代码定义如下:
@Data @Builder @NoArgsConstructor @AllArgsConstructor @JsonPropertyOrder({"code", "message", "data"}) public class ResponseResult<T> implements Serializable { private static final long serialVersionUID = 1L; /** * 返回的对象 */ @JsonInclude(JsonInclude.Include.NON_NULL) private T data; /** * 返回的编码 */ private Integer code; /** * 返回的信息 */ private String message; /** * @param data 返回的数据 * @param <T> 返回的数据类型 * @return 响应结果 */ public static <T> ResponseResult<T> OK(T data) { return packageObject(data, GlobalCodeEnum.GL_SUCC_0); } /** * 自定义系统异常信息 * * @param code * @param message 自定义消息 * @param <T> * @return */ public static <T> ResponseResult<T> systemException(Integer code, String message) { return packageObject(null, code, message); } }
当然,这样的统一报文格式也不仅仅只处理异常返回,正常的数据报文格式也可以通过该对象来进行统一封装!
看完上述内容,你们对Java中怎么实现接口数据校验有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。