深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)【享学Java】(中)

简介: 深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)【享学Java】(中)

ParameterMessageInterpolator

资源束消息插值器,不支持el表达式,支持参数值表达式


public class ParameterMessageInterpolator extends AbstractMessageInterpolator {
  @Override
  public String interpolate(Context context, Locale locale, String term) {
    // 简单的说就是以$打头,就认为是EL表达式  啥都不处理
    if ( InterpolationTerm.isElExpression( term ) ) {
      return term;
    } else {
      // 核心处理方法是context.getConstraintDescriptor().getAttributes().get( parameter ) 拿到对应的值
      ParameterTermResolver parameterTermResolver = new ParameterTermResolver();
      return parameterTermResolver.interpolate( context, term );
    }
  }
}


根据代码里可以看出:注解里的属性都是可以使用引用的。如@NotNull执行context.getConstraintDescriptor().getAttributes()如下:


image.png



所以你的message里可以使用{groups}、{message}。。。等占位符。比如这样写@NotNull(message = "{message} -> 名字不能为null") 结果为:{message} -> 名字不能为null -> 名字不能为null: null


ResourceBundleMessageInterpolator


它比上更加强大些,支持EL表达式,使用了javax.el.ExpressionFactory来解析它,最终是委托于InterpolationTerm来处理,具体源码此处就省略了。


Hibernate Validation它使用的是ResourceBundleMessageInterpolator来既支持参数,也支持EL表达式~

当然如果你对默认的提示词语不开心,你可以自定义自己的插值器哦~~


TraversableResolver:可移动的处理器


它的意思从字面是非常不好理解,我用粗暴的语言解释为:确定某个属性是否能被ValidationProvider访问~


注意:访问每个属性的时候它都会被调用来判断一下子~


public interface TraversableResolver {
  // 是否是可达的
  boolean isReachable(Object traversableObject,
            Node traversableProperty,
            Class<?> rootBeanType,
            Path pathToTraversableObject,
            ElementType elementType);
  // 是否是可级联的
  boolean isCascadable(Object traversableObject,
             Node traversableProperty,
             Class<?> rootBeanType,
             Path pathToTraversableObject,
             ElementType elementType);
}

它的继承树如下:


image.png


ConstraintValidatorFactory:校验器工厂


校验里很重要的一个处理逻辑地是ConstraintValidato,它就是工厂,可以根据指定的Class类型生产一个实例(其实就是调用了构造函数new出来一个~)


public interface ConstraintValidatorFactory {
  <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key);
  // 释放方法是提供给Spring容器集成时 .destroyBean(instance);的
  void releaseInstance(ConstraintValidator<?, ?> instance);
}

它的继承树如下


image.png


此处就只讨论ConstraintValidatorFactoryImpl


public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory {
  // NewInstance的run方法最终就是执行了这句话:clazz.getConstructor().newInstance()而已
  // 因此最终就是创建了一个key的实例而已~  Spring相关的会把它和Bean容器结合起来
  @Override
  public final <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
    return run( NewInstance.action( key, "ConstraintValidator" ) );
  }
  @Override
  public void releaseInstance(ConstraintValidator<?, ?> instance) {
    // noop
  }
  // 入参是个函数式接口:java.security.PrivilegedAction
  private <T> T run(PrivilegedAction<T> action) {
    return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
  }
}


ParameterNameProvider:参数名提供器


提供方法、构造函数的入参names们。逻辑比较简单~


public interface ParameterNameProvider {
  List<String> getParameterNames(Constructor<?> constructor);
  List<String> getParameterNames(Method method);
}


它的继承树如下:


image.png


DefaultParameterNameProvider


public class DefaultParameterNameProvider implements ParameterNameProvider {
  @Override
  public List<String> getParameterNames(Constructor<?> constructor) {
    return doGetParameterNames( constructor );
  }
  @Override
  public List<String> getParameterNames(Method method) {
    return doGetParameterNames( method );
  }
  // Executable是1.8提供的抽象类。两个子类刚好就是:Method和Constructor
  // 因此在Java8后一个方法搞定:executable.getParameters();
  private List<String> doGetParameterNames(Executable executable) {
    Parameter[] parameters = executable.getParameters();
    List<String> parameterNames = new ArrayList<>( parameters.length );
    for ( Parameter parameter : parameters ) {
      parameterNames.add( parameter.getName() );
    }
    return Collections.unmodifiableList( parameterNames );
  }
}


ReflectionParameterNameProvider


@deprecated since 6.0,因为通过反射去获取方法名字已经是默认的了,所以可以计划移除~


If ‘-parameters’ is used at compile time, actual names will be returned. Otherwise, it will be arg0, arg1…


给个使用DefaultParameterNameProvider获取参数名们的例子:


    public static void main(String[] args) {
        DefaultParameterNameProvider parameterNameProvider = new DefaultParameterNameProvider();
        // 拿到Person的无参构造和有参构造(@NoArgsConstructor和@AllArgsConstructor)
        Arrays.stream(Person.class.getConstructors()).forEach(c -> {
            List<String> parameterNames = parameterNameProvider.getParameterNames(c);
            System.out.println(parameterNames);
        });
        // Method此处我就省略不写了 
    }

一样的对于Java8而言,编译器’-parameters’ 才会有争取的名称~


它的效果特别像Spring的org.springframework.core.ParameterNameDiscoverer


ClockProvider


这个接口很简单,就是提供一个Clock,给@Past、@Future等判断作为参考

public class DefaultClockProvider implements ClockProvider {
  public static final DefaultClockProvider INSTANCE = new DefaultClockProvider();
  // 单例的存在
  private DefaultClockProvider() {
  }
  @Override
  public Clock getClock() {
    return Clock.systemDefaultZone();
  }
}


默认就是服务器当前时间。若你有参考的时间逻辑,自己提供一个ClockProvider实现注册进Configuration即可~


ValueExtractor:值提取器



值提取器,蛮重要的,从容器内把值提取处理啊,@since 2.0


public interface ValueExtractor<T> {
  // 从原始值originalValue提取到receiver里
  void extractValues(T originalValue, ValueReceiver receiver);
  // 提供一组方法,用于接收ValueExtractor提取出来的值
  // 必须将该值传递给与原始值类型对应的最佳方法。
  interface ValueReceiver {
    // 接收从对象中提取的值。
    void value(String nodeName, Object object);
    // 接收从未编入索引的可ITerable对象中提取的值,如List、Map、Iterable等
    void iterableValue(String nodeName, Object object);
    // 处理List
    void indexedValue(String nodeName, int i, Object object);
    // 处理Map
    void keyedValue(String nodeName, Object key, Object object);
  }
}


注意:提取值的时候,会执行doValidate完成校验。容易想到,ValueExtractor的实现类就非常之多(所有的实现类都是内建的,非public的):


image.png


示例两个如下:

// 这种泛型的写法非常有意思~int @ExtractedValue[]代表int数组  并且还是使用了@ExtractedValue标注的
class IntArrayValueExtractor implements ValueExtractor<int @ExtractedValue[]> {
  static final ValueExtractorDescriptor DESCRIPTOR = new ValueExtractorDescriptor( new IntArrayValueExtractor() );
  private IntArrayValueExtractor() {
  }
  @Override
  public void extractValues(int[] originalValue, ValueReceiver receiver) {
    for ( int i = 0; i < originalValue.length; i++ ) {
      receiver.indexedValue( NodeImpl.ITERABLE_ELEMENT_NODE_NAME, i, originalValue[i] );
    }
  }
}
class IterableValueExtractor implements ValueExtractor<Iterable<@ExtractedValue ?>> {
  ...
  @Override
  public void extractValues(Iterable<?> originalValue, ValueReceiver receiver) {
    for ( Object object : originalValue ) {
      receiver.iterableValue( NodeImpl.ITERABLE_ELEMENT_NODE_NAME, object );
    }
  }
}
class OptionalValueExtractor implements ValueExtractor<Optional<@ExtractedValue ?>> {
  ...
  @Override
  public void extractValues(Optional<?> originalValue, ValueExtractor.ValueReceiver receiver) {
    receiver.value( null, originalValue.isPresent() ? originalValue.get() : null );
  }
}






相关文章
|
1天前
|
安全 Java 调度
Java一分钟:多线程编程初步:Thread类与Runnable接口
【5月更文挑战第11天】本文介绍了Java中创建线程的两种方式:继承Thread类和实现Runnable接口,并讨论了多线程编程中的常见问题,如资源浪费、线程安全、死锁和优先级问题,提出了解决策略。示例展示了线程通信的生产者-消费者模型,强调理解和掌握线程操作对编写高效并发程序的重要性。
10 3
|
2天前
|
Java
【JAVA基础篇教学】第五篇:Java面向对象编程:类、对象、继承、多态
【JAVA基础篇教学】第五篇:Java面向对象编程:类、对象、继承、多态
|
2天前
|
存储 安全 Java
Java容器类List、ArrayList、Vector及map、HashTable、HashMap
Java容器类List、ArrayList、Vector及map、HashTable、HashMap
|
3天前
|
Java 编译器 开发者
Java一分钟之-继承:复用与扩展类的特性
【5月更文挑战第9天】本文探讨了Java中的继承机制,通过实例展示了如何使用`extends`创建子类继承父类的属性和方法。文章列举了常见问题和易错点,如构造器调用、方法覆盖、访问权限和类型转换,并提供了解决方案。建议深入理解继承原理,谨慎设计类结构,利用抽象类和接口以提高代码复用和扩展性。正确应用继承能构建更清晰、灵活的代码结构,提升面向对象设计能力。
9 0
|
3天前
|
Java
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
13 0
|
3天前
|
Java API 调度
【Java多线程】Thread类的基本用法
【Java多线程】Thread类的基本用法
6 0
|
3天前
|
SQL Java 数据库连接
JDBC Java标准库提供的一些api(类+方法) 统一各种数据库提供的api
JDBC Java标准库提供的一些api(类+方法) 统一各种数据库提供的api
9 0
|
4天前
|
Java
Java一分钟之-类与对象:面向对象编程入门
【5月更文挑战第8天】本文为Java面向对象编程的入门指南,介绍了类与对象的基础概念、常见问题及规避策略。文章通过代码示例展示了如何定义类,包括访问修饰符的适当使用、构造器的设计以及方法的封装。同时,讨论了对象创建与使用时可能遇到的内存泄漏、空指针异常和数据不一致等问题,并提供了相应的解决建议。学习OOP需注重理论与实践相结合,不断编写和优化代码。
26 1
|
5天前
|
Java 开发者
在Java中,接口和超类在多态性中扮演着重要的角色
Java中的接口和超类支持多态性,接口作为规范,允许多继承和回调机制;超类提供基类,实现代码重用和方法重写,两者共同促进代码的灵活性和可维护性。
26 10
|
5天前
|
Java
Java并发Futures和Callables类
Java程序`TestThread`演示了如何在多线程环境中使用`Futures`和`Callables`。它创建了一个单线程`ExecutorService`,然后提交两个`FactorialService`任务,分别计算10和20的阶乘。每个任务返回一个`Future`对象,通过`get`方法获取结果,该方法会阻塞直到计算完成。计算过程中模拟延迟以展示异步执行。最终,打印出10!和20!的结果。