自定义方法和实体校验器

自定义方法和实体校验器

1.背景

Spring 本身就集成方法级别的参数校验,实体校验一般使用 hibernate-validator 脚本,这两个都是基于java 对象校验(validation )-JSR303规范来实现的。使用也是比较方便,具体使用可以看下的 使用demo
,自己写的这个工具主要是有一个项目,非Spring项目的,一个非Spring的轻量开发的,目前需要给第三个做数据接口,涉及一些参数校验,hibernate-validator 仅仅对实体进行校验,因此写下自己的校验,并非遵循 JSR303,目的只要能快速校验必须的参数,如非空、时间格式、正则表达式、长度限制等等,也不做分组等支持!

主要目标

1.能支持动态扩展
2.支持方法级别校验
3.支持实体校验

使用方法

1.独立使用

独立使用demo,我自己在非Spring的项目也是使用aop的方式,在service的方法之前检查参数!下面是单独使用的列子:

   /**
    * @author wwupower
    * @Title: ValidatorTest
    * @history 2019年06月13日
    * @since JDK1.8
    */
   public class ValidatorTest {
   
       public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
           //初始化参数校验工厂 并添加自定义校验器
           ParamsValidatorFactory paramsValidatorFactory = new ParamsValidatorFactory("com.power.validator.valid.NotEmptyValidator");
           //设置的快速检查模式
           ParamsValidator paramsValidator = paramsValidatorFactory.createParamsValidator().setFastModel(false);
           //单个实体对象检验
   //        List<ValidatorResult> validatorResults = paramsValidator.validEntity(new DemoVO().setLength("1").setReg("ac_ssasa").setMax("11212121"), DemoVO.class);
   //        validatorResults.forEach(validatorResult -> {
   //            System.out.println(validatorResult.getMsg());
   //        });
           DemoService demoService = new DemoService();
           //使用代理
           Method method = ReflectUtil.getMethod(demoService.getClass(), "findDemos", String.class, DemoVO.class);
           method.invoke(demoService, "aa", new DemoVO().setLength("1").setReg("ac_ssasa").setMax("11212121"));
           //方法级别校验
           List<ValidatorResult> validatorMenthodrResults = paramsValidator.validMethod(method.getParameters(),
                   "",new DemoVO().setLength("122323").setReg("ac_ssasa").setMax("1"));
           System.out.println("---方法级别校验-");
           validatorMenthodrResults.forEach(validatorResult -> {
               System.out.println(validatorResult.getMsg());
           });
       }
   
   }

2.集成Spring

假如你想在Spring使用的话,在Spring.xml添加注入

    <beans:bean id="paramsValidatorFactory"
        class="com.power.validator.ParamsValidatorFactory">
        <!--   <beans:property
            name="userDefindVlidatorClasses"
            value="com.power.validator.valid.NotEmptyValidator"/>-->
    </beans:bean>
    <beans:bean id="paramsValidato"
        factory-bean="paramsValidatorFactory"
        factory-method="createParamsValidator">
        <beans:property name="fastModel" value="false"/>
    </beans:bean>

然后在通过 Aop,在 service 的方法执行前检查,然后统一异常捕获抛出!

/**
 * 参数检查切面类
 */
@Aspect
@Component
public class ParamVerifyAspect {

    @Pointcut("execution(* com.power.api.v1..*.controller..*.*(..))")
    private void pointCutControllerMethod() {
    }

    @Pointcut("execution(* com.power.v1..*.service..*.*(..))")
    private void pointServiceCutMethod() {
    }

    /**
     * 调用前扫描
     *
     * @param joinPoint
     * @throws Exception
     */
    @Before("pointServiceCutMethod()")
    public void paramServiceCheck(JoinPoint joinPoint) throws Exception {
        ParamsValidator paramsValidator = (ParamsValidator) Toolkit.getSpringBean(ParamsValidator.class);
        Object[] args = joinPoint.getArgs();
        //获取方法参数
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Parameter[] parameters = signature.getMethod().getParameters();
        List<ValidatorResult> validatorResults = paramsValidator.validMethod(parameters, args);
        if(CollectionUtils.isNotEmpty(validatorResults)){
            throw new ParamsValidatorException(paramsValidator.getErrorMsg(validatorResults));
        }
    }

    /**
     * 调用前扫描
     *
     * @param joinPoint
     * @throws Exception
     */
    @Before("pointCutControllerMethod()")
    public void paramCheck(JoinPoint joinPoint) throws Exception {
        //ParamsVerifyUtil.getInstance().verifyParams(joinPoint);
        //获取方法参数
       /* ParamsValidator paramsValidator = (ParamsValidator) Toolkit.getSpringBean(ParamsValidator.class);
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Parameter[] parameters = signature.getMethod().getParameters();
        paramsValidator.validMethod(parameters, args);*/
    }

}

自定义扩展

先创建自己的注解如:

/**
 * @author wwupower
 * @Title: 长度校验
 * @history 2019年06月13日
 * @since JDK1.8
 */

public class LengthValitdator implements BaseValidator<Length, String> {

    @Override
    public ValidatorResult valid(String value, Length length) {
        if (StrUtil.isEmpty(value)) {
            return new ValidatorResult().setSuccess(true).setMsg(length.msg());
        }
        if(value.length()>length.max()){
            return new ValidatorResult().setSuccess(false).setMsg(length.msg());
        }
        if(value.length()<length.min()){
            return new ValidatorResult().setSuccess(false).setMsg(length.msg());
        }
        return new ValidatorResult().setSuccess(true).setMsg(length.msg());
    }
}

校验器

/**
 * @author wwupower
 * @Title: 长度校验
 * @history 2019年06月13日
 * @since JDK1.8
 */

public class LengthValitdator implements BaseValidator<Length, String> {

    @Override
    public ValidatorResult valid(String value, Length length) {
        if (StrUtil.isEmpty(value)) {
            return new ValidatorResult().setSuccess(true).setMsg(length.msg());
        }
        if(value.length()>length.max()){
            return new ValidatorResult().setSuccess(false).setMsg(length.msg());
        }
        if(value.length()<length.min()){
            return new ValidatorResult().setSuccess(false).setMsg(length.msg());
        }
        return new ValidatorResult().setSuccess(true).setMsg(length.msg());
    }

}

在工厂创建时候添加自己的校验器

 //初始化参数校验工厂 并添加自定义校验器
   ParamsValidatorFactory paramsValidatorFactory = new ParamsValidatorFactory("com.power.validator.valid.LengthValitdator");
   //设置的快速检查模式
   ParamsValidator paramsValidator = paramsValidatorFactory.createParamsValidator().setFastModel(false);

使用用注解

public class DemoVO {
    @Length(min = 3L,max = 10L)
    String length;

    @Pattern(regexp = "^[a-zA-Z_]\\w{4,19}$", msg = "用户名必须以字母下划线开头,可由字母数字下划线组成")
    String reg;

    @Min(value = 2)
    String min;

    @Max(value = 1)
    String max;


    public String getLength() {
        return length;
    }

    public DemoVO setLength(String length) {
        this.length = length;
        return this;
    }

    public String getReg() {
        return reg;
    }

    public DemoVO setReg(String reg) {
        this.reg = reg;
        return this;
    }

    public String getMin() {
        return min;
    }

    public DemoVO setMin(String min) {
        this.min = min;
        return this;
    }

    public String getMax() {
        return max;
    }

    public DemoVO setMax(String max) {
        this.max = max;
        return this;
    }

}

代码已上传到GitHub

<think>我们之前讨论了在属性a校验前根据属性b的值更新a再校验新a值的需求,并给出了两种方案:自定义校验器AOP切面。现在用户进一步询问关于Java自定义校验器中调用自定义校验方法实现方式。这实际上是在深入探讨方案一(自定义校验器)的具体实现细节。 用户引用内容提到:“自定义校验器通过注解与反射实现”,因此我们将重点放在自定义校验器内部如何调用其他校验方法上。以下是分步骤实现方式: ### 步骤1:定义自定义校验注解 ```java import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = CustomValidator.class) // 指定校验器 public @interface CustomValidation { String message() default "校验失败"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` ### 步骤2:实现校验器并调用自定义方法 在`isValid`方法中,我们可以调用任意自定义方法来完成校验逻辑: ```java import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class CustomValidator implements ConstraintValidator<CustomValidation, String> { @Override public void initialize(CustomValidation constraintAnnotation) { // 初始化操作(可选) } @Override public boolean isValid(String value, ConstraintValidatorContext context) { // 步骤1:获取宿对象(包含当前属性的整个实体) Object targetObject = getRootBean(context); // 步骤2:调用自定义校验方法(封装业务逻辑) return validateWithCustomMethod(targetObject, value); } private Object getRootBean(ConstraintValidatorContext context) { // 通过Hibernate Validator特定API获取(需依赖hibernate-validator) if (context instanceof HibernateConstraintValidatorContext) { return ((HibernateConstraintValidatorContext) context) .getBeanMetadata() .getBeanInstance(); } // 通用方法(需要反射) try { // 反射获取context中的parent属性(非标准方法,此处仅作示意) Field parentField = context.getClass().getDeclaredField("parent"); parentField.setAccessible(true); Object parent = parentField.get(context); // 继续向上追溯直到找到包含实体对象的上下文(实际实现会更复杂) // ...(省略具体反射路径) return null; // 实际开发中需要完整反射链 } catch (Exception e) { throw new RuntimeException("无法获取宿对象", e); } } private boolean validateWithCustomMethod(Object entity, String currentValue) { // 这里实现你的自定义校验逻辑,例如: // 1. 检查实体其他属性(如属性b) // 2. 根据b的值更新currentValue // 3. 校验更新后的值 // 示例:调用另一个自定义校验工具类 return ValidationHelper.customCheckMethod(entity, currentValue); } } ``` ### 步骤3:创建自定义校验工具类 将复杂校验逻辑提取到独立工具类: ```java public class ValidationHelper { public static boolean customCheckMethod(Object entity, String value) { // 模拟业务逻辑:如果实体有getB()方法且b为特定值时,要求a满足特定格式 try { Method getB = entity.getClass().getMethod("getB"); String bValue = (String) getB.invoke(entity); // 根据b的值更新a的逻辑 String updatedValue = updateValue(value, bValue); // 对更新后的值进行校验 return checkValueFormat(updatedValue); } catch (Exception e) { return false; } } private static String updateValue(String a, String b) { return "特殊状态".equals(b) ? "prefix_" + a : a; } private static boolean checkValueFormat(String value) { return value != null && value.length() > 5; // 示例校验规则 } } ``` ### 关键点说明 1. **获取宿对象**: - 推荐方式:使用Hibernate Validator的扩展API`HibernateConstraintValidatorContext`(需添加依赖) - 备选方式:通过反射遍历`ConstraintValidatorContext`内部结构(不稳定,依赖具体实现) 2. **调用自定义方法**: - 直接在`isValid`中写业务逻辑会导致代码臃肿 - 最佳实践:将业务逻辑委托给独立工具类(如上例的`ValidationHelper`) 3. **反射性能优化**: - 缓存反射获得的`Method`/`Field`对象(使用`ConcurrentHashMap`) ```java private static final Map<Class<?>, Method> methodCache = new ConcurrentHashMap<>(); Method getB = methodCache.computeIfAbsent(entity.getClass(), cls -> cls.getMethod("getB")); ``` ### 依赖说明 若使用Hibernate Validator API需添加依赖: ```xml <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.2.0.Final</version> </dependency> ``` ### 相关问题 1. 如何在不依赖Hibernate的情况下获取宿对象? 2. 自定义校验器中如何访问多个关联属性? 3. 如何避免自定义校验器中的反射性能开销? 4. 自定义校验器能否注入Spring容器管理的Bean? 5. 如何为动态更新的属性生成带路径的错误信息? [^1]: 自定义校验器的核心实现机制 [^2]: 反射在获取宿对象过程中的关键作用 </think>### Java自定义校验器调用自定义校验方法实现方式 在Java Bean Validation中,自定义校验器可以通过调用自定义校验方法实现复杂校验逻辑。以下是具体实现步骤: #### 1. 自定义校验注解 ```java import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = CustomValidator.class) // 绑定校验器 public @interface CustomCheck { String message() default "校验失败"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` #### 2. 实现校验器并调用自定义方法 ```java import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class CustomValidator implements ConstraintValidator<CustomCheck, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { // 1. 获取宿对象(包含当前属性的整个实体) Object rootBean = getRootBean(context); // 2. 调用自定义校验方法 return customValidationMethod(rootBean, value); } // 自定义校验方法(封装业务逻辑) private boolean customValidationMethod(Object entity, String value) { // 示例:根据实体其他属性进行动态校验 try { // 反射获取属性b的值 Field bField = entity.getClass().getDeclaredField("b"); bField.setAccessible(true); Object bValue = bField.get(entity); // 根据b的值更新a String updatedValue = updateValue(value, bValue); // 校验更新后的值 return validateUpdatedValue(updatedValue); } catch (Exception e) { return false; } } // 值更新逻辑 private String updateValue(String currentValue, Object bValue) { return "特殊状态".equals(bValue) ? "UPDATE_" + currentValue : currentValue; } // 自定义校验逻辑 private boolean validateUpdatedValue(String value) { return value != null && value.startsWith("UPDATE_"); } // 获取宿对象(通过Hibernate扩展API) private Object getRootBean(ConstraintValidatorContext context) { return context.unwrap(HibernateConstraintValidatorContext.class) .getBeanMetadata() .getBeanInstance(); } } ``` #### 3. 在实体类中使用 ```java public class BusinessEntity { @CustomCheck // 应用自定义校验 private String a; private String b; // 影响a的校验逻辑 // getters/setters } ``` #### 4. 控制器中的调用 ```java @RestController public class BusinessController { @PostMapping("/validate") public ResponseEntity<?> validateEntity(@Valid @RequestBody BusinessEntity entity) { // 自动触发自定义校验逻辑 return ResponseEntity.ok("校验通过"); } } ``` ### 关键实现细节 1. **宿对象获取**: - 使用Hibernate Validator扩展API获取完整实体对象 ```java context.unwrap(HibernateConstraintValidatorContext.class) .getBeanMetadata() .getBeanInstance(); ``` - 需添加依赖:`org.hibernate.validator:hibernate-validator` 2. **自定义方法封装**: - 将核心校验逻辑封装在独立方法中(如`customValidationMethod()`) - 方法内部可包含: - 反射获取其他属性值 - 动态值更新逻辑 - 复杂业务规则校验 3. **反射优化**: ```java // 缓存Field对象提升性能 private static final Map<Class<?>, Field> fieldCache = new ConcurrentHashMap<>(); Field bField = fieldCache.computeIfAbsent(entity.getClass(), cls -> { try { Field f = cls.getDeclaredField("b"); f.setAccessible(true); return f; } catch (NoSuchFieldException e) { throw new IllegalStateException(e); } }); ``` 4. **校验结果处理**: - 校验失败时添加自定义错误信息 ```java context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate("更新值校验失败: " + updatedValue) .addPropertyNode("a") .addConstraintViolation(); return false; ``` ### 典型应用场景 1. **条件校验**:当字段b为特定值时,字段a需满足特殊规则 2. **动态转换**:校验前根据其他字段值修改当前字段值 3. **跨字段关联**:多个字段组合校验(如开始日期 < 结束日期) 4. **外部服务集成**:调用远程服务验证数据一致性 ### 相关问题 1. 如何避免自定义校验器中的反射性能开销? 2. 自定义校验器能否注入Spring Bean进行数据库校验? 3. 如何在自定义校验中处理多个关联属性的复杂逻辑? 4. 如何为动态生成的校验错误指定具体字段路径? 5. 自定义校验器的执行顺序如何控制? [^1]: 自定义校验器的核心实现机制 [^2]: 反射在获取宿对象过程中的关键作用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值