Dubbo注解方式數(shù)據(jù)校驗(yàn)支持詳解
數(shù)據(jù)校驗(yàn)
簡(jiǎn)介:
作為一個(gè)Java開(kāi)發(fā)者我們或多或少在Spring MVC使用場(chǎng)景中接觸過(guò)數(shù)據(jù)校驗(yàn)(Bean Validation)。Bean Validation技術(shù)隸屬于Java EE規(guī)范,期間有多個(gè)JSR(Java Specification Requests)支持,目前共有三次相關(guān)JSR標(biāo)準(zhǔn)發(fā)布:
- JSR303 最早(2009)
- JSR349
- JSR380

JSR303
JSR303提出很早(2009年),它為 基于注解的 JavaBean驗(yàn)證定義元數(shù)據(jù)模型和API。JSR-303主要是對(duì)JavaBean進(jìn)行驗(yàn)證,如方法級(jí)別(方法參數(shù)/返回值)、依賴注入等的驗(yàn)證是沒(méi)有指定的。
作為開(kāi)山之作,它規(guī)定了Java數(shù)據(jù)校驗(yàn)的模型和API,這就是Java Bean Validation 1.0版本。
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
該版本提供了13個(gè)現(xiàn)在常見(jiàn)的校驗(yàn)注解:
| 注解 | 支持類型 | 含義 | null值是否校驗(yàn) |
|---|---|---|---|
| @AssertFalse | bool | 元素必須是false | 否 |
| @AssertTrue | bool | 元素必須是true | 否 |
| @DecimalMax | Number的子類型(浮點(diǎn)數(shù)除外)以及String | 元素必須是一個(gè)數(shù)字,且值必須<=最大值 | 否 |
| @DecimalMin | 同上 | 元素必須是一個(gè)數(shù)字,且值必須>=最小值 | 否 |
| @Max | 同上 | 同上 | 否 |
| @Min | 同上 | 同上 | 否 |
| @Digits | 同上 | 元素構(gòu)成是否合法(整數(shù)部分和小數(shù)部分) | 否 |
| @Future | 時(shí)間類型(包括JSR310) | 元素必須為一個(gè)將來(lái)(不包含相等)的日期(比較精確到毫秒) | 否 |
| @Past | 同上 | 元素必須為一個(gè)過(guò)去(不包含相等)的日期(比較精確到毫秒) | 否 |
| @NotNull | any | 元素不能為null | 是 |
| @Null | any | 元素必須為null | 是 |
| @Pattern | String | 元素需符合指定的正則表達(dá)式 | 否 |
| @Size | String/Collection/Map/Array | 元素大小需在指定范圍中 | 否 |
它的官方參考實(shí)現(xiàn)如下:

JSR349
該規(guī)范2013年完成伴隨java EE 7一起發(fā)布,就是我們比較熟悉的Bean Validation1.1。
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
相較于1.0版本,它主要的改進(jìn)/優(yōu)化有如下幾點(diǎn):
- 標(biāo)準(zhǔn)化了Java平臺(tái)的約束定義、描述、和驗(yàn)證
- 支持方法級(jí)驗(yàn)證(入?yún)⒒蚍祷刂档尿?yàn)證)
- Bean驗(yàn)證組件的依賴注入
- 與上下文和DI依賴注入集成
- 使用EL表達(dá)式的錯(cuò)誤消息插值,讓錯(cuò)誤消息動(dòng)態(tài)化起來(lái)(強(qiáng)依賴于ElManager)
- 跨參數(shù)驗(yàn)證。比如密碼和驗(yàn)證密碼必須相同
注解個(gè)數(shù)上,相較于1.0版本并沒(méi)新增~
它的官方參考實(shí)現(xiàn)如下:

注:當(dāng)你導(dǎo)入了hibernate-validator后,無(wú)需再顯示導(dǎo)入javax.validation
JSR380
當(dāng)下主流版本,也就是Java Bean Validation 2.0,它完成于2017年8月,在2019年8月發(fā)布,屬于Java EE 8的一部分。它的官方參考實(shí)現(xiàn)只有唯一的Hibernate validator了:

此版本具有很重要的現(xiàn)實(shí)意義,主要有以下變化:
支持通過(guò)注解泛型類型來(lái)驗(yàn)證容器內(nèi)的元素,如:List<@Positive Integer> positiveNumbers,即容器內(nèi)元素須為正數(shù)
- 1. 更靈活的集合類型級(jí)聯(lián)驗(yàn)證;例如,現(xiàn)在可以驗(yàn)證映射的鍵和值,如:
Map<@Valid CustomerType, @Valid Customer> customersByType - 2. 支持java.util.Optional類型,并且支持通過(guò)插入額外的值提取器來(lái)支持自定義容器類型
讓@Past/@Future注解支持注解在JSR310時(shí)間上
新增內(nèi)建的注解類型(共9個(gè)):@Email, @NotEmpty, @NotBlank, @Positive, @PositiveOrZero, @Negative, @NegativeOrZero, @PastOrPresent和@FutureOrPresent
所有內(nèi)置的約束現(xiàn)在都支持重復(fù)標(biāo)記
JDK最低版本要求:JDK 8
新增注解
| 注解 | 支持類型 | 含義 | null值是否校驗(yàn) |
|---|---|---|---|
| String | 元素必須是電子郵箱地址 | 否 | |
| @NotEmpty | 容器類型 | 集合的Size必須大于0 | 是 |
| @NotBlank | String | 字符串必須包含至少一個(gè)非空白的字符 | 是 |
| @Positive | Positive | 元素必須必須為正數(shù)(不包括0) | 否 |
| @PositiveOrZero | 同上 | 同上(包括0) | 否 |
| @Negative | 同上 | 元素必須必須為負(fù)數(shù)(不包括0) | 否 |
| @NegativeOrZero | 同上 | 同上(包括0) | 否 |
| @PastOrPresent | 時(shí)間類型 | 在@Past基礎(chǔ)上包括相等 | 否 |
| @FutureOrPresent | 時(shí)間類型 | 在@Futrue基礎(chǔ)上包括相等 | 否 |
從1.1版本起就需要El管理器支持用于錯(cuò)誤消息動(dòng)態(tài)插值,因此需要自己額外導(dǎo)入EL的實(shí)現(xiàn)。EL也屬于Java EE標(biāo)準(zhǔn)技術(shù),可認(rèn)為是一種表達(dá)式語(yǔ)言工具,它并不僅僅是只能用于Web,可以用于任意地方(類比Spring的SpEL)
這是EL技術(shù)規(guī)范的API:
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
Expression Language 3.0表達(dá)式語(yǔ)言規(guī)范于2013-4-29發(fā)布,Tomcat 8、Jetty 9、GlasshFish 4都已經(jīng)支持實(shí)現(xiàn)了EL 3.0,如果你是web環(huán)境,就不用自己手動(dòng)導(dǎo)入了
簡(jiǎn)單來(lái)說(shuō)以上JSR提供了一套Bean校驗(yàn)規(guī)范的API,維護(hù)在包javax.validation.constraints下。該規(guī)范使用屬性或者方法參數(shù)或者類上的一套簡(jiǎn)潔易用的注解來(lái)做參數(shù)校驗(yàn)。開(kāi)發(fā)者在開(kāi)發(fā)過(guò)程中,僅需在需要校驗(yàn)的地方加上形如@NotNull, @NotEmpty , @Email的注解,就可以將參數(shù)校驗(yàn)的重任委托給一些第三方校驗(yàn)框架來(lái)處理。
在Spring MVC中,只需要使用@Valid注解標(biāo)注在方法參數(shù)商,Spring MVC即可對(duì)參數(shù)對(duì)象進(jìn)行校驗(yàn),校驗(yàn)結(jié)果會(huì)放在BindingResult對(duì)象中。除了@Valid 還有 @Validated注解支持參數(shù)的分組校驗(yàn)。它們的區(qū)別:
- @Valid:沒(méi)有分組的功能。
- @Valid:可以用在方法、構(gòu)造函數(shù)、方法參數(shù)和成員屬性(字段)上
- @Validated:提供了一個(gè)分組功能,可以在入?yún)Ⅱ?yàn)證時(shí),根據(jù)不同的分組采用不同的驗(yàn)證機(jī)制
- @Validated:可以用在類型、方法和方法參數(shù)上。但是不能用在成員屬性(字段)上
兩者是否能用于成員屬性(字段)上直接影響能否提供嵌套驗(yàn)證的功能
嵌套驗(yàn)證
看下圖:
比如我們現(xiàn)在有個(gè)實(shí)體叫做Item:
public class Item {
@NotNull(message = "id不能為空")
@Min(value = 1, message = "id必須為正整數(shù)")
private Long id;
@NotNull(message = "props不能為空")
@Size(min = 1, message = "至少要有一個(gè)屬性")
private List<Prop> props;
}
Item帶有很多屬性,屬性里面有:pid、vid、pidName和vidName,如下所示:
public class Prop {
@NotNull(message = "pid不能為空")
@Min(value = 1, message = "pid必須為正整數(shù)")
private Long pid;
@NotNull(message = "vid不能為空")
@Min(value = 1, message = "vid必須為正整數(shù)")
private Long vid;
@NotBlank(message = "pidName不能為空")
private String pidName;
@NotBlank(message = "vidName不能為空")
private String vidName;
}
屬性這個(gè)實(shí)體也有自己的驗(yàn)證機(jī)制,比如pid和vid不能為空,pidName和vidName不能為空等。
正常情況,Spring Validation框架只會(huì)對(duì)Item的id和props做非空和數(shù)量驗(yàn)證,不會(huì)對(duì)props字段里的Prop實(shí)體進(jìn)行字段驗(yàn)證。
如何進(jìn)行嵌套校驗(yàn)?
為了能夠進(jìn)行嵌套驗(yàn)證,必須手動(dòng)在Item實(shí)體的props字段上明確指出這個(gè)字段里面的實(shí)體也要進(jìn)行驗(yàn)證。由于@Validated不能用在成員屬性(字段)上,但是@Valid能加在成員屬性(字段)上,而且@Valid類注解上也說(shuō)明了它支持嵌套驗(yàn)證功能,那么我們能夠推斷出:@Valid加在方法參數(shù)時(shí)并不能夠自動(dòng)進(jìn)行嵌套驗(yàn)證,而是用在需要嵌套驗(yàn)證類的相應(yīng)字段上,來(lái)配合方法參數(shù)上@Validated或@Valid來(lái)進(jìn)行嵌套驗(yàn)證。
修改Item類如下所示:
public class Item {
@NotNull(message = "id不能為空")
@Min(value = 1, message = "id必須為正整數(shù)")
private Long id;
@Valid // 嵌套驗(yàn)證必須用@Valid
@NotNull(message = "props不能為空")
@Size(min = 1, message = "props至少要有一個(gè)自定義屬性")
private List<Prop> props;
}
除了上面常見(jiàn)的@NotNull、@Min、@NotBlank和@Size等校驗(yàn)注解我們還可以自定義校驗(yàn)注解~
類級(jí)別驗(yàn)證(多字段聯(lián)合驗(yàn)證)
約束也可以放在類級(jí)別上(也就說(shuō)注解標(biāo)注在類上)。在這種情況下,驗(yàn)證的主體不是單個(gè)屬性,而是整個(gè)對(duì)象。如果驗(yàn)證依賴于對(duì)象的幾個(gè)屬性之間的相關(guān)性,那么類級(jí)別約束就能搞定。
這個(gè)需求場(chǎng)景在平時(shí)開(kāi)發(fā)中也非常常見(jiàn),比如此處我舉個(gè)簡(jiǎn)單場(chǎng)景案例:修改用戶名密碼,需要輸入兩遍新密碼:newPass,newPassAgain,要求newPass.equals(newPassAgain)。
如果用事務(wù)腳本來(lái)實(shí)現(xiàn)這個(gè)驗(yàn)證規(guī)則,那么你的代碼里肯定穿插著類似這樣的代碼:
if (!this.newPass.equals(this.newPassAgain)){
throw new RuntimeException("...");
}
雖然這么做也能達(dá)到校驗(yàn)的效果,但很明顯這不夠優(yōu)雅。
但是基于Hibernate-Validator內(nèi)置的@ScriptAssert,可以很容易的處理這種case:
@ScriptAssert(lang = "javascript", alias = "_", script = "_.newPass.equals(_.newPassAgain)",message = "兩個(gè)密碼不相等")
public class SecContent implements Serializable {
@NotNull(message = "age 不能為空",groups = {TestGroup.class})
private Integer age;
@NotBlank
private String newPass;
@NotBlank
private String newPassAgain;
...
}
@ScriptAssert支持寫(xiě)腳本來(lái)完成驗(yàn)證邏輯,這里使用的是javascript(缺省情況下的唯一選擇,也是默認(rèn)選擇)
@ScriptAssert是內(nèi)置就提供的,因此使用起來(lái)非常的方便和通用。但缺點(diǎn)也是因?yàn)檫^(guò)于通用,因此語(yǔ)義上不夠明顯,需要閱讀腳本才知。推薦少量(非重復(fù)使用)、邏輯較為簡(jiǎn)單時(shí)使用,更為輕巧
自定義校驗(yàn)
舉例說(shuō)明自定義注解的實(shí)現(xiàn):需要一個(gè)自定義注解來(lái)校驗(yàn)入?yún)ame不能和已存在name重名
1.自定義注解
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueConstraintValidator.class)
public @interface UniqueConstraint {
//下面三個(gè)屬性是必須有的屬性
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2.新建一個(gè)UniqueConstraintValidator類來(lái)驗(yàn)證注解
//自定義校驗(yàn)注解 的 校驗(yàn)邏輯
//不需要加注解@Component,因?yàn)閷?shí)現(xiàn)了ConstraintValidator接口自動(dòng)會(huì)注冊(cè)為spring bean
public class UniqueConstraintValidator implements ConstraintValidator<UniqueConstraint,Object> {
@Autowired
private UserService userService;
@Override
public void initialize(UniqueConstraint uniqueConstraint) {
System.out.println("my validator init");
}
//Object為校驗(yàn)的字段類型
//返回true則校驗(yàn)成功
//o為校驗(yàn)字段的值,constraintValidatorContext為校驗(yàn)注解里的屬性值
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
String username = (String) o;
TbUser user = userService.findByUsername(username);
return user==null?true:false;
}
}
UniqueConstraintValidator類必須實(shí)現(xiàn)ConstraintValidator接口initialize方法以及驗(yàn)證方法isValid- 具體的校驗(yàn)邏輯在
isValid方法中做校驗(yàn)
使用的時(shí)候在需要的字段上標(biāo)記該注解即可:

Dubbo RPC參數(shù)校驗(yàn)
那么Dubbo作為國(guó)產(chǎn)優(yōu)秀的開(kāi)源RPC框架,支持注解方式校驗(yàn)參數(shù)么?答案當(dāng)然是支持的~
ValidationFilter

ValidationFilter通過(guò)在實(shí)際方法調(diào)用之前,根據(jù)調(diào)用者url配置的validation屬性值找到正確的{Validator}實(shí)例來(lái)調(diào)用驗(yàn)證。
關(guān)于ValidationFilter是如何被調(diào)用的是dubbo spi的內(nèi)容這里就不提了,但是要想其生效需要在consumer或者provider端配置一下:
consumer:
@DubboReference(validation = "true")
private DemoService demoService;
或provider:
@DubboService(validation = "true")
public class DemoServiceImpl implements DemoService {
注:如果在消費(fèi)端開(kāi)啟參數(shù)校驗(yàn),不通過(guò)就不會(huì)向服務(wù)端發(fā)起rpc調(diào)用,但是要自己處理校驗(yàn)異常ConstraintViolationException
Validator校驗(yàn)器
默認(rèn)ValidationFilter使用的校驗(yàn)器看下圖

JValidation實(shí)現(xiàn)代碼還是比較少的
/**
* Creates a new instance of {@link Validator} using input argument url.
* @see AbstractValidation
* @see Validator
*/
public class JValidation extends AbstractValidation {
/**
* Return new instance of {@link JValidator}
* @param url Valid URL instance
* @return Instance of JValidator
*/
@Override
protected Validator createValidator(URL url) {
return new JValidator(url);
}
}
可以看到實(shí)際干活的是org.apache.dubbo.validation.support.jvalidation.JValidator
在其validate方法中可以看到有一個(gè)@MethodValidated注解

點(diǎn)開(kāi)查看它的注釋

大意上能明白這注解是標(biāo)記在方法上支持分組校驗(yàn)的!
簡(jiǎn)單示例:
消費(fèi)方代碼:
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
@DubboReference(validation = "true")
private DemoService demoService;
@Override
public String sayHello(String name) {
return demoService.sayHello(name);
}
@Override
public String sayGoodBye(Content content) {
return demoService.sayGoodBye(content);
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return null;
}
}
dubbo client interface:
public interface DemoService {
String sayHello(String name);
@MethodValidated({TestGroup.class})
String sayGoodBye(Content content);
default CompletableFuture<String> sayHelloAsync(String name) {
return CompletableFuture.completedFuture(sayHello(name));
}
}
方法入?yún)ontent:
public class Content implements Serializable {
@NotNull(message = "name不能為空",groups = {TestGroup.class})
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
注:沒(méi)有設(shè)置groups的校驗(yàn)注解也會(huì)進(jìn)行校驗(yàn),作為默認(rèn)分組。最后捕獲下拋出的ConstraintViolationException以結(jié)構(gòu)化的json格式返回給調(diào)用方"校驗(yàn)錯(cuò)誤信息"
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot文件上傳大小設(shè)置方式(yml中配置)
這篇文章主要介紹了SpringBoot文件上傳大小設(shè)置方式(yml中配置),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java基礎(chǔ)學(xué)習(xí)之字符串知識(shí)總結(jié)
今天帶著大家復(fù)習(xí)一下Java基礎(chǔ)知識(shí)-字符串,文中介紹的非常詳細(xì),對(duì)初步學(xué)習(xí)Java或者復(fù)習(xí)Java的小伙伴們都很有幫助喲,需要的朋友可以參考下2021-05-05
Springboot自動(dòng)裝配之注入DispatcherServlet的實(shí)現(xiàn)方法
這篇文章主要介紹了Springboot自動(dòng)裝配之注入DispatcherServlet,Springboot向外界提供web服務(wù),底層依賴了springframework中的web模塊來(lái)實(shí)現(xiàn),那么springboot在什么時(shí)機(jī)向容器注入DispatcherServlet這個(gè)核心類的呢?帶著這個(gè)問(wèn)題一起通過(guò)本文學(xué)習(xí)吧2022-05-05
Java 數(shù)據(jù)結(jié)構(gòu)中二叉樹(shù)前中后序遍歷非遞歸的具體實(shí)現(xiàn)詳解
樹(shù)是一種重要的非線性數(shù)據(jù)結(jié)構(gòu),直觀地看,它是數(shù)據(jù)元素(在樹(shù)中稱為結(jié)點(diǎn))按分支關(guān)系組織起來(lái)的結(jié)構(gòu),很象自然界中的樹(shù)那樣。樹(shù)結(jié)構(gòu)在客觀世界中廣泛存在,如人類社會(huì)的族譜和各種社會(huì)組織機(jī)構(gòu)都可用樹(shù)形象表示2021-11-11
java控制臺(tái)實(shí)現(xiàn)可視化日歷小程序
這篇文章主要為大家詳細(xì)介紹了java控制臺(tái)實(shí)現(xiàn)可視化日歷小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
SpringBoot通過(guò)Nginx代理獲取真實(shí)IP
springboot作為后臺(tái)代碼,獲取到的登錄IP是前臺(tái)的代理服務(wù)器地址,并不是用戶的真實(shí)IP地址,本文主要介紹了SpringBoot通過(guò)Nginx代理獲取真實(shí)IP,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
Java多線程并發(fā)基礎(chǔ)實(shí)戰(zhàn)舉例
進(jìn)程與線程的區(qū)別,解釋并行與并發(fā)的概念,分析多線程導(dǎo)致的線程安全問(wèn)題(可見(jiàn)性、原子性、有序性),并探討Java通過(guò)volatile、synchronized及Happens-Before規(guī)則解決這些問(wèn)題的機(jī)制,最后分類線程安全程度及實(shí)現(xiàn)方案,感興趣的朋友一起看看吧2025-09-09

