還想看更多文章的朋友可以訪問(wèn)我的個(gè)人博客
在Hibernate Validator 校驗(yàn) (一)中,談了如何利用 Hibernate Validator 提供的約束注解完成較為簡(jiǎn)單的對(duì)象校驗(yàn),但這也不能滿足開發(fā)的需求。這時(shí),我們需要自己實(shí)現(xiàn)校驗(yàn)邏輯。
自定義校驗(yàn)邏輯
自實(shí)現(xiàn)校驗(yàn)有很多優(yōu)點(diǎn),一個(gè)項(xiàng)目下,肯定存在多個(gè)服務(wù)對(duì)同一類型的實(shí)例操作,而其校驗(yàn)邏輯大多類似。通過(guò)自實(shí)現(xiàn)校驗(yàn)將該邏輯抽象出來(lái),一方面減少代碼重復(fù),又可以滿足當(dāng)下模塊開發(fā)的思想。
先來(lái)看看測(cè)試類與 Controller 層實(shí)現(xiàn),其實(shí)與上一篇中的實(shí)現(xiàn)無(wú)太大差別,只是此次我們將使用正則表達(dá)式校驗(yàn)username,規(guī)則是匹配任何字類字符,包括下劃線,因此此處我們模擬發(fā)起增添用戶的POST請(qǐng)求。代碼如下:
Controller層實(shí)現(xiàn):
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
public User updateUser(@PathVariable Integer id, @RequestBody @Valid User user, BindingResult result) {
// 輸出錯(cuò)誤信息
if (result.hasErrors()) {
result.getAllErrors().forEach(err -> System.out.println(err.getDefaultMessage()));
}
user.setId(id);
return userService.update(user);
}
}
測(cè)試類:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void testValidUser() throws Exception {
Date date = new Date();
String content = "{\"username\": \"@ksjkd\",\"password\": \"password\",\"birthday\":"
- date.getTime() + "}";
mockMvc.perform(post("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk());
}
}
上一篇中也有提到,JPA Validation 規(guī)范對(duì)約束的定義包括兩部分,一是約束注解,如上篇中用到的@NotBlank、NotNull等就是約束注解;二是約束校驗(yàn)器,每一個(gè)約束注解都存在對(duì)應(yīng)的約束校驗(yàn)器,約束校驗(yàn)器實(shí)現(xiàn)具體的校驗(yàn)邏輯。我們自己實(shí)現(xiàn) validator 也需要滿足這兩點(diǎn)。
一、創(chuàng)建約束注解與其對(duì)應(yīng)的約束校驗(yàn)器
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { TestValidator.class })
public @interface TestConstraint {
String message() default ""; // 約束注解校驗(yàn)時(shí)的輸出消息
Class<?>[] groups() default { }; // 約束注解在校驗(yàn)時(shí)所屬的組別
Class<? extends Payload>[] payload() default { }; // 約束注解的有效負(fù)載
}
如上,在該注解上@Target與@Retention是 Java 中元注解中的兩個(gè),分別用于指明該注解的作用目標(biāo)與保留位置,此處不多做贅述。而Constraint注解特用于指明 JPA 約束注解與約束校驗(yàn)器的對(duì)應(yīng)關(guān)系。
另外,JPA Validation 規(guī)范要求自實(shí)現(xiàn)的約束注解必須聲明以上三個(gè)參數(shù),分別是:message, groups, payload。其中message屬性便是當(dāng)校驗(yàn)器校驗(yàn)失敗時(shí)的輸出消息,另兩個(gè)屬性的大致用途已在代碼中注釋,不再詳述。
如下,為該校驗(yàn)器的實(shí)現(xiàn):
public class TestValidator implements ConstraintValidator<TestConstraint, String> {
@Autowired
private TestService testService;
@Override
public void initialize(TestConstraint constraintAnnotation) {
// 添加約束注解在初始化時(shí)需要做的動(dòng)作
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
System.out.println(testService.test(value));
String pattern = "^\\w+$"; // 匹配任何字類字符,包括下劃線。與"[A-Za-z0-9_]"等效
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(value);
return m.matches();
}
}
自實(shí)現(xiàn)的校驗(yàn)器必須實(shí)現(xiàn)ConstraintValidator<A, T>接口,該接口有兩個(gè)泛型,分別是該校驗(yàn)器所“對(duì)應(yīng)的約束注解的類類型”和“被校驗(yàn)對(duì)象的類類型”。這里是針對(duì)String類型的username字段進(jìn)行校驗(yàn),所以兩泛型類型分別為TestConstraint與String。
如果是 Spring 項(xiàng)目,當(dāng)實(shí)現(xiàn)該接口則會(huì)自動(dòng)被 Bean 容器收集為 Bean。因此,我們可以按需求Autowired需要的Bean。TestService實(shí)現(xiàn)如下:
@Service
public class TestService {
public String test(String o) {
return "test:[" + o + "]";
}
}
本例中,該校驗(yàn)器采用“正則規(guī)則”對(duì)字符串進(jìn)行匹配。
二、在實(shí)體中聲明需要校驗(yàn)的字段
@Entity
public class User {
@TestConstraint(message = "測(cè)試校驗(yàn):用戶名必須是數(shù)字、字母或_的組合")
private String username;
...
}
利用我們方才定義的約束注解@TestConstraint對(duì) username 字段進(jìn)行校驗(yàn),并自定義校驗(yàn)錯(cuò)誤消息。
測(cè)試結(jié)果
在測(cè)試請(qǐng)求中,我們模擬的請(qǐng)求數(shù)據(jù)中username字段中添加了非法字符@,因此校驗(yàn)肯定是無(wú)法通過(guò)的:
