برای شروع کتابخانه 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());
}