برای شروع کتابخانه Hibernate Validator را به pom اضافه میکنیم :
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.10.Final</version> </dependency>
اگر از Spring Boot استفاده میکنید با اضافه شدن کتابخانه spring-boot-starter-web کتابخانه hibernate-validator هم اضافه خواهد شد و دیگر نیازی ندارد جداگانه آنرا اضافه کنید
داشتن اعتبار سنج دلخواه این اجازه را به ما میدهد که در قالب annotation آنرا روی model بکار بگیریم و دقیقا مواردی که نیاز است چک شود را بررسی کند
حالا به نحوه ایجاد یک اعتبار سنج برای شماره تلفن 11 رقمی میپردازیم :
@Documented @Constraint(validatedBy = ContactNumberValidator.class) @Target( { ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface ContactNumberConstraint { String message() default "Invalid phone number"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
با Constraint@ کلاسی که قرار است فیلد را اعتبار سنجی کند مشخص میکنیم
()message پیامی که در صورت معتبر نبودن نمایش داده میشود
حالا یک کلاس Validator میسازیم :
public class ContactNumberValidator implements ConstraintValidator<ContactNumberConstraint, String> { @Override public void initialize(ContactNumberConstraint contactNumber) { } @Override public boolean isValid(String contactField, ConstraintValidatorContext cxt) { return contactField != null && contactField.matches("[0-9]+") && (contactField.length() > 8) && (contactField.length() < 14); } }
این کلاس ConstraintValidator را پیاده سازی میکند که چارچوب اعتبار سنجی را با آن تعریف میکنیم و همچنین متد isValid، این متد قوانین اعتبار سنجی را تعیین میکند
برای تست :
یک کلاس model داریم که یک فیلد phone برای اعتبار سنجی دارد :
@ContactNumberConstraint private String phone;
کلاس controller :
@Controller public class ValidatedPhoneController { @GetMapping("/validatePhone") public String loadFormPage(Model m) { m.addAttribute("validatedPhone", new ValidatedPhone()); return "phoneHome"; } @PostMapping("/addValidatePhone") public String submitForm(@Valid ValidatedPhone validatedPhone, BindingResult result, Model m) { if(result.hasErrors()) { return "phoneHome"; } m.addAttribute("message", "Successfully saved phone: " + validatedPhone.toString()); return "phoneHome"; } }
صفحه view که یک فرم دارد که شماره تلفنی را میگیرد و در صورتی که شماره تلفن معتبر نبود پیام خطا و در صورت معتبر بودن شماره تلفن پیام موفقیت آمیز را نمایش خواهد داد :
<form:form action="/${pageContext.request.contextPath}/addValidatePhone" modelAttribute="validatedPhone"> <label for="phoneInput">Phone: </label> <form:input path="phone" id="phoneInput" /> <form:errors path="phone" cssClass="error" /> <input type="submit" value="Submit" /> </form:form>
تست نهایی :
@Test public void givenPhonePageUri_whenMockMvc_thenReturnsPhonePage(){ this.mockMvc. perform(get("/validatePhone")).andExpect(view().name("phoneHome")); }
@Test public void givenPhoneURIWithPostAndFormData_whenMockMVC_thenVerifyErrorResponse() { this.mockMvc.perform(MockMvcRequestBuilders.post("/addValidatePhone"). accept(MediaType.TEXT_HTML). param("phoneInput", "123")). andExpect(model().attributeHasFieldErrorCode( "validatedPhone","phone","ContactNumberConstraint")). andExpect(view().name("phoneHome")). andExpect(status().isOk()). andDo(print()); }
اعتبار سنجی در سطح کلاس :
میتوانیم اعتبار سنجی را چند فیلد و یا کل یک کلاس سفارشی کنیم مورد استفاده آن برای مواقعی است که بیش از یک فیلد باهم دیتاهایشان باید یکجا بررسی شود
ابتدا annotation آنرا میسازیم:
@Constraint(validatedBy = FieldsValueMatchValidator.class) @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface FieldsValueMatch { String message() default "Fields values don't match!"; String field(); String fieldMatch(); @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @interface List { FieldsValueMatch[] value(); } }
حالا کلاس Validator را میسازیم :
public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch, Object> { private String field; private String fieldMatch; public void initialize(FieldsValueMatch constraintAnnotation) { this.field = constraintAnnotation.field(); this.fieldMatch = constraintAnnotation.fieldMatch(); } public boolean isValid(Object value, ConstraintValidatorContext context) { Object fieldValue = new BeanWrapperImpl(value) .getPropertyValue(field); Object fieldMatchValue = new BeanWrapperImpl(value) .getPropertyValue(fieldMatch); if (fieldValue != null) { return fieldValue.equals(fieldMatchValue); } else { return fieldMatchValue == null; } } }
کلاس مدل را که دو فیلد password و email را دارد :
@FieldsValueMatch.List({ @FieldsValueMatch( field = "password", fieldMatch = "verifyPassword", message = "Passwords do not match!" ), @FieldsValueMatch( field = "email", fieldMatch = "verifyEmail", message = "Email addresses do not match!" ) }) public class NewUserForm { private String email; private String verifyEmail; private String password; private String verifyPassword; // standard constructor, getters, setters }
کلاس controller :
@Controller public class NewUserController { @GetMapping("/user") public String loadFormPage(Model model) { model.addAttribute("newUserForm", new NewUserForm()); return "userHome"; } @PostMapping("/user") public String submitForm(@Valid NewUserForm newUserForm, BindingResult result, Model model) { if (result.hasErrors()) { return "userHome"; } model.addAttribute("message", "Valid form"); return "userHome"; } }
تست آن :
public class ClassValidationMvcTest { private MockMvc mockMvc; @Before public void setup(){ this.mockMvc = MockMvcBuilders .standaloneSetup(new NewUserController()).build(); } @Test public void givenMatchingEmailPassword_whenPostNewUserForm_thenOk() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders .post("/user") .accept(MediaType.TEXT_HTML). .param("email", "john@yahoo.com") .param("verifyEmail", "john@yahoo.com") .param("password", "pass") .param("verifyPassword", "pass")) .andExpect(model().errorCount(0)) .andExpect(status().isOk()); } }
و برای دیدن تست خطا میتوان فیلد های password و email را بصورت غیر یکسان وارد کرد تا نتیجه پیام نامعتبر بودن را ببینیم :
@Test public void givenNotMatchingEmailPassword_whenPostNewUserForm_thenOk() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders .post("/user") .accept(MediaType.TEXT_HTML) .param("email", "john@yahoo.com") .param("verifyEmail", "john@yahoo.commmm") .param("password", "pass") .param("verifyPassword", "passsss")) .andExpect(model().errorCount(2)) .andExpect(status().isOk()); }