深入了解数据校验(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 );
  }
}






相关文章
|
11月前
|
Java 编译器 API
Java 密封类:精细化控制继承关系
Java 密封类:精细化控制继承关系
520 83
|
IDE Java 数据挖掘
Java 基础类从入门到精通实操指南
这份指南专注于**Java 17+**的新特性和基础类库的现代化用法,涵盖开发环境配置、数据类型增强(如文本块)、字符串与集合处理进阶、异常改进(如密封类)、IO操作及实战案例。通过具体代码示例,如CSV数据分析工具,帮助开发者掌握高效编程技巧。同时提供性能优化建议和常用第三方库推荐,适合从入门到精通的Java学习者。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
635 37
|
9月前
|
安全 Java 数据建模
Java记录类:简化数据载体的新选择
Java记录类:简化数据载体的新选择
486 101
|
9月前
|
安全 Java 开发者
Java记录类:简化数据载体的新方式
Java记录类:简化数据载体的新方式
385 100
|
10月前
|
安全 IDE Java
Java记录类型(Record):简化数据载体类
Java记录类型(Record):简化数据载体类
646 143
|
8月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
372 4
|
8月前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
385 5
|
8月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
481 5
|
8月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
413 1
|
8月前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
521 1