1. Gambaran keseluruhan
Dalam artikel ini, kita akan membincangkan cara menentukan dan mengesahkan batasan kaedah menggunakan Bean Validation 2.0 (JSR-380).
Dalam artikel sebelumnya, kami membincangkan JSR-380 dengan anotasinya yang terbina dalam, dan cara melaksanakan pengesahan harta tanah.
Di sini, kami akan memberi tumpuan kepada pelbagai jenis batasan kaedah seperti:
- kekangan parameter tunggal
- parameter silang
- kekangan pulangan
Juga, kita akan melihat bagaimana mengesahkan kekangan secara manual dan automatik menggunakan Spring Validator.
Untuk contoh berikut, kita memerlukan kebergantungan yang sama seperti di Java Bean Validation Basics.
2. Pengisytiharan Kekangan Kaedah
Untuk memulakan, pertama-tama kita akan membincangkan cara menyatakan kekangan pada parameter kaedah dan nilai kembali kaedah .
Seperti yang telah disebutkan sebelumnya, kita dapat menggunakan anotasi dari javax.validation.constraints , tetapi kita juga dapat menentukan batasan tersuai (misalnya untuk batasan khusus atau batasan parameter silang).
2.1. Kekangan Parameter Tunggal
Mendefinisikan kekangan pada parameter tunggal adalah mudah. Kita hanya perlu menambahkan anotasi pada setiap parameter seperti yang diperlukan :
public void createReservation(@NotNull @Future LocalDate begin, @Min(1) int duration, @NotNull Customer customer) { // ... }
Begitu juga, kita boleh menggunakan pendekatan yang sama untuk pembina:
public class Customer { public Customer(@Size(min = 5, max = 200) @NotNull String firstName, @Size(min = 5, max = 200) @NotNull String lastName) { this.firstName = firstName; this.lastName = lastName; } // properties, getters, and setters }
2.2. Menggunakan Kekangan Lintas Parameter
Dalam beberapa kes, kita mungkin perlu mengesahkan beberapa nilai sekaligus, misalnya, dua jumlah berangka satu lebih besar daripada yang lain.
Untuk senario ini, kita dapat menentukan batasan parameter silang khusus, yang mungkin bergantung pada dua atau lebih parameter.
Kekangan parameter silang boleh dianggap sebagai pengesahan kaedah yang setara dengan kekangan tahap kelas . Kami dapat menggunakan kedua-duanya untuk melaksanakan pengesahan berdasarkan beberapa sifat.
Mari fikirkan contoh mudah: variasi kaedah createReservation () dari bahagian sebelumnya mengambil dua parameter jenis LocalDate: tarikh mula dan tarikh akhir.
Oleh itu, kami ingin memastikan bahawa permulaan adalah pada masa depan, dan akhir adalah selepas permulaan . Tidak seperti pada contoh sebelumnya, kita tidak dapat menentukan ini menggunakan batasan parameter tunggal.
Sebaliknya, kita memerlukan kekangan parameter silang.
Berbeza dengan batasan parameter tunggal, batasan parameter silang dinyatakan pada kaedah atau konstruktor :
@ConsistentDateParameters public void createReservation(LocalDate begin, LocalDate end, Customer customer) { // ... }
2.3. Membuat Kekangan Lintas Parameter
Untuk melaksanakan kekangan @ConsistentDateParameters , kami memerlukan dua langkah.
Pertama, kita perlu menentukan anotasi kekangan :
@Constraint(validatedBy = ConsistentDateParameterValidator.class) @Target({ METHOD, CONSTRUCTOR }) @Retention(RUNTIME) @Documented public @interface ConsistentDateParameters { String message() default "End date must be after begin date and both must be in the future"; Class[] groups() default {}; Class[] payload() default {}; }
Di sini, ketiga sifat ini wajib untuk penjelasan kekangan:
- message - mengembalikan kunci lalai untuk membuat mesej ralat, ini membolehkan kita menggunakan interpolasi mesej
- kumpulan - membolehkan kami menentukan kumpulan pengesahan untuk kekangan kami
- muatan - boleh digunakan oleh klien dari Bean Validation API untuk menetapkan objek muatan tersuai kepada kekangan
Untuk perincian bagaimana menentukan batasan khusus, lihat dokumentasi rasmi.
Selepas itu, kita dapat menentukan kelas validator:
@SupportedValidationTarget(ValidationTarget.PARAMETERS) public class ConsistentDateParameterValidator implements ConstraintValidator { @Override public boolean isValid( Object[] value, ConstraintValidatorContext context) { if (value[0] == null || value[1] == null) { return true; } if (!(value[0] instanceof LocalDate) || !(value[1] instanceof LocalDate)) { throw new IllegalArgumentException( "Illegal method signature, expected two parameters of type LocalDate."); } return ((LocalDate) value[0]).isAfter(LocalDate.now()) && ((LocalDate) value[0]).isBefore((LocalDate) value[1]); } }
Seperti yang dapat kita lihat, kaedah isValid () mengandungi logik pengesahan yang sebenarnya. Pertama, kami memastikan bahawa kami mendapat dua parameter jenis LocalDate. Selepas itu, kami memeriksa sama ada kedua-duanya ada di masa hadapan dan akhirnya adalah selepas bermula .
Juga, penting untuk diperhatikan bahawa anotasi @SupportedValidationTarget (ValidationTarget . PARAMETERS) pada kelas ConsistentDateParameterValidator diperlukan. Sebabnya adalah kerana @ConsistentDateParameter ditetapkan pada tahap kaedah, tetapi kekangan harus diterapkan pada parameter kaedah (dan bukan pada nilai kembali kaedah, seperti yang akan kita bincangkan di bahagian berikutnya).
Catatan: spesifikasi Bean Validation mengesyorkan untuk menganggap nilai null sebagai sah. Sekiranya null bukan nilai yang sah, penggantian @NotNull seharusnya digunakan.
2.4. Kekangan Nilai Pulangan
Kadang kala kita perlu mengesahkan objek kerana dikembalikan dengan kaedah. Untuk ini, kita boleh menggunakan kekangan nilai kembali.
Contoh berikut menggunakan kekangan terbina dalam:
public class ReservationManagement { @NotNull @Size(min = 1) public List getAllCustomers() { return null; } }
Untuk getAllCustomers () , batasan berikut berlaku:
- Pertama, senarai yang dikembalikan tidak boleh kosong dan mesti mempunyai sekurang-kurangnya satu entri
- Tambahan pula, senarai tersebut tidak boleh mengandungi entri kosong
2.5. Kekangan Khusus Nilai Pulangan
Dalam beberapa kes, kami mungkin juga perlu mengesahkan objek kompleks:
public class ReservationManagement { @ValidReservation public Reservation getReservationsById(int id) { return null; } }
Dalam contoh ini, objek Pemesanan yang dikembalikan mesti memenuhi batasan yang ditentukan oleh @ValidReservation , yang akan kami tentukan seterusnya.
Sekali lagi, pertama kita harus menentukan anotasi kekangan :
@Constraint(validatedBy = ValidReservationValidator.class) @Target({ METHOD, CONSTRUCTOR }) @Retention(RUNTIME) @Documented public @interface ValidReservation { String message() default "End date must be after begin date " + "and both must be in the future, room number must be bigger than 0"; Class[] groups() default {}; Class[] payload() default {}; }
After that, we define the validator class:
public class ValidReservationValidator implements ConstraintValidator { @Override public boolean isValid( Reservation reservation, ConstraintValidatorContext context) { if (reservation == null) { return true; } if (!(reservation instanceof Reservation)) { throw new IllegalArgumentException("Illegal method signature, " + "expected parameter of type Reservation."); } if (reservation.getBegin() == null || reservation.getEnd() == null || reservation.getCustomer() == null) { return false; } return (reservation.getBegin().isAfter(LocalDate.now()) && reservation.getBegin().isBefore(reservation.getEnd()) && reservation.getRoom() > 0); } }
2.6. Return Value in Constructors
As we defined METHOD and CONSTRUCTOR as target within our ValidReservation interface before, we can also annotate the constructor of Reservation to validate constructed instances:
public class Reservation { @ValidReservation public Reservation( LocalDate begin, LocalDate end, Customer customer, int room) { this.begin = begin; this.end = end; this.customer = customer; this.room = room; } // properties, getters, and setters }
2.7. Cascaded Validation
Finally, the Bean Validation API allows us to not only validate single objects but also object graphs, using the so-called cascaded validation.
Hence, we can use @Valid for a cascaded validation, if we want to validate complex objects. This works for method parameters as well as for return values.
Let's assume that we have a Customer class with some property constraints:
public class Customer { @Size(min = 5, max = 200) private String firstName; @Size(min = 5, max = 200) private String lastName; // constructor, getters and setters }
A Reservation class might have a Customer property, as well as further properties with constraints:
public class Reservation { @Valid private Customer customer; @Positive private int room; // further properties, constructor, getters and setters }
If we now reference Reservation as a method parameter, we can force the recursive validation of all properties:
public void createNewCustomer(@Valid Reservation reservation) { // ... }
As we can see, we use @Valid at two places:
- On the reservation-parameter: it triggers the validation of the Reservation-object, when createNewCustomer() is called
- As we have a nested object graph here, we also have to add a @Valid on the customer-attribute: thereby, it triggers the validation of this nested property
This also works for methods returning an object of type Reservation:
@Valid public Reservation getReservationById(int id) { return null; }
3. Validating Method Constraints
After the declaration of constraints in the previous section, we can now proceed to actually validate these constraints. For that, we have multiple approaches.
3.1. Automatic Validation With Spring
Spring Validation provides an integration with Hibernate Validator.
Note: Spring Validation is based on AOP and uses Spring AOP as the default implementation. Therefore, validation only works for methods, but not for constructors.
If we now want Spring to validate our constraints automatically, we have to do two things:
Firstly, we have to annotate the beans, which shall be validated, with @Validated:
@Validated public class ReservationManagement { public void createReservation(@NotNull @Future LocalDate begin, @Min(1) int duration, @NotNull Customer customer){ // ... } @NotNull @Size(min = 1) public List getAllCustomers(){ return null; } }
Secondly, we have to provide a MethodValidationPostProcessor bean:
@Configuration @ComponentScan({ "org.baeldung.javaxval.methodvalidation.model" }) public class MethodValidationConfig { @Bean public MethodValidationPostProcessor methodValidationPostProcessor() { return new MethodValidationPostProcessor(); } }
The container now will throw a javax.validation.ConstraintViolationException, if a constraint is violated.
If we are using Spring Boot, the container will register a MethodValidationPostProcessor bean for us as long as hibernate-validator is in the classpath.
3.2. Automatic Validation With CDI (JSR-365)
As of version 1.1, Bean Validation works with CDI (Contexts and Dependency Injection for Jakarta EE).
If our application runs in a Jakarta EE container, the container will validate method constraints automatically at the time of invocation.
3.3. Programmatic Validation
For manual method validation in a standalone Java application, we can use the javax.validation.executable.ExecutableValidator interface.
We can retrieve an instance using the following code:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); ExecutableValidator executableValidator = factory.getValidator().forExecutables();
ExecutableValidator offers four methods:
- validateParameters() and validateReturnValue() for method validation
- validateConstructorParameters() and validateConstructorReturnValue() for constructor validation
Validating the parameters of our first method createReservation() would look like this:
ReservationManagement object = new ReservationManagement(); Method method = ReservationManagement.class .getMethod("createReservation", LocalDate.class, int.class, Customer.class); Object[] parameterValues = { LocalDate.now(), 0, null }; Set
violations = executableValidator.validateParameters(object, method, parameterValues);
Note: The official documentation discourages to call this interface directly from the application code, but to use it via a method interception technology, like AOP or proxies.
Sekiranya anda berminat bagaimana menggunakan antara muka ExecutableValidator , anda boleh melihat dokumentasi rasmi.
4. Kesimpulan
Dalam tutorial ini, kami melihat sekilas cara menggunakan batasan kaedah dengan Hibernate Validator, juga kami membincangkan beberapa ciri baru JSR-380.
Pertama, kami membincangkan cara menyatakan pelbagai jenis kekangan:
- Kekangan parameter tunggal
- Parameter silang
- Kekangan nilai pulangan
Kami juga melihat bagaimana mengesahkan kekangan secara manual dan automatik menggunakan Spring Validator.
Seperti biasa, kod sumber penuh contoh terdapat di GitHub.