当前位置:网站首页>Bean Validation核心组件篇----04
Bean Validation核心组件篇----04
2022-07-23 11:52:00 【大忽悠爱忽悠】
Bean Validation核心组件篇----04
引言
再了解了校验器上下文ValidatorContext,知道它可以对校验器Validator的核心五大组件分别进行定制化设置,那么这些核心组件在校验过程中到底扮演着什么样的角色呢,本文一探究竟。
作为核心组件,是有必要多探究一分的。以此为基,再扩散开了解和使用其它功能模块便将如鱼得水。但是过程枯燥是真的,所以需要坚持呀。
Bean Validation校验器的这五大核心组件通过ValidatorContext可以分别设置:若没设置(或为null),那就回退到使用ValidatorFactory默认的组件。
准备好的组件,统一通过ValidatorFactory暴露出来予以访问:
public interface ValidatorFactory extends AutoCloseable {
...
MessageInterpolator getMessageInterpolator();
TraversableResolver getTraversableResolver();
ConstraintValidatorFactory getConstraintValidatorFactory();
ParameterNameProvider getParameterNameProvider();
@since 2.0
ClockProvider getClockProvider();
...
}
MessageInterpolator
直译为:消息插值器。按字面不太好理解:简单的说就是对message内容进行格式化,若有占位符{}或者el表达式${}就执行替换和计算。对于语法错误应该尽量的宽容。
校验失败的消息模版交给它处理就成为了人能看得懂的消息格式,因此它能够处理消息的国际化:消息的key是同一个,但根据不同的Locale展示不同的消息模版。最后在替换/技术模版里面的占位符即可~
这是Bean Validation的标准接口,Hibernate Validator提供了实现:

Hibernate Validation它使用的是ResourceBundleMessageInterpolator来既支持参数,也支持EL表达式。
javax.el.ExpressionFactory这个API来支持EL表达式${}的,形如这样:must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}它是能够动态计算出${inclusive == true ? 'or equal to ' : ''}这部分的值的。
public interface MessageInterpolator {
String interpolate(String messageTemplate, Context context);
String interpolate(String messageTemplate, Context context, Locale locale);
}
接口方法直接了当:根据上下文Context填充消息模版messageTemplate。它的具体工作流程我用图示如下:

context上下文里一般是拥有需要被替换的key的键值对的,如下图所示:

Hibernate对Context的实现中扩展出了如图的两个Map(非JSR标准),可以让你优先于 constraintDescriptor取值,取不到再fallback到标准模式的ConstraintDescriptor里取值,也就是注解的属性值。具体取值代码如下:
ParameterTermResolver:
private Object getVariable(Context context, String parameter) {
// 先从hibernate扩展出来的方式取值
if (context instanceof HibernateMessageInterpolatorContext) {
Object variable = ( (HibernateMessageInterpolatorContext) context ).getMessageParameters().get( parameter );
if ( variable != null ) {
return variable;
}
}
// fallback到标准模式:从注解属性里取值
return context.getConstraintDescriptor().getAttributes().get( parameter );
}
大部分情况下我们只用得到注解属性里面的值,也就是错误消息里可以使用{注解属性名}这种方式动态获取到注解属性值,给与友好错误提示。
上下文里的Message参数和Expression参数如何放进去的?在后续高级使用部分,会自定义k-v替换参数,也就会使用到本部分的高级应用知识,后文见。
TraversableResolver
能跨越的处理器。从字面是非常不好理解,用粗暴的语言解释为:确定某个属性是否能被ValidationProvider访问,当每访问一个属性时都会通过它来判断一下子,提供两个判断方法:
public interface TraversableResolver {
// 是否是可达的
boolean isReachable(Object traversableObject,
Node traversableProperty,
Class<?> rootBeanType,
Path pathToTraversableObject,
ElementType elementType);
// 是否是可级联的(是否标注有@Valid注解)
boolean isCascadable(Object traversableObject,
Node traversableProperty,
Class<?> rootBeanType,
Path pathToTraversableObject,
ElementType elementType);
}
该接口主要根据配置项来进行判断,并不负责。内部使用,调用者基本无需关心,也不见更改其默认机制,暂且略过。
ConstraintValidatorFactory
约束校验器工厂。ConstraintValidator约束校验器我们应该不陌生:每个约束注解都得指定一个/多个约束校验器,形如这样:
@Constraint(validatedBy = {
xxx.class })。
ConstraintValidatorFactory就是工厂:可以根据Class生成对象实例。
public interface ConstraintValidatorFactory {
// 生成实例:接口并不规定你的生成方式
<T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key);
// 释放实例。标记此实例不需要再使用,一般为空实现
// 和Spring容器集成时 .destroyBean(instance)时会调用此方法
void releaseInstance(ConstraintValidator<?, ?> instance);
}
Hibernate提供了唯一实现ConstraintValidatorFactoryImpl:使用空构造器生成实例 clazz.getConstructor().newInstance();。
小贴士:接口并没规定你如何生成实例,Hibernate Validator是使用空构造这么实现的而已~
ParameterNameProvider
参数名提供器。这个组件和Spring的ParameterNameDiscoverer作用是一毛一样的:获取方法/构造器的参数名。
public interface ParameterNameProvider {
List<String> getParameterNames(Constructor<?> constructor);
List<String> getParameterNames(Method method);
}
提供的实现:
- DefaultParameterNameProvider:基于Java反射API Executable#getParameters()实现
@Test
public void test9() {
ParameterNameProvider parameterNameProvider = new DefaultParameterNameProvider();
// 拿到Person的无参构造和有参构造(@NoArgsConstructor和@AllArgsConstructor)
Arrays.stream(Person.class.getConstructors()).forEach(c -> System.out.println(parameterNameProvider.getParameterNames(c)));
}
运行程序,输出:
[arg0, arg1, arg2, arg3]
[]
一样的,若你想要打印出明确的参数名,请在编译参数上加上-parameters参数。
- ReflectionParameterNameProvider:已过期。请使用上面的default代替
- ParanamerParameterNameProvider:基于com.thoughtworks.paranamer.Paranamer实现参数名的获取,需要额外导入相应的包才行。
ClockProvider
时钟提供器。这个接口很简单,就是提供一个Clock,给@Past、@Future等阅读判断提供参考。唯一实现为DefaultClockProvider:
public class DefaultClockProvider implements ClockProvider {
public static final DefaultClockProvider INSTANCE = new DefaultClockProvider();
private DefaultClockProvider() {
}
// 默认是系统时钟
@Override
public Clock getClock() {
return Clock.systemDefaultZone();
}
}
默认使用当前系统时钟作为参考。若你的系统有全局统一的参考标准,比如统一时钟,那就可以通过此接口实现自己的Clock时钟,毕竟每台服务器的时间并不能保证是完全一样的不是,这对于时间敏感的应用场景(如竞标)需要这么做。
以上就是对Validator校验器的五个核心组件的一个描述,总体上还是比较简单。其中第一个组件:MessageInterpolator插值器我认为是最为重要的,需要理解好了。对后面做自定义消息模版、国际化消息都有用。
ValueExtractor
值提取器。2.0版本新增一个比较重要的组件API,作用:把值从容器内提取出来。这里的容器包括:数组、集合、Map、Optional等等。
// T:待提取的容器类型
public interface ValueExtractor<T> {
// 从原始值originalValue提取到receiver里
void extractValues(T originalValue, ValueReceiver receiver);
// 提供一组方法,用于接收ValueExtractor提取出来的值
interface ValueReceiver {
// 接收从对象中提取的值
void value(String nodeName, Object object);
// 接收可以迭代的值,如List、Map、Iterable等
void iterableValue(String nodeName, Object object);
// 接收有索引的值,如List Array
// i:索引值
void indexedValue(String nodeName, int i, Object object);
// 接收键值对的值,如Map
void keyedValue(String nodeName, Object key, Object object);
}
}
容易想到,ValueExtractor的实现类就非常之多(所有的实现类都是内建的,非public的,这就是默认情况下支持的容器类型):

举例两个典型实现:
// 提取List里的值 LIST_ELEMENT_NODE_NAME -> <list element>
class ListValueExtractor implements ValueExtractor<List<@ExtractedValue ?>> {
static final ValueExtractorDescriptor DESCRIPTOR = new ValueExtractorDescriptor( new ListValueExtractor() );
private ListValueExtractor() {
}
@Override
public void extractValues(List<?> originalValue, ValueReceiver receiver) {
for ( int i = 0; i < originalValue.size(); i++ ) {
receiver.indexedValue( NodeImpl.LIST_ELEMENT_NODE_NAME, i, originalValue.get( i ) );
}
}
}
// 提取Optional里的值
@UnwrapByDefault
class OptionalLongValueExtractor implements ValueExtractor<@ExtractedValue(type = Long.class) OptionalLong> {
static final ValueExtractorDescriptor DESCRIPTOR = new ValueExtractorDescriptor( new OptionalLongValueExtractor() );
@Override
public void extractValues(OptionalLong originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.isPresent() ? originalValue.getAsLong() : null );
}
}
校验器Validator通过它把值从容器内提取出来参与校验,从这你应该就能理解为毛从Bean Validation2.0开始就支持验证容器内的元素了吧,形如这样:List<@NotNull @Valid Person>、Optional<@NotNull @Valid Person>,可谓大大的方便了使用。
若你有自定义容器,需要提取的需求,那么你可以自定义一个ValueExtractor实现,然后通过ValidatorContext#addValueExtractor()添加进去即可
参考
边栏推荐
- 冒泡排序-看着一篇就够啦
- 3D数学 - 矢量
- Remote system command execution
- 作为测试人员,不能不懂的adb命令和操作
- [untitled]
- Idées de conception sur l'initialisation des paramètres d'entrée de page
- day14函数模块
- sqlnet. Ora-12154 and ora-01017 connection exceptions caused by incorrect ora file settings
- 上课作业(5)——#576. 饥饿的牛(hunger)
- Custom encapsulation pop-up box (with progress bar)
猜你喜欢

Ultra detailed MP4 format analysis

《快速掌握QML》第四章 事件处理

Custom encapsulation pop-up box (with progress bar)

How to become an elegant Hardware Engineer?

Mercedes Benz new energy product line: luxury new energy market may change the pattern

2022 the most NB JVM foundation to tuning notes, thoroughly understand Alibaba P6 small case

Remember SQL optimization once

Software testing weekly (No. 81): what can resist negativity is not positivity, but concentration; What can resist anxiety is not comfort, but concrete.

How beautiful can VIM be configured?

“1+1>10”:无代码/低代码与RPA技术的潜在结合
随机推荐
PHP code audit 4 - SQL injection vulnerability
C# 关闭当前电脑指令
Php:filter pseudo protocol [bsidescf 2020]had a bad day
奔驰新能源产品线:豪华新能源市场或将改变格局
One minute rule for sequential disk access
Umijs - data transmission between main and sub applications of Qiankun
pydensecrf安装
W3C 推出去中心化标识符作为 Web 标准
再获殊荣 | OpenSCA获选中国软博会“全球十大开源软件产品”
How beautiful can VIM be configured?
What is the real HTAP? (2) Challenge article
後綴錶達式(暑假每日一題 4)
SharedPreferences data storage
Unity-笔记-ILRuntime接入
【无标题】
Don't want dto set. Dto can be written like this
2022 the most NB JVM foundation to tuning notes, thoroughly understand Alibaba P6 small case
Of the 24 almost inevitable JVM interview questions, I only know 7. How many can you answer?
没有了华为,高通任意涨价,缺乏核心技术的国产手机只能任由宰割
SharedPreferences数据储存