Validasi Kustom MVC Musim Semi

1. Ikhtisar

Umumnya, ketika kita perlu memvalidasi input pengguna, Spring MVC menawarkan validator standar yang telah ditentukan sebelumnya.

Namun, saat kami perlu memvalidasi jenis input yang lebih khusus, kami memiliki kemungkinan untuk membuat logika validasi kustom kami sendiri .

Di artikel ini, kami akan melakukannya - kami akan membuat validator khusus untuk memvalidasi formulir dengan bidang nomor telepon, kemudian menampilkan validator khusus untuk beberapa bidang.

Artikel ini berfokus pada Spring MVC. Artikel kami Validasi di Spring Boot menjelaskan cara melakukan validasi kustom di Spring Boot.

2. Penyiapan

Untuk memanfaatkan API, tambahkan dependensi ke file pom.xml Anda :

 org.hibernate hibernate-validator 6.0.10.Final  

Versi terbaru dari ketergantungan tersebut dapat diperiksa di sini.

Jika kita menggunakan Spring Boot, maka kita hanya dapat menambahkan spring-boot-starter-web, yang juga akan membawa dependensi hibernate-validator .

3. Validasi Kustom

Membuat validator khusus mengharuskan kita meluncurkan anotasi kita sendiri dan menggunakannya dalam model kita untuk menegakkan aturan validasi.

Jadi, mari buat validator khusus kita - yang memeriksa nomor telepon . Nomor telepon harus lebih dari delapan digit tetapi tidak lebih dari 11 digit.

4. Anotasi Baru

Mari buat @interface baru untuk mendefinisikan anotasi kita:

@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[] payload() default {}; }

Dengan anotasi @Constraint , kami mendefinisikan kelas yang akan memvalidasi bidang kami, message () adalah pesan kesalahan yang ditampilkan di antarmuka pengguna dan kode tambahan adalah kode boilerplate yang paling sesuai dengan standar Spring.

5. Membuat Validator

Sekarang mari buat kelas validator yang memberlakukan aturan validasi kita:

public class ContactNumberValidator implements ConstraintValidator { @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); } }

Kelas validasi mengimplementasikan antarmuka ConstraintValidator dan harus mengimplementasikan metode isValid ; dalam metode inilah kami mendefinisikan aturan validasi kami.

Secara alami, kami menggunakan aturan validasi sederhana di sini, untuk menunjukkan cara kerja validator.

ConstraintValidator mendefinisikan logika untuk memvalidasi batasan yang diberikan untuk objek tertentu. Penerapan harus mematuhi batasan berikut:

  • objek harus menentukan tipe non-parametrized
  • parameter umum objek harus berupa jenis karakter pengganti tak terbatas

6. Menerapkan Anotasi Validasi

Dalam kasus kami, kami telah membuat kelas sederhana dengan satu bidang untuk menerapkan aturan validasi. Di sini, kami menyiapkan bidang beranotasi kami untuk divalidasi:

@ContactNumberConstraint private String phone;

Kami mendefinisikan bidang string dan menganotasinya dengan anotasi khusus kami @ContactNumberConstraint. Di pengontrol kami, kami membuat pemetaan kami dan menangani kesalahan jika ada:

@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"; } }

Kami mendefinisikan pengontrol sederhana ini yang memiliki satu halaman JSP , dan menggunakan metode submitForm untuk menerapkan validasi nomor telepon kami.

7. Tampilan

Tampilan kami adalah halaman JSP dasar dengan formulir yang memiliki satu bidang. Saat pengguna mengirimkan formulir, maka bidang akan divalidasi oleh validator khusus kami dan dialihkan ke halaman yang sama dengan pesan validasi berhasil atau gagal:

 Phone:      

8. Tes

Sekarang mari kita uji pengontrol kita dan periksa apakah itu memberi kita respons dan tampilan yang sesuai:

@Test public void givenPhonePageUri_whenMockMvc_thenReturnsPhonePage(){ this.mockMvc. perform(get("/validatePhone")).andExpect(view().name("phoneHome")); }

Juga, mari kita uji bahwa bidang kita divalidasi, berdasarkan masukan pengguna:

@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()); }

Dalam pengujian, kami memberikan masukan "123" kepada pengguna, dan - seperti yang kami harapkan - semuanya berfungsi dan kami melihat kesalahan di sisi klien .

9. Validasi Tingkat Kelas Kustom

Anotasi validasi khusus juga dapat ditentukan di tingkat kelas untuk memvalidasi lebih dari satu atribut kelas.

Kasus penggunaan umum untuk skenario ini memverifikasi jika dua bidang kelas memiliki nilai yang cocok.

9.1. Membuat Anotasi

Mari tambahkan anotasi baru yang disebut FieldsValueMatch yang nantinya bisa diterapkan ke kelas. Anotasi akan memiliki dua bidang parameter dan fieldMatch yang mewakili nama bidang untuk dibandingkan:

@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(); } }

Kita dapat melihat anotasi khusus kita juga berisi sub-antarmuka Daftar untuk mendefinisikan beberapa anotasi FieldsValueMatch pada sebuah kelas.

9.2. Membuat Validator

Selanjutnya, kita perlu menambahkan kelas FieldsValueMatchValidator yang akan berisi logika validasi sebenarnya:

public class FieldsValueMatchValidator implements ConstraintValidator { 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; } } }

Metode isValid () mengambil nilai dari dua bidang dan memeriksa apakah nilainya sama.

9.3. Menerapkan Anotasi

Mari kita membuat NewUserForm kelas model ditujukan untuk data yang diperlukan untuk pengguna pendaftaran, yang memiliki dua email dan sandi atribut, bersama dengan dua verifyEmail dan verifyPassword atribut untuk memasukkan kembali dua nilai.

Karena kita memiliki dua bidang untuk diperiksa terhadap bidang yang cocok, mari tambahkan dua anotasi @FieldsValueMatch pada kelas NewUserForm , satu untuk nilai email , dan satu untuk nilai kata sandi :

@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 }

Untuk memvalidasi model di Spring MVC, mari buat pengontrol dengan pemetaan POST / user yang menerima objek NewUserForm yang dianotasi dengan @Valid dan memverifikasi apakah ada kesalahan validasi:

@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"; } }

9.4. Menguji Anotasi

Untuk memverifikasi anotasi tingkat kelas kustom kita, mari tulis pengujian JUnit yang mengirimkan informasi yang cocok ke endpoint / pengguna , lalu memverifikasi bahwa respons tidak berisi error:

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", "[email protected]") .param("verifyEmail", "[email protected]") .param("password", "pass") .param("verifyPassword", "pass")) .andExpect(model().errorCount(0)) .andExpect(status().isOk()); } }

Selanjutnya, mari tambahkan juga pengujian JUnit yang mengirimkan informasi yang tidak cocok ke endpoint / pengguna dan menegaskan bahwa hasilnya akan berisi dua error:

@Test public void givenNotMatchingEmailPassword_whenPostNewUserForm_thenOk() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders .post("/user") .accept(MediaType.TEXT_HTML) .param("email", "[email protected]") .param("verifyEmail", "[email protected]") .param("password", "pass") .param("verifyPassword", "passsss")) .andExpect(model().errorCount(2)) .andExpect(status().isOk()); }

10. Ringkasan

Dalam artikel singkat ini, kami telah menunjukkan cara membuat validator khusus untuk memverifikasi bidang atau kelas dan menyambungkannya ke Spring MVC.

Seperti biasa, Anda dapat menemukan kode dari artikel di Github.