除过在客户端做JavaScript数据校验外,服务器端做数据校验是很有必要的。Spring提供数据校验,SpringBoot工程里使用没有多大变化。
数据校验分为两种:
[list][*]单字段校验(比如:非空、长度、大小等),Java的标准Bean Validation(内部实现是Hibernate Validator)
[*]关系多字段校验(比如:时间期间、密码的两次输入等),Spring 的 org.springframework.validation.Validator[/list]
[b](1)单字段校验[/b]
Form字段上添加注解
src/main/java/com/rensanning/springboot/web/form/ValidSampleForm.java
public class ValidSampleForm {
@NotBlank
@Size(max=5)
private String name;
}
Contrller注入参数前添加@Validated
src/main/java/com/rensanning/springboot/web/ValidSampleContrller.java
@Controller
public class ValidSampleContrller {
@RequestMapping(value="/validSample", method = RequestMethod.POST)
public String postValidSample(@ModelAttribute("form") @Validated ValidSampleForm form, BindingResult result, Model model) {
if (result.hasErrors()) {
for(FieldError err: result.getFieldErrors()) {
log.debug("error code = [" + err.getCode() + "]");
}
}
return "validSample";
}
}
页面中显示错误信息
src/main/resources/templates/validSample.html
一览显示错误信息
- th:class="${e.global}? globalerrMsg : fielderrMsg" th:text="${e.message}" />
[b](2)自定义错误信息[/b]
Spring默认错误信息
[quote]hibernate-validator-5.3.4.Final.jar\org\hibernate\validator\ValidationMessages.properties[/quote]
自定义错误信息
Java中自定义
@NotNull(message="不能为空!")
外部自定义
@NotNull(message="{sample.bean_validation.notNull}")
[quote]sample.bean_validation.notNull=不能为空![/quote]
SpringBoot自动读取classpath中的ValidationMessages.properties里的错误信息
src/main/resources/ValidationMessages.properties
[quote]javax.validation.constraints.Pattern.message=
javax.validation.constraints.Size.message=
javax.validation.constraints.Min.message=
org.hibernate.validator.constraints.NotBlank.message=[/quote]
统一到国际化信息文件messages.properties
@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(messageSource);
return localValidatorFactoryBean;
}
@Override
public org.springframework.validation.Validator getValidator() {
return validator();
}
[b](3)嵌套校验 @Valid[/b]
public class OrderForm {
@NotNull
@Valid
private AddressForm receiverAddress;
@NotNull
@Valid
private AddressForm senderAddress;
}
public class AddressForm {
@NotNull
@Size(min = 1, max = 100)
private String address;
}
集合Bean校验也和上边一致:
public class OrderForm {
@NotNull
@Size(min = 1, max = 3)
@Valid
private List addresses;
}
[b](4)组校验[/b]
定义Group
public class ValidSampleForm {
public static interface Group1 {};
public static interface Group2 {};
@NotBlank(groups=Group1.class)
@Size(max=5, groups=Group1.class)
private String name;
@Min(value=0, groups={Group1.class, Group2.class})
private Integer age;
@NotBlank(groups=Group2.class)
private Date birthday;
}
只校验Group1的字段
@RequestMapping(value="/validSample", method = RequestMethod.POST)
public String postValidSample(@ModelAttribute("form") @Validated(Group1.class) ValidSampleForm form, BindingResult result, Model model) {
if (result.hasErrors()) {
for(FieldError err: result.getFieldErrors()) {
log.debug("error code = [" + err.getCode() + "]");
}
}
return "validSample";
}
[b]顺序执行校验[/b]
假如以下定义,如果name为空,两个错误提示都会显示到页面上。
@NotEmpty(message = "请输入姓名!")
@Length(min = 1, max = 10, message = "1到10位字符")
private String name;
只有@NotEmpty不出错的情况下才执行@Length:
public interface First { }
public interface Second { }
@GroupSequence({First.class, Second.class})
public interface All {
}
@NotEmpty(message = "请输入姓名!", groups = First.class)
@Length(min = 1, max = 10, message = "1到10位字符", groups = Second.class)
private String name;
@RequestMapping(method = RequestMethod.POST)
public String indexPost(@ModelAttribute("form") @Validated(All.class) Person p, BindingResult r) {
return "index";
}
[b](5)多字段校验[/b]
实现Spring的Validator
@Component
public class ValidSampleValidator implements Validator {
public boolean supports(Class> c) {
return ValidSampleForm.class.isAssignableFrom(c);
}
@Override
public void validate(Object paramObject, Errors paramErrors) {
if (paramErrors.hasFieldErrors("from") || paramErrors.hasFieldErrors("to")) {
return;
}
ValidSampleForm form = (ValidSampleForm)paramObject;
Date from = form.getFrom();
Date to = form.getTo();
if (from != null && to != null && from.compareTo(to) > 0) {
paramErrors.rejectValue("from", "validator.Period");
}
}
}
通过@InitBinder添加自定义校验
@Controller
public class ValidSampleController {
@Autowired
private ValidSampleValidator validSampleValidator;
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.addValidators(validSampleValidator);
}
@RequestMapping(value="/validSample", method = RequestMethod.POST)
public String postValidSample(@ModelAttribute("form") ValidSampleForm form, BindingResult result, Model model) {
if (result.hasErrors()) {
for(FieldError err: result.getFieldErrors()) {
log.debug("error code = [" + err.getCode() + "]");
}
}
return "validSample";
}
}
src/main/resources/messages.properties
[quote]validator.Period=[/quote]
一个Controller需要校验多个Form的话:
@Controller
public class XxxController {
@ModelAttribute("AaaForm")
public AaaForm() {
return new AaaForm();
}
@ModelAttribute("BbbForm")
public BbbForm() {
return new BbbForm();
}
@InitBinder("AaaForm")
public void initBinderForAaa(WebDataBinder binder) {
binder.addValidators(aaaValidator);
}
@InitBinder("BbbForm")
public void initBinderForBbb(WebDataBinder binder) {
binder.addValidators(bbbValidator);
}
}
[b](6)自定义校验[/b]
[b]a - 已有注解基础上自定义[/b]
@NotBlank
@Size(min=1, max=5)
public @interface Name {
String message() default "{com.rensanning.springboot.validator.constraints.Name.message}";
// ...
}
[quote]com.rensanning.springboot.validator.constraints.Name.message=xxx[/quote]
@Name
private String name;
[b]b - 单字段校验[/b]
public class AgeValidator implements ConstraintValidator {
int min;
int max;
@Override
public void initialize(Age annotation) {
min = annotation.min();
max = annotation.max();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext paramConstraintValidatorContext) {
if (value == null) {
return true;
}
if (value < min || value > max) {
return false;
}
return true;
}
}
@Constraint(validatedBy={AgeValidator.class})
public @interface Age {
String message() default "{com.rensanning.springboot.validator.constraints.Age.message}";
int min() default 0;
int max() default 100;
}
[quote]com.rensanning.springboot.validator.constraints.Age.message=[/quote]
@Age(min=18)
private Integer age;
[b]c - 多字段关联校验[/b]
@Constraint(validatedBy={PeriodValidator.class})
public @interface Period {
String message() default "{com.rensanning.springboot.validator.constraints.Period.message}";
String fieldFrom() default "from";
String fieldTo() default "to";
}
[quote]com.rensanning.springboot.validator.constraints.Period.message=[/quote]
public class PeriodValidator implements ConstraintValidator {
private String fieldFrom;
private String fieldTo;
private String message;
@Override
public void initialize(Period annotation) {
this.fieldFrom = annotation.fieldFrom();
this.fieldTo = annotation.fieldTo();
this.message = annotation.message();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper beanWrapper = new BeanWrapperImpl(value);
Date from = (Date)beanWrapper.getPropertyValue(fieldFrom);
Date to = (Date)beanWrapper.getPropertyValue(fieldTo);
if (from != null && to != null && from.compareTo(to) > 0) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message)
.addNode(fieldFrom)
.addConstraintViolation();
return false;
}
return true;
}
}
@Period(fieldFrom="from", fieldTo="to")
public class ValidSampleForm {
private Date from;
private Date to;
}
[b](7)同时支持Hibernate Validator 和 Spring Validator[/b]
@Controller
public class UserController {
@Autowired
private UserFormValidator userFormValidator;
@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(@ModelAttribute("userForm") @Valid User user,
BindingResult result, Model model) {
// 手动执行Spring Validator
userFormValidator.validate(user, result);
if (result.hasErrors()) {
//...
} else {
//...
}
}
}