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






相关文章
|
2月前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
160 57
|
13天前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
|
2月前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
66 8
|
2月前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
77 17
|
2月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
2月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
137 4
|
2月前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
86 2
|
2月前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
71 4
|
2月前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
56 5
|
2月前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
119 5