Quite often there is a need to apply validation to some of the bean's properties based on the state of the other properties of the same bean. Bean Validation allows to do this in several ways:
- usage of @AssertTrue on a getter method with validation logic
- usage of @ScriptAssert on a class level (Hibernate Validator specific)
- usage of custom constraint annotation on a class level
To show each of the approaches let us consider two classes representing a library member and a book (LibraryMember and Book).
Library members that have passed the trial period of their subscription can take as many books as they'd like, but also as they are regular members - they should be reading at least one book. Other users that are still on a trial period
can have one book at most. Now that we have the beans and know the validation logic - we can proceed to the examples on how such validation rules can be applied using Bean Validation.
Usage of @AssertTrue on a getter method
When any bean is validated by Bean Validation each of its properties (either fields or getters) is checked for
constraints, and if any are present - they are validated. Based on this we can create a custom method returning a
boolean, with
the getter signature, which will contain validation logic. Having such getter in place, we can apply any boolean
constraint to it, either @AssertTrue or @AssertFalse would
work. As we want to check for the bean correctness it make sense to return true from the
getter, if the bean is valid. Hence we use @AssertTrue.
Using the @AssertFalse is
also perfectly fine if it suites the logic more. The updated library member class will look like:
Important thing to note here is that the validated getter method LibraryMember#isValidMember() is private. This was done deliberately, as we don't want to expose any unnecessary methods as part of the visible API of the particular bean. Such methods should only be used by the Bean Validation and not called explicitly by the users. To make sure that validation works correctly and more importantly - that such private getter is picked up by the Bean Validation we can run a few tests:
We can also check that the constraint is not lost and is "inherited" by the subclasses. For that we can create ExtendedLibraryMember that extends our
LibraryMember with constructors matching the ones from the super class and nothing else.
And the corresponding test will look like:
Such approach is fast and easy to implement. But it also has some downsides as well. First of all, we mix the validation logic with the bean logic, we mix multiple responsibilities in one class. While it might be fine for a few such rules, it gets much messier as more validation rules we have. Also we cannot easily test these validation rules separately.
Usage of @ScriptAssert as a class level constraint
The previous approach had some downsides, maybe this one would be better? Hibernate Validator as a reference implementation of Bean Validation provides additional functionality and constraints. One of such constraints is @ScriptAssert, which allows to write simple class level constraints using script expressions written in one of the JSR 223 complaint scripting languages. To apply the same validation rule as in the previous case we simply need to add this constraint on a class level as in the following example:
As we can see we don't need to modify the class itself. There is no need for additional getters or anything else. And we can
run the similar test to make sure that everything works:
We moved the validation logic out of the bean into the script expression, but it also made us add additional
dependencies for the scripting language that was chosen (groovy in our case). And also such code is locked from switching to other Bean Validation implementations as the Hibernate Validator specific constraint was used. It is also worth mentioning that
@ScriptAssert is
a repeatable annotation, hence we can add as many validation rules as we'd like, and that's a good thing.
Usage of custom constraint on a class level
To be able to use a custom constraint approach we need to create an annotation for it first:
And implement a ConstraintValidator<ValidLibraryMember, LibraryMember>
interface as a validator which will contain our validation rule logic.
For a more detailed instructions on how to create and add custom constraint please see this post Adding custom constraint definitions via the Java service loader.With the constraint and validator in place we can annotate our class with the new constraint annotation: and run the same set of tests as in other cases. This approach allows us to move the validation logic out of the bean and also gives us more flexibility as we have access to ConstraintValidatorContext, and in case of Hibernate Validator - to HibernateConstraintValidatorInitializationContext initialization context as well. For example we can provide different messages for different types of library members. This can be done with the usage of ConstraintValidatorContext as in the example below, where constraint violation is built manually: This approach is more flexible but it also requires a bit more work and a few more classes to write.
Conclusions
We looked at a few ways on how we can implement validation rules, which require knowledge of the state of multiple properties. Let's point out the advantages and disadvantages of each approach as well as when they should be used.Using getter and @AssertTrue.
Advantages:
- Probably the fastest and easiest of all the others to achieve the result
- Validation logic lives in the bean
- Hard to test separate validation rules, as getters are not publicly available
- Can be used when there's a very small amount of such getters and they are relatively simple
Using class level @ScriptAssert.
Advantages:
Advantages:
- When compared to the previous case - removes the validation logic from the bean to script parameter of the annotation. Which removes unnecessary code from the bean itself.
- Additional dependencies for scripting engine
- A strong dependency on Hibernate Validator. Not a big issue as most likely you are using it anyway, but for those who want to be independent from implementations this approach wouldn't work
- Still hard to test separate validation rules
- Similarly to the previous case, can be used when there's relatively small amount of rules with additional condition that corresponding required dependencies are already added for different reasons.
Using custom class level constraint.
Advantages:
Advantages:
- Gives a lot of flexibility
- Easy to test separate rules, as each of validator classes can be tested on its own
- Moves the validation rule logic out of the bean
- Requires to write more classes
- Can be used any time, as long as adding a few classes is not a problem.
All the code samples can be found on GitHub.
To be continued...
COMMENTS