键盘敲烂,月薪过万作业不做,等于没学
当前系列: 从JSP到Spring 修改讲义

对用户输入进行校验(不能为空、最大/最小长度、email格式等)是一个常见需求,所以我们会利用现成的工具,而不会自己在Controller的handler method中写if...else...


hibernate-validator

虽然以hibernate开头,但并不从属于作为ORM工具的hibernate,它是实现了JSR(Java Specification Requests)的、通过annotation直接指定校验要求的一个独立组件。

在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一个项目下面。


@Validated 和 BindingResult

SpringMVC在model绑定的时候就可以进行model验证,前提是:

  • handler method中的model参数被@Validated(或@Valid)注解
    public ModelAndView input(@Validated RegisterModel model){
    PS:@Validated是spring提供的更新的注解
  • springmvc-servlet.xml中配置了mvc:annotation-driven,告诉SpringMVC“由annotation驱动”,这里就是:在model绑定的时候,要根据@Validated或@Valid注解进行验证:
    <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的内容,

  • 可以在注解中由message指定
  • 未指定:由hibernate-validator提供当地(locale)默认错误提示(演示:查看ValidationMessages_zh_CN.properties


<@spring.showErrors>

BindingResult也会传递给freemarker模板,我们可以直接在模板中呈现错误提示:

<@spring.showErrors "<br />" "error"/>

样式

第一个参数是分隔符,仍然不建议使用(间距换行等都应该用CSS实现)

第二个参数可以指定错误提示的样式,

  • 既可以是style
  • 也可以是class
演示:

PS:混乱,和之前的attributes参数冲突,个人感觉不好

HTML转义

因为spring.ftl声明了:

<#ftl output_format="HTML" strip_whitespace=true>
所以${}呈现的内容一律都会被HTML转义

演示:<br />分割符和message中的a标签“原样”呈现

@Length(min = 4, message = "* 最少4个字符,详见<a href='#'>说明</a>")

spring.ftl中注释说明的使用:

  • springMacroRequestContext.setDefaultHtmlEscape(true)
  • web.xml配置
  • 设置htmlEscape变量值

都只能进一步的转义RequestContext中的内容,无法避免其被HTML转义,从而呈现出HTML效果。

推荐的解决方案是:

  • 从spring-webmvc-xxx.jar中复制出spring.ftl,粘贴到WEB-INF/views/shared下,并重命名为_spring.ftl
  • 在页面中将其按普通.ftl模板页面引入
  • 随心所欲的更改,比如:
    <b>${error?no_esc}</b>
    <#if error_has_next>${separator?no_esc}</#if>
演示:<br />和<a>生效。


深入理解

当前path

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有可能被重用,从而出现:

  • 某个字段(比如password)被标记了需要验证不能为空,
  • 但模板中根本就没有使用这个字段生成表单元素

的情况。@想一想@:这时候后台还会验证这个字段么?

演示:后台不会验证未(被.ftl)使用(生成表单元素)的字段password。

为什么呢?

是否要对某个字段进行验证,和从request请求中获得的信息挂钩:如果request中根本就没有某个键值对信息,就不用验证。

#体会#:password为空,和根本就没有password键名的区别

addError()

有时候验证的结果需要更复杂的逻辑,比如用户名是否已经被使用,需要到数据库进行查询:

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的话:

  • 文本框就会显示一个默认值0,因为int有默认值
  • 删除0再提交,会报异常,因为空字符串无法转变成int类型

@NotEmpty:不能为空字符串,字段类型为String时使用

@NotBlank:在@NotEmpty的基础上,多个空格也不行(推荐)

不仅仅是文本框,选择类表单元素也可以约束:

  • 选select,以hobbie为例
    • 首先添加一个用户不选的option:
      hobbies.put("", "-----");
    • 然后添加@NotEmpty(因为value是String类型)的约束
      @NotEmpty
      private String hobby;
  • 单选radio,以isMale为例
    • 使用表单标签:
      <@spring.formRadioButtons "command.isMale" 
      	command.genders
      	"" />
    • 生成可选项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;


作业

对照一起帮,为之前的表单标签添加验证。注意不要遗漏:

  1. 联系方式:QQ只能由数字组成
  2. 求督促:开始执行时间必须大于当前时间(日期)
  3. 我的消息:选中数量大于1小于5(纯练习用)
  4. 注册:确认密码和密码必须一致
  5. 求督促:目标结束时间大于开始执行时间
  6. 求助发布:设置的帮帮币悬赏数量不能高于现有的数量

提高自学:分组和自定义


学习笔记
源栈学历
键盘敲烂,月薪过万作业不做,等于没学

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 从JSP到Spring 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码