You have to work very hard to make it work effortlessly. This article has been
https://www.yourbatman.cnIncluded, which together with the Spring technology stack, MyBatis, JVM, middleware and other small and beautiful
columnFor free learning. Follow the public account [
The Utopia of BAT【 break down one by one, thoroughly grasp, refuse to be tasted.

The foreword ✍

Hello, I’m YourBatman.

The previous article covered the differences between JSR, Bean Validation, and Hibernate Validator, as well as the code that showed how to do annotation-based Java Bean Validation, which is a convenient way to do contract programming in the Java world.

But have you ever considered that most of the time, we just have some simple independent arguments (such as method input int age), and we don’t need to make a big effort to install a Java Bean, for example, I want to write something like this to achieve the corresponding constraint effect:

public @NotNull Person getOne(@NotNull @Min(1) Integer id, String name) { ... };

This article explores how to use Bean Validation to gracefully and declaratively validate method parameters and return values as well as constructor parameters and return values.

declarativeIn addition to the benefit of having elegant, non-intrusive code, there is a non-negligible advantage: it’s much more usable for anyone who knows the semantics just by looking at the declaration, without having to know your implementation
A sense of security.

Version of the agreement

  • Bean Validation version:2.0.2
  • Hibernate Validator Version:6.1.5. The Final

✍ body

Bean Validation version 1.0 only supports validating Java beans, and version 1.1 now supports validating methods/constructors using the executableValidator that was added in version 1.1:

Public interface ExecutableValidator {// ExecutableValidator <T> Set< ConstraintCheesecake <T>> validateParameters(T object, Method Method, object [] parameterValues, Class<? >... groups); <T> Set<ConstraintViolation<T>> validateReturnValue(T object, Method method, Object returnValue, Class<? >... groups); Calibration: / / Constructor parameter + return value < T > Set < ConstraintViolation < T > > validateConstructorParameters (Constructor <? extends T> constructor, Object[] parameterValues, Class<? >... groups); <T> Set<ConstraintViolation<T>> validateConstructorReturnValue(Constructor<? extends T> constructor, T createdObject, Class<? >... groups); }

Actually we is no stranger to the word the Executable, interface to the JDK Java. Lang. Reflect. The Executable it’s only two two implementations is Method and Constructor, and here just photograph echo.

Before the following code example, we provide two methods to get the validator (using the default configuration) for later use:

Private Validator obtainValidator() {// 1, Use the [default configuration] to get a Validator factory. This configuration can be provided by the provider or SPI ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); / / 2, get a validator return validatorFactory. GetValidator (); } / / method is used to check the validator private ExecutableValidator obtainExecutableValidator () {return obtainValidator () forExecutables (); }

Because validators such as the Validator are thread-safe, there is generally only one copy per application globally, and therefore only one initialization is required.

Check the Java Bean

Let’s review how you validate Java beans. Write the JavaBeans and validators (all using the JSR standard API) and declare the constraint annotations:

@ToString @Setter @Getter public class Person { @NotNull public String name; @NotNull @Min(0) public Integer age; } @Test public void test1() { Validator validator = obtainValidator(); Person person = new Person(); person.setAge(-1); Set<ConstraintViolation<Person>> result = validator.validate(person); Map (v-> v.getPropertyPath() + "" + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println); }

Run the program, console output:

Name cannot be NULL: NULL age needs to be between 1 and 18: -1

This is the most classic application. So the question is, if your method parameter is a Java Bean, how do you validate it?

Tip: Some people think that annotating a constraint on a property has the same effect as annotating a set method,
It’s notThe reason you have this illusion is because Spring handles the writing for you, which will unfold later when you integrate with Spring

Calibration method

Validation of the method is the focus of this paper. Let’s say I have a Service like this:

public class PersonService { public Person getOne(Integer id, String name) { return null; }}

Now, the following constraints are required for the implementation of this method:

  1. Id is required (not null) and the minimum value is 1, but name is not required
  2. The return value cannot be NULL

The following parts are divided into two parts: validation method parameters and validation return values.

Verify method parameters

As mentioned above, the getOne method takes two incoming arguments, and we need to check the id parameter. If Bean Validation is not used, the code should write the Validation logic like this:

Public Person getOne(Integer ID, String Name) {if (id == null) {throw new IllegalArgumentException(" ID cannot be null"); } if (id < 1) {throw new IllegalArgumentException("id must be greater than or equal to 1"); } return null; }

There’s nothing wrong with this, but the downside is obvious:

  1. This type of code is not very nutritious, and if the validation logic is a little bit more, it will look very long
  2. Callers can’t know your semantics without looking at your execution logic. For example, it doesn’t know if the ID is to be passed or not to be passed, so there’s no contract
  3. The code is intrusive

Optimization scheme

Now that you have learned Bean Validation, it is more elegant to leave the Validation aspect to a more professional one:

public Person getOne(@NotNull @Min(1) Integer id, String Name) throws NoSuchMethodException {Method currMethod = this.getClass().getMethod("getOne"); Integer.class, String.class); Set<ConstraintViolation<PersonService>> validResult = obtainExecutableValidator().validateParameters(this, currMethod, new Object[]{id, name}); if (! validResult.isEmpty()) { // ... ValidResult validResult. Stream (). Map (v-> v.getPropertyPath() + "" + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println); Throw new IllegalArgumentException(" Parameter error "); } return null; }

The test procedure is very simple:

@Test public void test2() throws NoSuchMethodException {new PersonService().getOne(0, "A "); }

Run the program, console output:

GetOne. Arg0 minimum not less than 1:0 Java. Lang. IllegalArgumentException: parameter error...

It met expectations perfectly. But what the hell is Arg 0? If you are interested, you can add the compiler parameters and run again. There is a surprise

By annotating the constraint rules, two of the above three problems are successfully solved, especially the declarative constraint to solve problem 3, which is very helpful for the improvement of development efficiency, because the contract has been formed.

There’s another problem: the code is intrusive. Yes, the validation logic is still written in the method body, but when it comes to solving code intrusion problems, I don’t need to mention AOP to think about it. In general, we have two approaches to AOP available:

  1. Java EE based implementation of @Inteceptors
  2. Based on Spring Framework implementation

Obviously, the former is the official Java standard and the latter is the actual standard, so Mark will leave this minor issue until we get to Bean Validation and Spring integration later on.

Check the return value of the method

Validation of return values is something that many people may not have heard of, or touched on very rarely, compared to method parameters. In principle, a method should be responsible for its inputs and outputs: a valid input, an explicit output, and this explicit output should preferably be constrained.

The getOne method title above requires that the return value cannot be NULL. Return if(result == null); return if(result == null);

public Person getOne(Integer id, String name) throws NoSuchMethodException { // ... Person result = null; Person result = null; Person result = null; Person result = null; Person result = null; If (result == null) {throw new IllegalArgumentException(" The return result cannot be null"); } return result; }

Again, there are three problems with this code:

  1. This type of code is not very nutritious, and if the validation logic is a little bit more, it will look very long
  2. Callers can’t know your semantics without looking at your execution logic. For example, the caller does not know if the return is possible to be null, and no contract is formed
  3. The code is intrusive

Optimization scheme

Without more words, go straight to the code.

public @NotNull Person getOne(@NotNull @Min(1) Integer id, String name) throws NoSuchMethodException { // ... Person result = null; Person result = null; // Check Method currMethod = this.getClass().getMethod("getOne", Integer.class, String.class); Set<ConstraintViolation<PersonService>> validResult = obtainExecutableValidator().validateReturnValue(this, currMethod, result); if (! validResult.isEmpty()) { // ... ValidResult validResult. Stream (). Map (v-> v.getPropertyPath() + "" + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println); Throw new IllegalArgumentException(" Parameter error "); } return result; }

Write test code:

@test public void test2() throws NoSuchMethodException {@test public void test2() throws NoSuchMethodException {@test public void test2() throws NoSuchMethodException PersonService (.) getOne (1, "A brother"); }

Run the program, console output:

GetOne. < value > return cannot be null: null Java. Lang. IllegalArgumentException: parameter error...

There is a small detail here: when you call getOne and have IDEA automatically fill in the return value for you, the validation rules are shown to you earlier, which is the contract. Obviously, when you get such a result, can you use it with confidence and no longer use “if(XXX! =null). This is the power of contract programming. You can increase your programming efficiency exponentially within your team. Try it

Check construction method

Well, uh, (⊙, ⊙)… . Play by yourself, remember ~

Added: How are Java beans checked as input parameters?

If a Java Bean is a method parameter, how do you use Bean Validation?

public void save(Person person) {
}

The following reasonable requirements can be put forward for constraints:

  1. Person cannot be NULL
  2. It’s a legitimate Person model. In other words, you have to follow all the validation rules in Person

Add validation to the save method as follows:

public void save(@NotNull Person person) throws NoSuchMethodException { Method currMethod = this.getClass().getMethod("save", Person.class); Set<ConstraintViolation<PersonService>> validResult = obtainExecutableValidator().validateParameters(this, currMethod, new Object[]{person}); if (! validResult.isEmpty()) { // ... ValidResult validResult. Stream (). Map (v-> v.getPropertyPath() + "" + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println); Throw new IllegalArgumentException(" Parameter error "); }}

Write the test program:

@Test public void test3() throws NoSuchMethodException {// save. Arg0 cannot be null: null // new PersonService().save(null); new PersonService().save(new Person()); }

Runs the program, the console has no output, which means the validation passes. Obviously, the newly created Person is not a valid model object, so it can be concluded that the validation logic in the model has not been implemented. What can we do? Do I still need to use the Validator to validate with the API?

All right, to be clear, the famous @Valid annotation at this point reads:

public void save(@NotNull @Valid Person person) throws NoSuchMethodException { ... }

Run the test program again, and the console outputs:

Save. Arg0. The name cannot be null: null. Save arg0. Age is not null: null Java. Lang. IllegalArgumentException: parameter error...

This is really perfect.

Tip:
@ValidAnnotations for Validation
cascadeProperty, method parameter, or method return type. For example, if your property is still a Java Bean and you want to drill down to validate its constraints, put this annotation on the property header. In addition, you can do this by using @Valid
Recursive validation, so you can annotate a List and perform a check on each object inside it

As an aside: I’m sure you asked about the difference between the @Valid and the @ appropriateness provided by Spring. The answer is: not the same thing at all. It’s a pure coincidence. As for why this is the case, it will be clear when you integrate it with Spring.

Extra 2: Should annotations be written on the interface or the implementation?

This is one of the interview questions I like to ask before, because I think this question is more practical. Let’s take an example of the above save method, extract an interface, and write all the constraint comments:

public interface PersonInterface {
    void save(@NotNull @Valid Person person) throws NoSuchMethodException;
}

Subclass implementation, do not write a comment:

public class PersonService implements PersonInterface { @Override public void save(Person person) throws NoSuchMethodException { ... // method body code is the same as above, omitted}}

The test program is also the same as above, and is:

@Test public void test3() throws NoSuchMethodException {// save. Arg0 cannot be null: null // new PersonService().save(null); new PersonService().save(new Person()); }

Run the program, console output:

Save. Arg0. The name cannot be null: null. Save arg0. Age is not null: null Java. Lang. IllegalArgumentException: parameter error...

As expected, without any problems. That’s not all. There are many ways to combine them. For example, constraint annotations are all written on the implementation class. Realization of analog interface less; More than interface……

Due to space constraints, I won’t post the process of the experiment in the article, and I’ll just throw out the conclusion:

  • If the method is an implementation of an interface method, then there are two types of cases (common logic for both cases: constraint rules are based on the interface, some are valid, and none are valid) :

    • Keep the same constraints as the interface methods (limit case: if the interface has no constraint comments, you can’t either)
    • The implementation class does not write any constraints, and the result is that there are constraints in the interface, and there are no constraints
  • If the method is not an implementation of an interface method, then it’s simple: just do what you want

It is worth noting that there is another issue with the integration with Spring: should the @Validated annotations be on the interface (methods) or on the implementation classes (methods)? You might as well think about it yourself, the answer is bound to be later to share.

✍ summary

This article covers another classic use case of Bean Validation: Validate method parameters and return values. This is followed by an AOP integration with Spring that will release even more power.

In addition, through this article you should be able to feel the benefits of contract programming again, in a word: can be solved by contract, do not hard code, life is short, less code more fun.

Finally, a quick question: do you think more code is safer, or less code is more robust? Can code that has been validated 100 times not have to be repeatedly validated each time?

Recommend reading:
  • 1. This first post will improve your understanding of Bean Validation data Validation

♥ ♥ Brother A

Author A brother (YourBatman)
Personal site www.yourbatman.cn
E-mail [email protected]
WeChat fsx641385712
Active platform
The public, Bat Utopia (ID: Bat-Utopia)
Knowledge of the planet The Utopia of BAT
Daily article recommendations Daily article recommendations