當前位置:網站首頁>Bean Validation核心組件篇----04
Bean Validation核心組件篇----04
2022-07-23 16:09:54【大忽悠愛忽悠】
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()添加進去即可
參考
版權聲明
本文為[大忽悠愛忽悠]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/204/202207231152135559.html
邊欄推薦
猜你喜歡
隨機推薦
- BGP機房的優點
- 真人踩過的坑,告訴你避免自動化測試常犯的10個錯誤
- 判斷是否為void類型
- C語言——幾道C語言經典習題
- openvino_datawhale
- C語言基礎知識梳理(一)
- Redis源碼與設計剖析 -- 7.快速列錶
- 比特,比特,字節,字的概念與區別
- 項目部署(簡版)
- JDBC的學習以及簡單封裝
- [pytho-flask筆記5]藍圖簡單使用
- Web Component-自定義元素的生命周期
- 數倉4.0筆記——業務數據采集
- 數倉4.0筆記——用戶行為數據采集四
- 對.h5文件的迭代顯示,h5py數據操作
- 常用數學知識匯總
- “東數西算”下數據中心的液冷GPU服務器如何發展?
- 硬件知識1--原理圖和接口類型(基於百問網硬件操作大全視頻教程)
- 鋼結構基本原理複習
- Unity3d:UGUI源碼,Rebuild優化
- 快速解决:Xshell拖不進去文件夾或者軟件包的問題
- RHCSA--文件內容瀏覽、cut、uniq、sort、.tr命令使用
- 信號完整性(SI)電源完整性(PI)學習筆記(三十二)電源分配網路(四)
- EasyGBS平臺出現錄像無法播放並存在RTMP重複推流現象,是什麼原因?
- 第七天筆記
- 【可視化調度軟件】上海道寧為SMB組織帶來NETRONIC下載、試用、教程
- 概率沉思錄:2.The quantitative rules
- 常用的鼠標事件和鍵盤事件
- C#:in、out、ref關鍵字
- GRE,MGRE的詳細了解;OSPF基礎配置知識
- Creo 9.0 如何快速修改CAD坐標系?
- 第五天筆記
- 强化學習——策略梯度理解點
- shell跑的時候需要的需要了解命令
- OKRK3399開發板預留I2C4掛載EEPROM
- 優化華為雲服務器采用Key登陸
- 第2章 基礎查詢與排序
- 【C語言】猜數字小遊戲+關機小程序
- 什麼是Per-Title編碼?
- @FeignClient使用詳細教程(圖解)