当前位置:网站首页>Bean Validation核心組件篇----04
Bean Validation核心組件篇----04
2022-07-23 16:09: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()添加進去即可
參考
边栏推荐
- 1060 Are They Equal
- 【运维】ssh tunneling 依靠ssh的22端口实现访问远程服务器的接口服务
- A quietly rising domestic software is too strong!
- 浅谈‘过早优化’
- Unity notes ilruntime access
- Who is responsible for the problems of virtual anchor and idol endorsement products? Lawyer analysis
- MySQL-字符串按照数值排序
- Bug modification
- Mathematical Modeling Typesetting
- 远程系统命令执行
猜你喜欢

After effects tutorial, how to create animation in after effects?

【无标题】

Axure advanced

MySQL 灵魂 16 问,你能撑到第几问?

Fake XML cookbook of XML xxE vulnerability

After Effects 教程,如何在 After Effects 中创建动画?

Chapter 4 event handling of quick mastering QML

黑马程序员-接口测试-四天学习接口测试-第三天-postman高级用法,newman例集导出导入,常用断言,断言json数据,工作原理,全局,环境变量,时间戳,请求前置脚本,关联,批量执行测试用例

云服务器ECS远程监控

Dark horse programmer - interface test - four day learning interface test - third day - advanced usage of postman, export and import of Newman case set, common assertions, assertion JSON data, working
随机推荐
专访|开源之夏新星牛学蔚
再获殊荣 | OpenSCA获选中国软博会“全球十大开源软件产品”
New infrastructure of enterprise data in the era of digital transformation | love Analysis Report
bug修改
黑马程序员-接口测试-四天学习接口测试-第三天-postman高级用法,newman例集导出导入,常用断言,断言json数据,工作原理,全局,环境变量,时间戳,请求前置脚本,关联,批量执行测试用例
Redis master-slave replication
中年危机,35岁被退休,打工人拿什么来抗衡资本家?
CONDA set up proxy
远程系统命令执行
Class homework (5) -- 576. Hungry cattle
Suffix expression (summer vacation daily question 4)
(BFS) template + example (maze, eight digits)
Details of task switching
Php:filter pseudo protocol [bsidescf 2020]had a bad day
现代商业无代码开发平台的治理和网络安全
What is the real HTAP? (2) Challenge article
Bug modification
List merging (summer vacation daily question 3)
Unity notes ilruntime access
php:filter伪协议之[BSidesCF 2020]Had a bad day