A must read! Revealed N Practical Tips for SpringBoot Interface Parameter Verification

Original Springboot practical case collection Spring Family Bucket practical case source code 2023-11-08 08:35 Published in Xinjiang

Spring Family Bucket practical case source code

Detailed explanation of spring, springboot, springcloud case development

376 original content

No public

Environment: SpringBoot2.6.12

In actual development work, most interfaces need to verify the validity of parameters. The parameters may be simple basic data types or object types. Basically all interfaces that receive parameters need to verify these parameters. Verified, how did you verify these parameters? Next, I will show you what verification methods I have used in actual projects! . This case will introduce the following 7 aspects in detail.

  1. Simple parameter verification

  2. Parameter verification grouping

  3. Single parameter verification

  4. Nested parameter validation

  5. Customized tool parameter verification

  6. International Support

  7. Uniform processing of AOP verification parameters

Before formally introducing the main content, we still need to understand and learn some specifications JSR303.

What are JSRs?

JSR is the abbreviation of Java Specification Requests, which means Java specification proposal. It is a formal request to JCP (Java Community Process) to add a standardized technical specification. Anyone can submit JSRs to add new APIs and services to the Java platform. JSR has become an important standard in the Java world. JSR-303 is a sub-specification in JAVA EE 6 called Bean Validation. Hibernate Validator is the reference implementation of Bean Validation. Hibernate Validator provides implementation of all built-in constraints in the JSR 303 specification, in addition to some additional constraint. Relevant notes are as follows:

Picture

The SpringValidation verification framework provides @Validated (Spring’s JSR-303 specification, a variant of the standard JSR-303) for parameter verification mechanism in Spring, and javax provides @Valid (the standard JSR-303 specification), combined with The BindingResult object can directly obtain error information. In this case, the two are equivalent, but there are differences, which will be explained in the following cases.

1. Configuration dependencies

<dependencies></code><code><dependency></code><code><groupId>org.springframework.boot</groupId></code><code><artifactId>spring-boot-starter -web</artifactId></code><code></dependency></code><code><dependency></code><code><groupId>org.aspectj</groupId></code><code><artifactId>aspectjrt</artifactId></code><code></dependency></code><code><dependency></code><code><groupId>org.aspectj</groupId></code><code><artifactId>aspectjweaver</artifactId></code><code><scope>runtime</scope></code><code></dependency></code><code></dependencies>< /pre>
<p>The org.aspectj dependency is that in the end we need to use AOP technology to implement unified parameter verification.</p>
<p><strong>2. Parameter verification</strong></p>
<ul><li> <p>Simple parameter verification</p></li></ul>
<ul><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li></ul>
<pre>public class Users {<!-- --></code><code> @NotEmpty(message = "Name is required")</code><code> private String name ;</code><code> @Min(value = 10, message = "Age cannot be less than 10")</code><code> private Integer age ;</code><code> @Length(min = 6, max = 18, message = "Email is between 6 and 18")</code><code> private String email ;</code><code> @NotEmpty(message = "Phone number is required")</code><code> private String phone ;</code><code> // The two interfaces here will be used in the following cases</code><code> public static interface G1 {}</code><code> public static interface G2 {}</code><code>}

Different annotations are applied here to restrict the fields that need to be verified. The next step is to add the corresponding annotations to the Controller interface:

@ResponseBody</code><code>public class UsersController extends BaseController {<!-- --></code><code> @RequestMapping(value = "/valid/save1", method = RequestMethod. POST)</code><code> public Object save1(@RequestBody @Validated Users user, BindingResult result) {<!-- --></code><code> Optional<List<String>> op = valid(result ) ;</code><code> if (op.isPresent()) {<!-- --></code><code> return op.get() ;</code><code> }</code><code> return "success" ;</code><code> }</code><code>}</code><code>public class BaseController {<!-- --></code><code> protected Optional<List<String>> valid(BindingResult result) {<!-- --></code><code> if (result.hasErrors()) {<!-- --></code> <code> return Optional.of(result.getAllErrors().stream().map(err -> err.getDefaultMessage()).collect(Collectors.toList())) ;</code><code> }</code><code> return Optional.empty() ;</code><code> }</code><code>}

The Users object that receives the parameters must be annotated with @Validated in front, and error information can be collected through BindingResult (you can determine whether there is error information); the test is as follows:

Picture

Correct situation

Picture

  • Parameter verification group

Sometimes our object may be applied to different scenarios. What should we do if different verification rules appear? At this time, we can apply the grouping function. Different application scenarios can specify different groups and start playing. Note: JSR303 does not have grouping functionality.

public class Users {<!-- --></code><code> @NotEmpty(message = "Name cannot be empty", groups = G1.class)</code><code> private String name ;</code><code> @Min(value = 10, message = "Age cannot be less than 10", groups = G1.class)</code><code> @Min(value = 20, message = \ "Age cannot be less than 20", groups = G2.class)</code><code> private Integer age ;</code><code> @Length(min = 6, max = 18, message = "Email is between Between 6 and 18", groups = {G1.class, G2.class})</code><code> private String email;</code><code> @NotEmpty(message = "Phone number is required" )</code><code> private String phone ;</code><code> public static interface G1 {}</code><code> public static interface G2 {}</code><code>}

The groups attribute is added to different fields here to indicate which group they belong to. Note that in this entity class we have defined two more classes G1, G2 is used for grouping (specifically specify which group). Interface processing:

@RequestMapping(value = "/valid/save1", method = RequestMethod.POST)</code><code>public Object save1(@RequestBody @Validated(Users.G1.class) Users user, BindingResult result ) {<!-- --></code><code> Optional<List<String>> op = valid(result) ;</code><code> if (op.isPresent()) {<!-- --></code><code> return op.get() ;</code><code> }</code><code> return "success" ;</code><code>}</code><code>@RequestMapping(value = "/valid/save2", method = RequestMethod.POST)</code><code>public Object save2(@RequestBody @Validated(Users.G2.class) Users user, BindingResult result) {<!-- --></code><code> Optional<List<String>> op = valid(result) ;</code><code> if (op.isPresent()) {<!- - --></code><code> return op.get() ;</code><code> }</code><code> return "success" ;</code><code>}</ pre>
<p>In these two interfaces, @Validated(Users.G2.class) respectively specifies its own group. Next, test to see the effect.</p>
<p>Group G1 test:</p>
<p></p>
<p><img alt="Picture" height="651" src="//i2.wp.com/img-blog.csdnimg.cn/img_convert/22e44dfcbff8cdbf1f44464269dd306b.png" width="933" ></p>
<p>Judging from the information returned here, although our phone wrote @NotEmpty, it did not work because we did not specify its group, and we specified that the interface uses G1 grouping.</p>
<p>Group G2 test:</p>
<p></p>
<p><img alt="Picture" height="502" src="//i2.wp.com/img-blog.csdnimg.cn/img_convert/22c57f59f155f93efaf682ae87c4f8fd.png" width="940" ></p>
<p>It is found in this interface that the name verification is G1, so verification will not be performed here, and the age judgment cannot be less than 20.</p>
<ul><li> <p>Single parameter verification</p></li></ul>
<p>The verification of a single parameter does not require an entity object. Generally, JSR303 related annotations can be directly applied to the interface parameters. You also need to add the @Validated annotation to the Controller class.</p>
<ul><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li></ul>
<pre>@Validated</code><code>public class UsersController extends BaseController {<!-- --></code><code> @PackMapping("/valid/find")</code><code> public Object find(@NotEmpty(message = "Parameter Id cannot be empty") String id) {<!-- --></code><code> return "Parameter found【" + id + "】" ;</code><code> }</code><code>}

In this interface, annotations are directly applied to parameters. test:

Picture

At the same time, the console will output the following exception:

Picture

You also find that this kind of exception information prompt is very unfriendly. Next, let’s do a simple local exception handling.

We only need to add the following method to the Controller:

@ExceptionHandler(ConstraintViolationException.class)</code><code>@ResponseBody</code><code>public Object ConstraintViolationExceptionHandler(ConstraintViolationException e) {<!-- --></code><code> String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());</code><code> return message ;</code><code>}

In this Controller we added an exception handling handler (simply output the error message).

Picture

  • Nested parameter verification

In actual work, parameter objects are often much more complicated than this. There may be other objects nested in the Users object, and these other objects may also require parameter verification. Next, let’s take a look at how this nested parameter is verified.

public class Users {<!-- --></code><code> @NotEmpty(message = "Name cannot be empty", groups = G1.class)</code><code> private String name ;</code><code> @Min(value = 10, message = "Age cannot be less than 10", groups = G1.class)</code><code> @Min(value = 20, message = \ "Age cannot be less than 20", groups = G2.class)</code><code> private Integer age ;</code><code> @Length(min = 6, max = 18, message = "Email is between Between 6 and 18", groups = {G1.class, G2.class})</code><code> private String email;</code><code> @NotEmpty(message = "Phone number is required" )</code><code> private String phone ;</code><code> @Valid</code><code> private Address address;</code><code>}

Note: The verification of the nested object Address requires adding the @Valid annotation above.

public class Address {<!-- --></code><code> @NotEmpty(message = "Address information is required")</code><code> private String addr ;</code> <code>}

Test interface:

@RequestMapping(value = "/valid/save3", method = RequestMethod.POST)</code><code>public Object save3(@RequestBody @Validated Users user, BindingResult result) {<!-- - -></code><code> Optional<List<String>> op = valid(result) ;</code><code> if (op.isPresent()) {<!-- --></code> <code> return op.get() ;</code><code> }</code><code> return "success" ;</code><code>}

There is nothing special about the interface that is exactly the same as before. Note: The verification here does not set the grouping, so the verification is done without setting the grouping field.

test:

Picture

Here we find that our address information has not been verified at all. Next let’s change the parameters

Picture

After setting the address field in the parameters, the parameters are verified. Next, modify the Users entity. Let the Address default new and then test it.

@Valid</code><code>private Address address = new Address();

Picture

We found that even if our input parameter does not have an address field, it can still be verified. You need to pay attention here.

  • Custom parameter verification

Please check out [Tips] Essential tools for API interface parameter verification to make your code more efficient!

  • International Support

public class Users {<!-- --></code><code> @NotEmpty(message = "{name.notempty}", groups = G1.class)</code><code> private String name ;</code><code> @Min(value = 10, message = "Age cannot be less than 10", groups = G1.class)</code><code> @Min(value = 20, message = "Age cannot be less than 20", groups = G2.class)</code><code> private Integer age ;</code><code> @Length(min = 6, max = 18, message = "Email address Between 6 and 18", groups = {G1.class,</code><code> G2.class})</code><code> private String email ;</code><code> @NotEmpty(message = "Phone required")</code><code> private String phone ;</code><code> @Valid</code><code> private Address address = new Address();</code><code>}

Note that we use an expression for the message attribute in the name field here, and name.notempty is the key we defined in the resource file. Next, create the following properties file under resources/:

Picture

The properties file must start with ValidationMessages. Default file and zh_CN content:

name.notempty=name is required

en_US content:

name.notempty=name is require

test:

Picture

In order to simulate the English environment, we need to set the request header Accept-Language: en-US

Picture

The message defined in en_US.properties is displayed and the internationalization is completed.

  • Unified processing of AOP verification parameters

Custom annotation tags require an interface for unified parameter verification processing.

@Target(ElementType.METHOD)</code><code>@Retention(RetentionPolicy.RUNTIME)</code><code>@Documented</code><code>public @interface EnableValidate {}

AOP aspect class

@Component</code><code>@Aspect</code><code>public class ValidateAspect {<!-- --></code><code> @Pointcut("@annotation(com.pack .params.valid.EnableValidate)")</code><code> public void valid() {}</code><code> @Before("valid()")</code><code> public void validateBefore(JoinPoint jp) {<!-- --></code><code> Object[] args = jp.getArgs() ;</code><code> for (Object arg : args) {<!- - --></code><code> if (arg instanceof BindingResult) {<!-- --></code><code> BindingResult result = (BindingResult) arg ;</code><code> if (result .hasErrors()) {<!-- --></code><code> String messages = result.getAllErrors().stream().map(err -> err.getDefaultMessage()).collect(Collectors.joining (",")) ;</code><code> throw new ParamsException(messages) ;</code><code> }</code><code> }</code><code> }</code><code> }</code><code>}

A pre-notification is defined to intercept the interface marked with @EnableValidate annotation. If there is an exception, collect the error information and throw the exception information. test:

@PackMapping(value = "/valid/save1", method = RequestMethod.POST)</code><code>@EnableValidate</code><code>public Object save1(@RequestBody @Validated(Users. G1.class) Users user, BindingResult result) {<!-- --></code><code> Optional<List<String>> op = valid(result) ;</code><code> if (op. isPresent()) {<!-- --></code><code> return op.get() ;</code><code> }</code><code> return "success" ;</code> code><code>}

Picture

At this point, we have implemented unified processing of parameters through AOP technology, but outputting error messages in this way is very unfriendly. Next, we will improve it and intercept processing through global exception notifications. The exception information here can be formatted through global exception handling.

Done! ! !