数据格式校验的重要性
关于数据格式完整的重要性相信各位都有足够的认识,很多时候我们被要求这个类的这个字段不可为空,这个框应该填入的是数字,这个框应该填入的是一个邮箱格式/手机号码格式...,面对一连串的格式要求传统的办法是自行写一个类对传入的这个类进行一一判断审查,但是作为一名程序员必定不能容忍这种方法,故而在这里给各位介绍一种方便的检验方法。
框架&导入包
SSM、SSH框架
基本校验器:javax.validation
hibernate校验器:org.hibernate.validator
以上两个包版本最好选择比较新的,否则一些注解不支持
格式校验配置顺序
1.新建错误信息文件
2.绑定校验器
3.编写pojo类
4.根据要求给pojo类属性添加格式注解
以下是详解
第一步:新建错误信息文件
该文件是非必须的
该文件存在的意义仅仅是为了不要暴露设计者的设计思想罢了,并不是一个硬需求,到后面的注解编写就会理解这里说的是什么。
一般建议使用properties文件方便XML的读入。
第二步:绑定校验器
这一步一定要做,否则配置出来的校验器将无法工作。那么绑定是怎么个绑定法?以SSM框架为例,必然有一配置文件spring-mvc.xml,虽然每个人自己搭的SSM框架可能名字起的不是很一致,但是大概都是这种格式的名称,在其中添加进去如下代码:
<mvc:annotation-driven validator="validator"/>
<!-- 校验器 -->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- hibernate校验器 -->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
<!-- 指定校验使用的资源文件,在文件中配置校验错误信息,如果不指定则默认使用classpath下的
ValidationMessages.properties -->
<property name="validationMessageSource" ref="messageSource" />
</bean>
这个配置是什么意思呢?也就是定义一数据校验器,class为:org.hibernate.validator.HibernateValidator
而下面的这个validationMessageSource是我们写的那个错误信息文件,之前说了这个是非必须的所以这个配置也是可以省略的。
假如你新建了错误信息文件,那么需要新添加这个配置:
<!-- 校验错误信息配置文件 xxx.properties -->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- 资源文件名 基名-->
<property name="basenames">
<list>
<value>classpath:你的文件名称,后缀可以省略</value>
</list>
</property>
<!-- 资源文件编码格式 -->
<property name="fileEncodings" value="utf-8" />
<!-- 对资源文件内容缓存时间,单位秒 -->
<property name="cacheSeconds" value="120" />
</bean>
这样配置就完成了。
第三,四步:编写pojo类,根据要求编写错误信息
这是本是非常简单的一件事,但是由于我们的校验器的存在使得这个类承担了更多的责任,他需要在属性上面加上注解,如下:
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
public class Auctionuser {
private Integer userid;
@Size(min=3,max=6,message="{register.username.length.error}")
private String username;
@Size(min=6,message="{register.password.length.error}")
private String userpassword;
@Pattern(regexp="[0-9]{18}",message="{register.usercardno.length.error}")
private String usercardno;
@Size(min=7,max=8,message="{register.usertel.length.error}")
private String usertel;
private String useraddress;
private String userpostnumber;
private Integer userisadmin;
private String userquestion;
private String useranswer;
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
public String getUserpassword() {
return userpassword;
}
public void setUserpassword(String userpassword) {
this.userpassword = userpassword == null ? null : userpassword.trim();
}
public String getUsercardno() {
return usercardno;
}
public void setUsercardno(String usercardno) {
this.usercardno = usercardno == null ? null : usercardno.trim();
}
public String getUsertel() {
return usertel;
}
public void setUsertel(String usertel) {
this.usertel = usertel == null ? null : usertel.trim();
}
public String getUseraddress() {
return useraddress;
}
public void setUseraddress(String useraddress) {
this.useraddress = useraddress == null ? null : useraddress.trim();
}
public String getUserpostnumber() {
return userpostnumber;
}
public void setUserpostnumber(String userpostnumber) {
this.userpostnumber = userpostnumber == null ? null : userpostnumber.trim();
}
public Integer getUserisadmin() {
return userisadmin;
}
public void setUserisadmin(Integer userisadmin) {
this.userisadmin = userisadmin;
}
public String getUserquestion() {
return userquestion;
}
public void setUserquestion(String userquestion) {
this.userquestion = userquestion == null ? null : userquestion.trim();
}
public String getUseranswer() {
return useranswer;
}
public void setUseranswer(String useranswer) {
this.useranswer = useranswer == null ? null : useranswer.trim();
}
}
可以看到其实它与一般的bean差不多但是多了一些注解例如@Size,@Pattern等,英文水平稍微好点的直接就理解这是什么了,这两个注解一个是属于javax.validation.constraints.Size另外一个属于javax.validation.constraints.Pattern,答案很明显了这两个就是承担校验的注解。以下是大部分注解的名称与使用方法:

图片来源于:https://www.cnblogs.com/xiaogangfan/p/5987659.html
如@Size(min=3,max=6,message="{register.username.length.error}")这句注释的意思就是这个属性的最低长度为3,最大长度为6,一旦错误后就会产生register.username.length.error这个错误,但是我们有个问题,register.username.length.error到底是个什么?还记得我们前面新建的错误信息文件吗,properties文件是一个以K-V形式存储信息的一个文件,下面是这个文件的截图:

我们可以在其中找到
register.username.length.error=u7528u6237u540Du957Fu5EA6u5FC5u987Bu4E3A3u52306u4F4D
后面的那个unicode编码是中文字符串,把鼠标移动到这串unicode编码中就能查看当前信息:

当然这个方法不是唯一的,你也可以直接在message中直接写入中文也是支持的,但是使用错误信息文件的方法一方面不会导致我们的错误信息暴露,另外一方面也易于维护,本人强烈推荐。
测试
做测试以前需要我们在我们的controller加入一些注解:
@RequestMapping("/register")
public String register(Model model,@Validated Auctionuser user,BindingResult bindResult) {
if(bindResult.hasErrors()) {
List<FieldError> errors = bindResult.getFieldErrors();
for(FieldError error:errors) {
model.addAttribute(error.getField(), error.getDefaultMessage());
}
model.addAttribute("registerUser", user);
return "register";
}
userservice.insertAuctionUser(user);
System.out.println("--------------->添加用户成功");
return "login";
}
这个controller很简单,用户在前端页面填写信息提交到这个方法,但是我们还是出现了一些陌生的东西例如:@Validated,这个注解就是告诉容器在传入这个方法前先对这个Auctionuser类的这个user对象进行一次数据格式校验,校验的就是刚刚我们写的那些@Size和@Pattern,假如出现了错误信息那么就把错误信息填充到BindingResult类中,一次校验对应一次BindingResult。
error.getField()获取属性名称(username,password等)
error.getDefaultMessage()获取错误信息message
这代码也不是很难所以也就不怎么解释了。
运行结果截图:
before:

after:

是不是挺有趣?
Jeesite数据格式校验
Jeesite是一个高度封装的框架,很多东西thinkgem老哥已经给我们封装好了,但是Jeesite的官方文档只能说内容太少太少了,很多地方只能看他的源码才能知道它的流程,最近更新了一下项目,把以前的校验方法都删除变成了上面这种格式,下面说一下Jeesite是如何使用数据校验的:
首先pojo类还是需要我们写注解,错误信息文件操作照常,接下来是重点:在controller中不要使用@Validated对bean进行校验!!!,Jeesite有自己的错误检查框架,每一个controller都会继承了一个名为BaseController的类,一旦出错后他会拦截异常直接跳转到对应的方法,代码如下,你可以通过ctrl进去basecontroller进行查看:
/**
* 参数绑定异常
*/
@ExceptionHandler({BindException.class, ConstraintViolationException.class,
ValidationException.class})
public String bindException() {
return "error/400";
}
而这个拦截后就直接跳转到了400页面我们就无法和上面那样对错误信息进行操作了,所以我们应该采用thinkgem老哥给我们留下的另外一条路:
/**
* 服务端参数有效性验证
* @param object 验证的实体对象
* @param groups 验证组
* @return 验证成功:返回true;严重失败:将错误信息添加到 message 中
*/
protected boolean beanValidator(Model model, Object object, Class<?>... groups) {
try{
BeanValidators.validateWithException(validator, object, groups);
}catch(ConstraintViolationException ex){
List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
list.add(0, "数据验证失败:");
addMessage(model, list.toArray(new String[]{}));
return false;
}
return true;
}
/**
* 服务端参数有效性验证
* @param object 验证的实体对象
* @param groups 验证组
* @return 验证成功:返回true;严重失败:将错误信息添加到 flash message 中
*/
protected boolean beanValidator(RedirectAttributes redirectAttributes, Object object, Class<?>...
groups) {
try{
BeanValidators.validateWithException(validator, object, groups);
}catch(ConstraintViolationException ex){
List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
list.add(0, "数据验证失败:");
addMessage(redirectAttributes, list.toArray(new String[]{}));
return false;
}
return true;
}
这两个方法都是可以进行数据校验的方法,只要在controller中调用然后根据需要选择使用哪一个就好:
需要跳转到另外一个页面的使用上面的那个,需要重定向到原来页面的使用下面的那个。
但是thinkgem老哥还是留了点小瑕疵,他的错误信息把我们的属性暴露出来了,所以我把他的信息方法改成了:
/**
* 辅助方法, 转换Set<ConstraintViolation>为List<propertyPath +separator+ message>.
*/
@SuppressWarnings("rawtypes")
public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation>
constraintViolations,
String separator) {
List<String> errorMessages = Lists.newArrayList();
for (ConstraintViolation violation : constraintViolations) {
//errorMessages.add(violation.getPropertyPath() + separator +
violation.getMessage());
errorMessages.add(violation.getMessage());
}
return errorMessages;
}
注释的那句是原来的,而我们只需要错误信息不需要属性名称,除非你知道你在做什么而且你觉得有必要你可以不改。
先码到这,后面有更多研究会进行修改。