Validasi Kacang Musim Semi
Validasi data bukanlah topik baru dalam pengembangan aplikasi web.
Kami melihat sekilas validasi data di ekosistem Java secara umum dan Spring Framework secara khusus. Platform Java telah menjadi standar de facto untuk mengimplementasikan validasi data yaitu spesifikasi Bean Validation. Spesifikasi Bean Validation memiliki beberapa versi:
- 1.0 (JSR-303),
- 1.1 (JSR-349),
- 2.0 (JSR 380) – versi terbaru

Spesifikasi ini mendefinisikan satu set komponen, antarmuka, dan anotasi. Ini menyediakan cara standar untuk menempatkan batasan pada parameter dan mengembalikan nilai metode dan parameter konstruktor, menyediakan API untuk memvalidasi objek dan grafik objek.
Model Deklaratif digunakan untuk memberikan batasan berupa anotasi pada objek dan bidangnya. Ada anotasi standar seperti @NotNull
, @Digits
, @Pattern
, @Email
, @CreditCard
. Ada kemampuan untuk membuat batasan kustom baru.
Validasi dapat berjalan secara manual atau lebih alami, ketika spesifikasi dan kerangka kerja lain memvalidasi data pada waktu yang tepat, misalnya, pengguna memasukkan, menyisipkan, atau memperbarui di JPA.
Validasi dalam Contoh Java
Mari kita lihat bagaimana hal itu dapat dilakukan dalam praktik dalam contoh Validasi Kacang sederhana ini di dalam aplikasi Java biasa.
Kami memiliki objek yang ingin kami validasi dengan semua bidang yang dianotasi dengan batasan.
public class SimpleDto {
@Min(value = 1, message = "Id can't be less than 1 or bigger than 999999")
@Max(999999)
private int id;
@Size(max = 100)
private String name;
@NotNull
private Boolean active;
@NotNull
private Date createdDatetime;
@Pattern(regexp = "^asc|desc$")
private String order = "asc";
@ValidCategory(categoryType="simpleDto")
private String category;
…
Constructor, getters and setters
Sekarang kita dapat menggunakannya dalam aplikasi Java sederhana dan memvalidasi objek secara manual.
public class SimpleApplication {
public static void main(String[] args) {
final SimpleDto simpleDto = new SimpleDto();
simpleDto.setId(-1);
simpleDto.setName("Test Name");
simpleDto.setCategory("simple");
simpleDto.setActive(true);
simpleDto.setOrder("asc");
simpleDto.setCreatedDatetime(new Date());
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.usingContext().getValidator();
Set constrains = validator.validate(simpleDto);
for (ConstraintViolation constrain : constrains) {
System.out.println(
"[" + constrain.getPropertyPath() + "][" + constrain.getMessage() + "]"
);
}
}
}
Dan hasil di Console akan menjadi:
“[id] [Id can't be less than 1 or bigger than 999999]”
Batasan Validasi dan Anotasi
Buat batasan dan anotasi validasi khusus.
@Retention(RUNTIME)
@Target(FIELD)
@Constraint(validatedBy = {ValidCategoryValidator.class})
public @interface ValidCategory {
String categoryType();
String message() default "Category is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And constraint validation implementation:
public class ValidCategoryValidator implements ConstraintValidator<ValidCategory, String> {
private static final Map<String, List> availableCategories;
static {
availableCategories = new HashMap<>();
availableCategories.put("simpleDto", Arrays.asList("simple", "advanced"));
}
private String categoryType;
@Override
public void initialize(ValidCategory constraintAnnotation) {
this.setCategoryType(constraintAnnotation.categoryType());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
List categories = ValidCategoryValidator.availableCategories.get(categoryType);
if (categories == null || categories.isEmpty()) {
return false;
}
for (String category : categories) {
if (category.equals(value)) {
return true;
}
}
return false;
}
}
Dalam contoh di atas, kategori yang tersedia berasal dari peta hash sederhana. Dalam kasus penggunaan aplikasi nyata, mereka dapat diambil dari database atau layanan lainnya.
Harap dicatat bahwa batasan dan validasi dapat ditentukan dan dilakukan tidak hanya pada tingkat bidang, tetapi juga pada seluruh objek.
Saat kita perlu memvalidasi berbagai dependensi bidang, misalnya, tanggal mulai, itu tidak boleh setelah tanggal akhir.
Implementasi Validasi Kacang yang paling banyak digunakan spesifikasinya adalah Validator Hibernasi dan Apache BVal .
Validasi dengan Spring
Kerangka kerja Spring menyediakan beberapa fitur untuk validasi.
- Dukungan untuk API Validasi Kacang versi 1.0, 1.1 (JSR-303, JSR-349) diperkenalkan di Kerangka Musim Semi yang dimulai dengan versi 3.
- Spring memiliki antarmuka Validatornya sendiri yang sangat mendasar dan dapat diatur dalam instance DataBinder tertentu. Ini bisa berguna untuk menerapkan logika validasi tanpa anotasi.
Validasi Kacang dengan Spring
Spring Boot menyediakan validasi yang dimulai yang dapat disertakan dalam proyek:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Starter ini menyediakan versi Validator Hibernasi kompatibel dengan Spring Boot saat ini.
Menggunakan Validasi Kacang, kami dapat memvalidasi badan permintaan, parameter kueri, variabel di dalam jalur (mis. / /simpledto/{id} ), atau metode atau parameter konstruktor apa pun.
Permintaan POST atau PUT
Dalam permintaan POST atau PUT, misalnya, kami melewati payload JSON, Spring secara otomatis mengubahnya menjadi objek Java dan sekarang kami ingin memvalidasi objek yang dihasilkan. Mari gunakan SimpleDto
objek dari 1 contoh:
@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {
@Autowired
private SimpleDtoService simpleDtoService;
@RequestMapping(path = "", method = RequestMethod.POST, produces =
"application/json")
public SimpleDto createSimpleDto(
@Valid @RequestBody SimpleDto simpleDto) {
SimpleDto result = simpleDtoService.save(simpleDto);
return result;
}
}
Kami baru saja menambahkan @Valid
anotasi ke SimpleDto
parameter yang dianotasi dengan @RequestBody
. Ini akan memberi tahu Spring untuk memproses validasi sebelum melakukan pemanggilan metode yang sebenarnya. Jika validasi gagal, Spring akan menampilkan MethodArgument NotValidException
yang, secara default, akan mengembalikan respons 400 (Permintaan Buruk).
Validasi Variabel Jalur
Memvalidasi Variabel Jalur bekerja sedikit berbeda. Masalahnya adalah sekarang kita harus menambahkan anotasi batasan langsung ke parameter metode, bukan di dalam objek.
Untuk membuatnya berfungsi, ada 2 opsi yang memungkinkan:
Opsi 1:@Anotasi yang Divalidasi
Tambahkan @Validated
anotasi ke pengontrol di tingkat kelas untuk mengevaluasi anotasi kendala pada parameter metode.
Opsi 2:Variabel Jalur
Gunakan objek yang mewakili variabel jalur seperti yang terlihat pada contoh di bawah ini:
@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {
@Autowired
private SimpleDtoService simpleDtoService;
@RequestMapping(path = "/{simpleDtoId}", method = RequestMethod.GET, produces =
"application/json")
public SimpleDto getSimpleDto(
@Valid SimpleDtoIdParam simpleDtoIdParam) {
SimpleDto result = simpleDtoService.findById(simpleDtoIdParam.getSimpleDtoId());
if (result == null) {
throw new NotFoundException();
}
return result;
}
}
Dalam hal ini, kami memiliki SimpleDtoIdParam
kelas yang berisi simpleDtoId
bidang yang akan divalidasi terhadap anotasi batasan Bean standar atau kustom. Nama Variabel Jalur (/{simpleDtoId} ) harus sama dengan nama bidang (sehingga Spring akan dapat menemukan penyetel untuk bidang ini).
private static final long serialVersionUID = -8165488655725668928L;
@Min(value = 1)
@Max(999999)
private int simpleDtoId;
public int getSimpleDtoId() {
return simpleDtoId;
}
public void setSimpleDtoId(int simpleDtoId) {
this.simpleDtoId = simpleDtoId;
}
}
Berbeda dengan Badan Permintaan validasi, Variabel Jalur validasi melempar ConstraintViolationException
alih-alih MethodArgumentNotValidException
. Oleh karena itu, kita perlu membuat penangan pengecualian khusus.
Kerangka kerja Java Spring juga memungkinkan memvalidasi parameter pada tingkat layanan dengan @Validated
anotasi (tingkat kelas) dan @Valid
(tingkat parameter).
Mengingat Spring JPA menggunakan Hibernate di bawahnya, ia juga mendukung Validasi Bean untuk kelas entitas. Harap dicatat bahwa mungkin bukan ide yang baik dalam banyak kasus mengandalkan tingkat validasi ini karena itu berarti bahwa semua logika sebelumnya berurusan dengan objek yang tidak valid.
Antarmuka Validasi Musim Semi
Spring mendefinisikan antarmukanya sendiri untuk Validator validasi (org.springframework.validation.Validator). Ini dapat disetel untuk instance DataBinder tertentu dan mengimplementasikan validasi tanpa anotasi (pendekatan non-deklaratif).
Untuk menerapkan pendekatan ini, kita perlu:
- Menerapkan Antarmuka Validator
- Tambahkan Validator
Menerapkan Antarmuka Validator
Implementasikan antarmuka Validator, misalnya mari bekerja dengan SimpleDto
. kami kelas:
@Component
public class SpringSimpleDtoValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return SimpleDto.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
if (errors.getErrorCount() == 0) {
SimpleDto param = (SimpleDto) target;
Date now = new Date();
if (param.getCreatedDatetime() == null) {
errors.reject("100",
"Create Date Time can't be null");
} else if (now.before(param.getCreatedDatetime())) {
errors.reject("101",
"Create Date Time can't be after current date time");
}
}
}
}
Periksa di sini apakah Datetime
created yang dibuat stempel waktu ada di masa mendatang.
Tambahkan Validator
Tambahkan implementasi Validator ke DataBinder
:
@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {
@Autowired
private SimpleDtoService simpleDtoService;
@Autowired
private SpringSimpleDtoValidator springSimpleDtoValidator;
@InitBinder("simpleDto")
public void initMerchantOnlyBinder(WebDataBinder binder) {
binder.addValidators(springSimpleDtoValidator);
}
@RequestMapping(path = "", method = RequestMethod.POST, produces =
"application/json")
public SimpleDto createSimpleDto(
@Valid @RequestBody SimpleDto simpleDto) {
SimpleDto result = simpleDtoService.save(simpleDto);
return result;
}
}
Sekarang kita memiliki SimpleDto
divalidasi menggunakan anotasi batasan dan implementasi Validasi Musim Semi khusus kami.