简单实现spring web工程下的请求参数校验

前言

数据的校验是web后端(服务端)一个不可或缺的功能,前端的js校验可以涵盖大部分的校验规则,如生日格式,邮箱格式校验等。但是为了避免用户绕过前端的js(浏览器),使用http工具直接向后端请求一些违法数据,web后端的数据校验也是必要的,可以防止脏数据落到数据库中。

概念

Bean Validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08),已经经历了三个版本。规范注解如:@Null@NotNull@Patternjavax.validation.constraints包下,只提供规范不提供实现。

Hibernate Validator则是Bean Validation的参考实现,它提供了Bean Validation规范中所有内置constraint的实现,除此之外还有一些附加的constraint,如@Email,@Length,@Range等,位于org.hibernate.validator.constraints包下。

Spring validationhibernate-validation进行了二次封装,显示校验validated bean时,你可以使用Spring validation或者hibernate validation,而spring validation另一个特性,便是在springmvc模块中添加了自动检验,并将校验信息封装进了特定的类中,位于org.springframework.validation包下。

检验类型

注解说明
@Null验证对象是否为空
@NotNull验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格
@NotEmpty检查约束元素是否为NULL或者是EMPTY
@AssertTrue验证 Boolean 对象是否为 true
@AssertFalse验证 Boolean 对象是否为 false
@Size(min=, max=)验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=)验证字符串长度是否在给定的范围之内
@Past验证 Date 和 Calendar 对象是否在当前时间之前
@Future验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern验证 String 对象是否符合正则表达式的规则
@Min验证 Number 和 String 对象是否大等于指定的值
@Max验证 Number 和 String 对象是否小等于指定的值
@DecimalMax被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=)验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度
@Range(min=, max=)验证值是不是在该范围内
@Valid递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email验证是否是邮件地址,如果为null,不进行验证,算通过验证

检验实现

创建工程
  1. 选中idea菜单File,从弹出的子菜单中选择New,再从子菜单中选择并点击Project...,如下图
    在这里插入图片描述
  2. 经过第一步的操作,会弹出如下图的界面,选择Spring Initializer,默认Project SDKInitializr Service URL的值,点击next按钮。
    在这里插入图片描述
  3. 经过第二步的操作,会弹出如下界面,设置GroupArtifact,点击Next按钮
    在这里插入图片描述
  4. 经过第三步,会弹出如下界面,选择Developer Tools,选中Lombok,选择Web,选中Srping Web Starter,点击Next按钮 在这里插入图片描述
  5. 经过第四步操作,会弹出如下界面,设置Project NameProject location,点击finish按钮 ,到此我们的项目创建完毕。接下来开始写代码在这里插入图片描述
引入swagger
  1. 在pom.xml文件中加入如下依赖
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.6.1</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.6.1</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-bean-validators</artifactId>
			<version>2.6.1</version>
		</dependency>

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-bean-validators</artifactId>
			<version>2.6.1</version>
		</dependency>
  1. com.study.web包下新建config包,在com.study.web.config包下,新建SwaggerConfig类,代码如下:
package com.study.web.config;

import static springfox.documentation.builders.PathSelectors.ant;
import com.google.common.base.Predicates;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * swagger工具配置
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurerAdapter {

    @Value("${swaggerUrl}")
    private String swaggerUrl;

    /**
     * SpringBoot默认已经将classpath:/META-INF/resources/和classpath:/META-INF/resources/webjars/映射
     * 所以该方法不需要重写,如果在SpringMVC中,可能需要重写定义(我没有尝试) 重写该方法需要 extends WebMvcConfigurerAdapter
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    @Bean
    public Docket restApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .host(swaggerUrl)
                //.apiInfo(apiInfo())
                .select()
                .paths(Predicates.and(ant("/**"), Predicates.not(ant("/error"))))
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Swagger Petstore")
                .description("Petstore API Description")
                .contact(new Contact("leongfeng", "http:/test-url.com", "leongfeng@163.com"))
                .license("Apache 2.0")
                .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
                .version("1.0.0")
                .build();
    }
}
新建UserController
  1. 在com.study.web.controller包下,新增UserController类,代码如下:
package com.study.web.controller;

import com.study.web.domain.vo.SaveUserVo;
import com.study.web.domain.vo.UpdateUserVo;
import com.study.web.domain.vo.UserVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author ZOUZHIHUI
 * @Date 2019-06-22
 */
@RestController("user")
@Api(value = "UserController", description = "用户接口")
public class UserController {

    @RequestMapping(value = "save", method = RequestMethod.POST)
    @ApiOperation(value = "save", nickname = "保存用户信息")
    public String save(@Validated @RequestBody SaveUserVo userVo) {
        return "success";
    }
}
  1. SaveUserVo类如下:
package com.study.web.domain.vo;

import javax.validation.constraints.NotEmpty;
import lombok.Data;

/**
 * @author ZOUZHIHUI
 * @Date 2019-06-22
 */
@Data
public class SaveUserVo {

    @NotEmpty(message = "用户名不能为空!")
    private String username;

    @NotEmpty(message = "密码不能为空!")
    private String password;

}

运行系统
  1. 配置application.properties文件
server.port=8989
server.servlet.context-path=/study-web

swaggerUrl: localhost:8989
  1. 找到StudyWebApplication类,右键debug运行,服务启动完之后,在浏览器上输入如下地址:http://localhost:8989/study-web/swagger-ui.html,能正常访问表示服务没问题。
测试
  1. 访问该地址http://localhost:8989/study-web/swagger-ui.html,找到user-controllersave接口,按下图,配置参数,点击Try it out按钮
    在这里插入图片描述
  2. 会得到如下的响应结果,表明验证功能生效
{
  "timestamp": "2019-06-26T02:45:25.879+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "NotEmpty.saveUserVo.username",
        "NotEmpty.username",
        "NotEmpty.java.lang.String",
        "NotEmpty"
      ],
      "arguments": [
        {
          "codes": [
            "saveUserVo.username",
            "username"
          ],
          "arguments": null,
          "defaultMessage": "username",
          "code": "username"
        }
      ],
      "defaultMessage": "用户名不能为空!",
      "objectName": "saveUserVo",
      "field": "username",
      "rejectedValue": "",
      "bindingFailure": false,
      "code": "NotEmpty"
    },
    {
      "codes": [
        "NotEmpty.saveUserVo.password",
        "NotEmpty.password",
        "NotEmpty.java.lang.String",
        "NotEmpty"
      ],
      "arguments": [
        {
          "codes": [
            "saveUserVo.password",
            "password"
          ],
          "arguments": null,
          "defaultMessage": "password",
          "code": "password"
        }
      ],
      "defaultMessage": "密码不能为空!",
      "objectName": "saveUserVo",
      "field": "password",
      "rejectedValue": "",
      "bindingFailure": false,
      "code": "NotEmpty"
    }
  ],
  "message": "Validation failed for object='saveUserVo'. Error count: 2",
  "path": "/study-web/save"
}

spring校验功能源码解析

  1. 参数验证调用流程,如下图,主要介绍几个重要流程
    在这里插入图片描述
  2. HandlerMethodArgumentResolverComposite类,该类实现了HandlerMethodArgumentResolver接口,该类存放了spring提供的所有参数解析器类。会根据请求参数来找到唯一的参数解析器类,本例子中是RequestResponseBodyMethodProcessor。也就是说不同从参数会有不同的解析器类。列表如下:
类名描述
RequestParamMethodArgumentResolver针对被 @RequestParam 注解修饰, 但类型不是 Map, 或类型是 Map, 并且 @RequestParam 中指定 name, 一般通过 MultipartHttpServletRequest
RequestParamMapMethodArgumentResolver针对被 @RequestParam注解修饰, 且参数类型是 Map 的, 且 @RequestParam 中没有指定 name, 从 HttpServletRequest 里面获取所有请求参数, 最后封装成 LinkedHashMap
PathVariableMethodArgumentResolver解决被注解 @PathVariable 注释的参数 <- 这个注解对应的是 uri 中的数据, 在解析 URI 中已经进行解析好了 <- 在 RequestMappingInfoHandlerMapping.handleMatch -> getPathMatcher().extractUriTemplateVariables
PathVariableMapMethodArgumentResolver针对被 @PathVariable 注解修饰, 并且类型是 Map的, 且 @PathVariable.value == null, 从 HttpServletRequest 中所有的 URI 模版变量 (PS: URI 模版变量的获取是通过 RequestMappingInfoHandlerMapping.handleMatch 获取)
MatrixVariableMethodArgumentResolver针对被 @MatrixVariable 注解修饰的参数起作用, 从 HttpServletRequest 中获取去除 ; 的 URI Template Variables 获取数据
MatrixVariableMapMethodArgumentResolver针对被 @MatrixVariable 注解修饰, 并且类型是 Map的, 且 MatrixVariable.name == null, 从 HttpServletRequest 中获取 URI 模版变量 <-- 并且是去除 ;
ServletModelAttributeMethodProcessor针对被@ModeAttribute注解修饰的
RequestResponseBodyMethodProcessor解决被 @RequestBody 注释的方法参数 <- 其间是用 HttpMessageConverter 进行参数的转换
RequestPartMethodArgumentResolver参数被 @RequestPart 修饰, 参数是 MultipartFile
RequestHeaderMethodArgumentResolver针对 参数被 RequestHeader 注解, 并且 参数不是 Map 类型, 数据通过 HttpServletRequest.getHeaderValues(name) 获取
RequestHeaderMapMethodArgumentResolver解决被 @RequestHeader 注解修饰, 并且类型是 Map 的参数, HandlerMethodArgumentResolver会将 Http header 中的所有 name <–> value 都放入其中
ServletCookieValueMethodArgumentResolver针对被 @CookieValue 修饰, 通过 HttpServletRequest.getCookies 获取对应数据
ExpressionValueMethodArgumentResolver针对被 @Value 修饰, 返回 ExpressionValueNamedValueInfo
SessionAttributeMethodArgumentResolver针对 被 @SessionAttribute 修饰的参数起作用, 参数的获取一般通过 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_SESSION)
RequestAttributeMethodArgumentResolver针对 被 @RequestAttribute 修饰的参数起作用, 参数的获取一般通过 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_REQUEST)
ServletRequestMethodArgumentResolver支持 WebRequest, ServletRequest, MultipartRequest, HttpSession, Principal, InputStream, Reader, HttpMethod, Locale, TimeZone, 数据通过 HttpServletRequest 获取
ServletResponseMethodArgumentResolver支持 ServletResponse, OutputStream, Writer 类型, 数据的获取通过 HttpServletResponse
HttpEntiyMethodProcessor针对 HttpEntity,RequestEntity 类型的参数进行参数解决, 将 HttpServletRequest 里面的数据转换成 HttpEntity
RedirectAttributesMethodArgumentResolver针对 RedirectAttributes及其子类的参数 的参数解决器, 主要还是基于 NativeWebRequest && DataBinder (通过 dataBinder 构建 RedirectAttributesModelMap)
ModelMethodProcessor针对 Model 及其子类的参数, 数据的获取一般通过 ModelAndViewContainer.getModel()
MapMethodProcessor针对参数是 Map, 数据直接从 ModelAndViewContainer 获取 Model
ErrorsMethodArgumentResolver参数是Errors
SessionStatusMethodArgumentResolver支持参数类型是 SessionStatus, 直接通过 ModelAndViewContainer 获取 SessionStatus
UriComponentsBuilderMethodArgumentResolver支持参数类型是 UriComponentsBuilder, 直接通过 ServletUriComponentsBuilder.fromServletMapping(request) 构建对象

参考:
spring mvc 数据绑定
SpringMVC 4.3 源码分析之 HandlerMethodArgumentResolver

  1. RequestResponseBodyMethodProcessor来解析具体参数,
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
		// 从request中读取参数,并转成对应的参数对象
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			// 创建WebDataBinder,此处默认创建ExtendedServletRequestDataBinder
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
				// 执行参数验证,如果验证失败,会把错误信息封装到binder中
				validateIfApplicable(binder, parameter);
				// 判断binder中是否有验证失败的信息,如果有,则抛出异常
				if (binder.getBindingResult().hasErrors() && 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注解
			Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
			// 判断是否有Validated注解或者是以Valid开头的注解,匹配@Valid注解
			if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
			   // 获取注解中的值,主要是用于group分组检验
				Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
				Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
				// 调用ExtendedServletRequestDataBinder类的validate()方法
				binder.validate(validationHints);
				break;
			}
		}
	}
  1. ExtendedServletRequestDataBinder,该方法遍历所有可用的验证器,并调用每个验证器的validate方法
public void validate(Object... validationHints) {
		Object target = getTarget();
		Assert.state(target != null, "No target to validate");
		BindingResult bindingResult = getBindingResult();
		// Call each validator with the same binding result
		for (Validator validator : getValidators()) {
			// 判断是否有分组验证功能,如果有则走下面的验证
			if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
				((SmartValidator) validator).validate(target, bindingResult, validationHints);
			}
			else if (validator != null) { // 没有分组验证,则走下面的验证
				validator.validate(target, bindingResult);
			}
		}
	}
  1. ValidatorAdapter
public void validate(Object target, Errors errors) {
		this.target.validate(target, errors);
	}
  1. SpringValidatorAdapter类中会调用hibernate-validate中的ValidatorImpl来实现具体的验证,并解析返回验证信息,放入Errors中,关于ValidatorImpl的具体验证规则,后续再分析
public void validate(Object target, Errors errors) {
		if (this.targetValidator != null) {
			processConstraintViolations(this.targetValidator.validate(target), errors);
		}
	}
protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
		for (ConstraintViolation<Object> violation : violations) {
			String field = determineField(violation);
			FieldError fieldError = errors.getFieldError(field);
			if (fieldError == null || !fieldError.isBindingFailure()) {
				try {
					ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
					String errorCode = determineErrorCode(cd);
					Object[] errorArgs = getArgumentsForConstraint(errors.getObjectName(), field, cd);
					if (errors instanceof BindingResult) {
						// Can do custom FieldError registration with invalid value from ConstraintViolation,
						// as necessary for Hibernate Validator compatibility (non-indexed set path in field)
						BindingResult bindingResult = (BindingResult) errors;
						String nestedField = bindingResult.getNestedPath() + field;
						if (nestedField.isEmpty()) {
							String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
							ObjectError error = new ObjectError(
									errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()) {
								@Override
								public boolean shouldRenderDefaultMessage() {
									return requiresMessageFormat(violation);
								}
							};
							error.wrap(violation);
							bindingResult.addError(error);
						}
						else {
							Object rejectedValue = getRejectedValue(field, violation, bindingResult);
							String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
							FieldError error = new FieldError(errors.getObjectName(), nestedField,
									rejectedValue, false, errorCodes, errorArgs, violation.getMessage()) {
								@Override
								public boolean shouldRenderDefaultMessage() {
									return requiresMessageFormat(violation);
								}
							};
							error.wrap(violation);
							bindingResult.addError(error);
						}
					}
					else {
						// got no BindingResult - can only do standard rejectValue call
						// with automatic extraction of the current field value
						errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
					}
				}
				catch (NotReadablePropertyException ex) {
					throw new IllegalStateException("JSR-303 validated property '" + field +
							"' does not have a corresponding accessor for Spring data binding - " +
							"check your DataBinder's configuration (bean property versus direct field access)", ex);
				}
			}
		}
	}

分组检验

新建注解类
  1. com.study.web.group包下,新增SaveUpdate两个接口类,主要对应新增和修改两个接口
package com.study.web.group;

/**
 * @author ZOUZHIHUI
 * @Date 2019-06-27
 */
public interface Save {

}
package com.study.web.group;

/**
 * @author ZOUZHIHUI
 * @Date 2019-06-27
 */
public interface Update {

}
UserController新增修改和新增接口
  1. 在新增方法中使用@Validate注解,value{Save.class}。在修改方法中使用@Validate注解,value{Update.class}
@RequestMapping(value = "save2", method = RequestMethod.POST)
    @ApiOperation(value = "save2", nickname = "保存用户信息2")
    public String save2(@Validated({Save.class}) @RequestBody UserVo userVo) {
        return "success";
    }

    @RequestMapping(value = "update2", method = RequestMethod.POST)
    @ApiOperation(value = "update2", nickname = "更新用户信息2")
    public String update2(@Validated({Update.class}) @RequestBody UserVo userVo) {
        return "success";
    }
  1. UserVo代码如下, 当新增时,usernamepassword参数不能为空;当修改时,idusername不能为空。
package com.study.web.domain.vo;

import com.study.web.group.Save;
import com.study.web.group.Update;
import javax.validation.constraints.NotEmpty;
import lombok.Data;

/**
 * @author ZOUZHIHUI
 * @Date 2019-06-22
 */
@Data
public class UserVo {

    @NotNull(groups = {Update.class})
    private Integer id;

    @NotEmpty(groups = {Save.class, Update.class})
    private String username;

    @NotEmpty(groups = {Save.class})
    private String password;

}

运行&测试
  1. 访问该地址http://localhost:8989/study-web/swagger-ui.html,找到user-controllersave2接口,按下图,配置参数,点击Try it out按钮测试新增方法,打开swagger页面,找到
  2. 会得到如下的响应结果,说明分组验证功能成功,新增是usernamepassword不能为空
{
  "timestamp": "2019-06-27T02:32:22.815+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "NotEmpty.userVo.password",
        "NotEmpty.password",
        "NotEmpty.java.lang.String",
        "NotEmpty"
      ],
      "arguments": [
        {
          "codes": [
            "userVo.password",
            "password"
          ],
          "arguments": null,
          "defaultMessage": "password",
          "code": "password"
        }
      ],
      "defaultMessage": "不能为空",
      "objectName": "userVo",
      "field": "password",
      "rejectedValue": "",
      "bindingFailure": false,
      "code": "NotEmpty"
    },
    {
      "codes": [
        "NotEmpty.userVo.username",
        "NotEmpty.username",
        "NotEmpty.java.lang.String",
        "NotEmpty"
      ],
      "arguments": [
        {
          "codes": [
            "userVo.username",
            "username"
          ],
          "arguments": null,
          "defaultMessage": "username",
          "code": "username"
        }
      ],
      "defaultMessage": "不能为空",
      "objectName": "userVo",
      "field": "username",
      "rejectedValue": "",
      "bindingFailure": false,
      "code": "NotEmpty"
    }
  ],
  "message": "Validation failed for object='userVo'. Error count: 2",
  "path": "/study-web/save2"
}
  1. 访问该地址http://localhost:8989/study-web/swagger-ui.html,找到user-controllerupdate2接口,按下图,配置参数,点击Try it out按钮
    在这里插入图片描述
  2. 会得到如下的响应结果,说明分组验证功能成功,修改是是idusername不能为空
{
  "timestamp": "2019-06-27T02:40:20.497+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "NotNull.userVo.id",
        "NotNull.id",
        "NotNull.java.lang.Integer",
        "NotNull"
      ],
      "arguments": [
        {
          "codes": [
            "userVo.id",
            "id"
          ],
          "arguments": null,
          "defaultMessage": "id",
          "code": "id"
        }
      ],
      "defaultMessage": "不能为null",
      "objectName": "userVo",
      "field": "id",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotNull"
    },
    {
      "codes": [
        "NotEmpty.userVo.username",
        "NotEmpty.username",
        "NotEmpty.java.lang.String",
        "NotEmpty"
      ],
      "arguments": [
        {
          "codes": [
            "userVo.username",
            "username"
          ],
          "arguments": null,
          "defaultMessage": "username",
          "code": "username"
        }
      ],
      "defaultMessage": "不能为空",
      "objectName": "userVo",
      "field": "username",
      "rejectedValue": "",
      "bindingFailure": false,
      "code": "NotEmpty"
    }
  ],
  "message": "Validation failed for object='userVo'. Error count: 2",
  "path": "/study-web/update2"
}

自定义检验

手动检验

关于自定义校验功能和在代码层面手动调用校验功能,请参考西面的连接:
使用spring validation完成数据后端校验

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值