spring-boot-starter-validation reference document
-
- Introduce dependencies
- Common annotations
- Most annotations
-
- `NotNull`
- `NotEmpty`
- `AssertFalse`
- `AssertTrue`
- `DecimalMax`
- `DecimalMin`
- `Digits`
- `Email`
- `Future`
- `Max`
- `Min`
- `Negative`
- `NegativeOrZero`
- `NotBlank`
- `Null`
- `Past`
- `Pattern`
- `Positive`
- `PositiveOrZero`
- `Size`
- `ScriptAssert`
- `Length`
- `Range`
- `URL`
- `CreditCardNumber`
- Example error message template:
- Example of method parameter verification:
- Nested verification example:
- Group verification example:
- Manually trigger verification
- `Spring boot` configuration verification fast failure mode
- `RequestBody` verification implementation principle
- Implementation principle of method parameter verification
- chat
Introduce dependencies
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Commonly used annotations
NotNull
NotBlank
Max
Min
Size
Most comments
NotNull
Annotated fields cannot be null
NotEmpty
Annotated fields cannot be null or empty.
Supports
Map
,CharSequence
,Collection
,Array
AssertFalse
Annotated fields must be
false
AssertTrue
Annotated fields must be
true
DecimalMax
The annotated element must be a number whose value must be less than or equal to the specified maximum value
Supports
BigDecimal
,BigInteger
,CharSequence
,
byte
,short
,int
,long
and their respective packaging types
DecimalMin
The annotated element must be a number whose value must be greater than or equal to the specified minimum value
Supports
BigDecimal
,BigInteger
,CharSequence
,
byte
,short
,int
,long
and their respective packaging types`
Digits
Annotated elements must be numbers within the acceptable range
Supports
BigDecimal
,BigInteger
,CharSequence
,
byte
,short
,int
,long
and their respective packaging types`
Email
The annotated string must be a well-formed email address
null elements are considered valid
Future
The annotated element must be a future instant, date, or time
Supported types are:
java.util.Date
java.util.Calendar
java.time.Instant
java.time.LocalDate
java.time.LocalDateTime
java.time.LocalTime
java.time.MonthDay
java.time.OffsetDateTime
java.time.OffsetTime
java.time.Year
java.time.YearMonth
java.time.ZonedDateTime
java.time.chrono.HijrahDate
java.time.chrono.JapaneseDate
java.time.chrono.MinguoDate
java.time.chrono.ThaiBuddhistDate
Max
The annotated element must be a number whose value must be less than or equal to the specified maximum value.
Supported types are:
BigDecimal
BigInteger
byte
,short
,int
,long
and their respective wrappers
Note thatdouble
andfloat
are not supported due to rounding errors (some providers may have some approximate support).
Thenull
element is considered valid.
Min
The annotated element must be a number whose value must be greater than or equal to the specified minimum value.
See item 10 for supported types and precautions:
Max
Negative
Annotated elements must be strictly negative (i.e., 0 is considered an invalid value)
Supported types are:
BigDecimal
BigInteger
byte
,short
,int
,long
,float
,double
and their respective wrappers
Thenull
element is considered valid.
NegativeOrZero
Annotated elements must be negative or 0
See item 12 for supported types:
Negative
NotBlank
Annotated elements cannot be null and must contain at least one non-whitespace character. Only supports
CharSequence
Null
Annotated elements must be null. accept any type
Past
The annotated element must be a past instant, date, or time
See item 9 for supported types:
Future
Pattern
The annotated
CharSequence
must match the specified regular expression. Regular expressions follow Java regular expression conventions
Thenull
element is considered valid.
Positive
Annotated elements must be strictly positive (i.e. 0 is considered an invalid value).
See item 12 for supported types:Negative
The
null
element is considered valid.
PositiveOrZero
Annotated elements must be positive or 0.
See item 12 for supported types:Negative
The
null
element is considered valid.
Size
The size of the annotated element must be within the specified bounds (inclusive).
Supported types are:
CharSequence
(evaluates the length of a character sequence)
Collection
(evaluate collection size)
Map
(evaluate Map size)
Array
(evaluate array length)
Thenull
element is considered valid.
ScriptAssert
A class-level constraint that evaluates script expressions against annotated elements. This constraint can be used to implement validation routines that depend on multiple properties of an annotated element.
Script expressions can be written in any scripting or expression language that is compatible withJSR 223
(“Java TM Platform Scripting”) engines found on the classpath. The following listing shows an example using theJavaScript
engine included withJDK
:Using a true expression language combined with shorter object aliases allows for very compact expressions:
Note:
javascript
isjava
Methods in the class can be called directly:
Public method:
_this.methodName(params)
Static method:
com.demo.utils.ValidUtils.LocalDateTimeValid(params)
The method return value needs to be of Boolean type, and the error message needs to specify the
message
attribute
@ScriptAssert(lang = "javascript", script = "_this.startDate.before(_this.endDate)") public class CalendarEvent {<!-- --> private Date startDate; private Date endDate; //... }
@ScriptAssert(lang = "jexl", script = "_.startDate > _.endDate", alias = "_") public class CalendarEvent {<!-- --> private Date startDate; private Date endDate; //... }
Length
Verify that the string is between
min
andmax
Range
Annotated elements must be in the appropriate scope. Applies to a numeric value or a string representation of a numeric value
URL
Verify that the annotated string is a URL
The parameters protocol, host and port match the corresponding parts of the URL. And you can use
regexp
and flags to specify additional regular expressions to further restrict the matching conditions.
Note: By default, the constraint validator for this constraint uses thejava.net.URL
constructor to validate strings. This means that a matching protocol handler needs to be available
CreditCardNumber
The annotated element must represent a valid credit card number. This is a
Luhn
algorithm implementation designed to check for user error, not credit card validity
Error message template example:
For example,
Max
,Min
,Range
@Max(value = 100, message = "The maximum does not exceed {value}") @Min(value = 0, message = "The minimum is not less than {value}") @Range(min = 0, max = 100, message = "The value is between {min}~{max}")
Example of method parameter verification:
@Validated public class Test {<!-- --> public void test(@Min(value = 0, message = "The minimum is not less than {value}") Integer age){<!-- -->} } public class Test {<!-- --> public void test(@RequestBody @Valid User user){<!-- -->} }
Nested validation example:
@Valid private User user;
Group verification example:
public interface CreateGroup {<!-- -->} public interface UpdateGroup {<!-- -->} public class TestDto {<!-- --> @Max(value = 100, groups = {<!-- -->CreateGroup.class}, message = "The maximum value does not exceed {value}") @Min(value = 0, groups = {<!-- -->UpdateGroup.class}, message = "The minimum is not less than {value}") private Integer age; } public class Test {<!-- --> public void test1(@RequestBody @Validated({<!-- -->CreateGroup.class}) TestDto dto){<!-- -->} public void test2(@RequestBody @Validated({<!-- -->UpdateGroup.class}) TestDto dto){<!-- -->} } // The annotations Valid and Validated can be mixed // Validated is a variant of Valid // Valid does not support group verification
Manually trigger verification
//Inject javax.validation.Validator @Autowired private Validator validator; public void valid(TestDto dto, Class<?> groupClass) {<!-- --> Set<ConstraintViolation<TestDto>> validateSet = validator.validate(dto, groupClass); if(validateSet.isEmpty()) {<!-- --> //Verification passed return; } // Verification failed for(ConstraintViolation<TestDto> validate: validateSet) {<!-- --> System.out.println(validate); } }
Spring boot
Configuration verification fast failure mode
//Fast failure means the verification ends when one of the conditions is not met. @Bean public Validator validator() {<!-- --> ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() // fail fast mode .failFast(true) .buildValidatorFactory(); return validatorFactory.getValidator(); }
RequestBody
Verification implementation principle
You can take a look at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
.
Among them, the method resolveArgument
@Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {<!-- --> // Encapsulate data into parameter object parameter = parameter.nestedIfOptional(); Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); if (binderFactory != null) {<!-- --> WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) {<!-- --> //Parameter verification validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() & amp; & amp; isBindExceptionRequired(binder, parameter)) {<!-- --> throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } if (mavContainer != null) {<!-- --> mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } return adaptArgumentIfNecessary(arg, parameter); } protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {<!-- --> Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation ann : annotations) {<!-- --> Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); // If there is a Validated annotation or an annotation starting with Valid, start verification if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {<!-- --> Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann)); Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {<!-- -->hints}); binder.validate(validationHints); break; } } } // WebDataBinder.validate implementation public void validate(Object target, Errors errors, Object... validationHints) {<!-- --> if (this.targetValidator != null) {<!-- --> processConstraintViolations( // Call Hibernate Validator to perform real verification this.targetValidator.validate(target, asValidationGroups(validationHints)), errors); } }
Implementation principle of method parameter verification
The implementation principle is actually AOP
. This method can actually be used with any spring bean
You can take a look at org.springframework.validation.beanvalidation.MethodValidationPostProcessor
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean {<!-- --> private Class<? extends Annotation> validatedAnnotationType = Validated.class; @Override public void afterPropertiesSet() {<!-- --> // Create aspects for all beans annotated with `@Validated` Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true); //Create Advisor for enhancement this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator)); } /** * Create AOP advice for method validation purposes, to be applied * with a pointcut for the specified 'validated' annotation. * @param validator the JSR-303 Validator to delegate to * @return the interceptor to use (typically, but not necessarily, * a {@link MethodValidationInterceptor} or subclass thereof) * @since 4.2 */ protected Advice createMethodValidationAdvice(@Nullable Validator validator) {<!-- --> return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor()); } }
public class MethodValidationInterceptor implements MethodInterceptor {<!-- --> @Override @Nullable public Object invoke(MethodInvocation invocation) throws Throwable {<!-- --> // Avoid Validator invocation on FactoryBean.getObjectType/isSingleton if (isFactoryBeanMetadataMethod(invocation.getMethod())) {<!-- --> return invocation.proceed(); } // group Class<?>[] groups = determineValidationGroups(invocation); // Standard Bean Validation 1.1 API ExecutableValidator execVal = this.validator.forExecutables(); Method methodToValidate = invocation.getMethod(); Set<ConstraintViolation<Object>> result; Object target = invocation.getThis(); Assert.state(target != null, "Target must not be null"); try {<!-- --> // Method input parameter verification result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups); } catch (IllegalArgumentException ex) {<!-- --> // Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011 // Let's try to find the bridged method on the implementation class... methodToValidate = BridgeMethodResolver.findBridgedMethod( ClassUtils.getMostSpecificMethod(invocation.getMethod(), target.getClass())); result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups); } // Throws an exception if the verification fails if (!result.isEmpty()) {<!-- --> throw new ConstraintViolationException(result); } Object returnValue = invocation.proceed(); // Verification of return value result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups); // Throws an exception if the verification fails if (!result.isEmpty()) {<!-- --> throw new ConstraintViolationException(result); } return returnValue; } private boolean isFactoryBeanMetadataMethod(Method method) {<!-- --> Class<?> clazz = method.getDeclaringClass(); // Call from interface-based proxy handle, allowing for an efficient check? if (clazz.isInterface()) {<!-- --> return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) & amp; & amp; !method.getName().equals("getObject")); } // Call from CGLIB proxy handle, potentially implementing a FactoryBean method? Class<?> factoryBeanType = null; if (SmartFactoryBean.class.isAssignableFrom(clazz)) {<!-- --> factoryBeanType = SmartFactoryBean.class; } else if (FactoryBean.class.isAssignableFrom(clazz)) {<!-- --> factoryBeanType = FactoryBean.class; } return (factoryBeanType != null & amp; & amp; !method.getName().equals("getObject") & amp; & amp; ClassUtils.hasMethod(factoryBeanType, method)); } /** * Determine the validation groups to validate against for the given method invocation. * <p>Default are the validation groups as specified in the {@link Validated} annotation * on the containing target class of the method. * @param invocation the current MethodInvocation * @return the applicable validation groups as a Class array */ protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {<!-- --> Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class); if (validatedAnn == null) {<!-- --> Object target = invocation.getThis(); Assert.state(target != null, "Target must not be null"); validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class); } return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]); } }
Whether it is requestBody
parameter verification or method parameter verification, Hibernate Validator
is ultimately called.
Chat
The above are all used to verify input parameters in the form of annotations. We can also write our own verification methods to achieve the same goal.
For some business logic, the verification introduced in this article may not be applicable.
My personal suggestion is to use annotations to verify empty parameters, and for the rest of the verification, we can write our own verification methods and call them before the business method starts.