对用户输入进行校验(不能为空、最大/最小长度、email格式等)是一个常见需求,所以我们会利用现成的工具,而不会自己在Controller的handler method中写if...else...
在ViewModel中maven引入:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.0.Final</version> </dependency>
因为我们的JDK和tomcat版本,我们这里使用的版本是5.2.0
然后就可以在ViewModel上添加相应的注解:
public class RegisterModel { @NotBlank @Size(min = 4, message = "长度不能小于4") private String username;可以在当前项目直接测试验证是否生效:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); BeanDescriptor descriptor = validator.getConstraintsForClass(RegisterModel.class); System.out.println(descriptor.isBeanConstrained());提示需要el依赖:
<dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>3.0.0</version> </dependency>
演示:有注解为true,没有为false
有点意思:Web项目取不到其他项目中Bean的annotation(估计是tomcat/servlet的问题)
Annotation[] annotations = RegisterModel.class.getDeclaredField("username") .getDeclaredAnnotations(); System.out.println(annotations.length);
所以我们的架构需要调整,从简单的角度考虑,直接把之前所有的项目放SpringMVC一个项目下面。
SpringMVC在model绑定的时候就可以进行model验证,前提是:
public ModelAndView input(@Validated RegisterModel model){PS:@Validated是spring提供的更新的注解
<mvc:annotation-driven />同时不要忘了添加mvc名称空间:
xmlns:mvc="http://www.springframework.org/schema/mvc"和xsi:schemaLocation
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
演示:验证未通过返回400错误
但我们的目的拿到校验的结果,所以需要在model参数后面再加一个参数:BindingResult
// 如果有异常信息 if (result.hasErrors()) { // 获取异常信息对象 List<ObjectError> errors = result.getAllErrors(); // 将异常信息输出 for (ObjectError error : errors) { System.out.println(error.getDefaultMessage()); } }
message的内容,
BindingResult也会传递给freemarker模板,我们可以直接在模板中呈现错误提示:
<@spring.showErrors "<br />" "error"/>
第一个参数是分隔符,仍然不建议使用(间距换行等都应该用CSS实现)
第二个参数可以指定错误提示的样式,
PS:混乱,和之前的attributes参数冲突,个人感觉不好
因为spring.ftl声明了:
<#ftl output_format="HTML" strip_whitespace=true>所以${}呈现的内容一律都会被HTML转义
演示:<br />分割符和message中的a标签“原样”呈现
@Length(min = 4, message = "* 最少4个字符,详见<a href='#'>说明</a>")
spring.ftl中注释说明的使用:
都只能进一步的转义RequestContext中的内容,无法避免其被HTML转义,从而呈现出HTML效果。
推荐的解决方案是:
<b>${error?no_esc}</b> <#if error_has_next>${separator?no_esc}</#if>
model中会标记多个需验证字段:
@NotBlank private String username; @Length(min = 4, message = "* 最少4个字符,详见<a href='#'>说明</a>") private String password;在.ftl中,表单有多个元素要验证:
<@spring.formInput "command.username" /> <@spring.showErrors "<br />"/><br /> <@spring.formPasswordInput "command.password" /> <@spring.showErrors "<br />"/><br />
@想一想@:每个showErrors怎么知道show哪一个error呢?
因为每生成一次表单元素,就会根据command.path更新一次绑定
<@bind path/>在bind中就为status等重新赋值,
<#assign status = springMacroRequestContext.getBindStatus(path)>而showError就依赖于重新bind的status:
<#list status.errorMessages as error>
Model有可能被重用,从而出现:
的情况。@想一想@:这时候后台还会验证这个字段么?
演示:后台不会验证未(被.ftl)使用(生成表单元素)的字段password。
为什么呢?
是否要对某个字段进行验证,和从request请求中获得的信息挂钩:如果request中根本就没有某个键值对信息,就不用验证。
#体会#:password为空,和根本就没有password键名的区别
有时候验证的结果需要更复杂的逻辑,比如用户名是否已经被使用,需要到数据库进行查询:
Boolean usernameExists = userService.hasName(model.getUsername()) != null;想要根据这个结果呈现错误提示,就需要调用BindingResult对象的addError()方法:
result.addError(new FieldError( "command", //model对象名称 "username", //字段名 "* 用户名已使用") //message );
@NotNull:不能为null值,在字段不是String类型时使用(String类型字段生成的表单即使为“空”,model绑定后也不是Null值)
@NotNull private Integer age;
注意这里不能是int age,如果是int的话:
@NotEmpty:不能为空字符串,字段类型为String时使用
@NotBlank:在@NotEmpty的基础上,多个空格也不行(推荐)
不仅仅是文本框,选择类表单元素也可以约束:
hobbies.put("", "-----");
@NotEmpty private String hobby;
<@spring.formRadioButtons "command.isMale" command.genders "" />
Map<String, String> genders = new HashMap<>(); genders.put("1", "男"); genders.put("0", "女");
@NotNull private Boolean isMale;
@Digits (integer, fraction):数值,integer指定整数部分最多能有多少位,fraction指定小数部分最大位数
@Digits(integer = 2, fraction = 1) private Double score;
不限正负!所以上述限制约束为:-99.9 至 99.9
@Email:不解释……^_^
@Pattern(value):传入任意一个正则表达式@Min(value)和@Max(value):最大最小值,用于整型
@Min(0) @Max(150) private Integer age;
@DecimalMin(value)和@DecimalMax(value):最大最小值,用于小数
@DecimalMin("0") @DecimalMax("100") private Double score;
但参数要求字符串类型,奇奇怪怪的……
@Range(min, max):推荐(配合@Digits)使用,整数小数通用
@Range(min = 0, max = 100) //默认min为0,max为long的最大值 private Double score;
@Length(min, max):用于字符串长度
@Size(max, min):用于集合,比如<@inputCheckboxes>:
@Size(min = 1, max = 2) private String[] hobby;
PS:用于字符串时(不建议)同@Length
@Past和@Future:必须早于或者晚于当前时间,但我们因为版本原因只能使用Calendar类型(不能使用LocalDate/Time)
@Past @DateTimeFormat(pattern = "yyyy-MM-dd") private Calendar enroll;
对照一起帮,为之前的表单标签添加验证。注意不要遗漏:
提高自学:分组和自定义
多快好省!前端后端,线上线下,名师精讲
更多了解 加: