Bean Validation数据校验和分组校验

前端校验后,为什么需要后端校验

在前面完成数据参数绑定到Controller时,我们可以在@RequestParam注解中做简单的空校验,就是设置required属性为true,以此来指定Controller方法中形参是否必须传入。数据校验是很常见的操作,有前端校验,即用户在前端页面上填写表单时,检查数据的合法性,再传入到后端。到了后端,还可以有后端的数据校验,后端校验通常是在业务逻辑方法,也就是我们的Controller方法里完成,例如在参数列表里使用注解完成数据校验。后端校验的原因是为了安全性考虑,防止别人通过接口乱调用后台的业务逻辑方法,假设在业务方法中不进行数据校验,有可能会被别让绕过前端校验,直接通过后端的接口方法调用,传入大量的脏数据到数据库。

在这篇日志中,总结自己使用集成Hibernate的校验框架, 和Spring MVC的Bean Validation数据校验。Bean Validation数据校验可以检查JavaBean中的数据,接下来先看看校验器的搭建。

 

配置validation校验器

validator声明和属性配置

上面说到,后端校验通常放在业务逻辑方法中,在Spring MVC里也就是我们的处理器适配器中具体实现方法,通过在形参列表中使用注解,完成数据校验。我的处理器映射器和适配器都是用注解的方式配置,即在Spring MVC的配置文件里使用的annotation-driven标签对,既然后端数据校验发生在处理器适配器中,当然校验器也要在annotation-driven标签里声明了:

<!-- 简写方式 配置注解的处理器映射器和适配器 -->
<!-- 添加名为"validator的校验器" -->
<mvc:annotation-driven  validator="validator"></mvc:annotation-driven>

在annotation-driven标签中我们声明一个validator属性,指定了一个id为validator校验器。然后我们就继续在下面配置这个id为“validator”的校验器属性:

<!-- 配置校验器 -->
<bean id="validator" 
			class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
	<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
	<property name="validationMessageSource" ref="messageSource" />
</bean>

LocalValidatorFactoryBean是默认的校验器实现,下面配置的providerClass为继承的Hibernate校验框架,最后validationMessageSource表示的是校验使用的资源文件,里面可以自定义编写当校验出数据不合法时,给出的错误提示。如果不配置这个校验资源文件,会默认使用ValidationMessages.properties。

校验资源文件配置

当我们声明了校验资源文件后,就可以使用自己编写的文件实现自定义错误提示信息。首先在Spring MVC配置文件中配置这个资源文件:

<!-- 配置校验资源文件 -->
		<bean id="messageSource" 
			class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
			<property name="basenames">
				<list>
					<value>classpath:UserValidationMessages</value>
				</list>
			</property>
			<property name="fileEncodings" value="utf-8" /> <!-- 资源文件编码格式 -->
			<property name="cacheSeconds" value="120" /> <!-- 资源文件内容的缓存事件1 -->
		</bean>


ReloadableResourceBundleMessageSource类用提供读取资源属性文件,也就是我们的校验资源文件,它需要指定一些属性,资源文件名basenames和资源文件的编码格式fileEncodings和资源文件内容的缓存时间cacheSeconds,在这里我们配置校验资源文件为UserValidationMessages,这是我们自己编写的properties文件:

# 设置校验错误提示信息
user.name.length.error=Please enter a name with a length of 1-20 characters.
user.gender.isEmpty=Please enter gender

在下面具体校验注解中我们才会看到它的使用。

前端数据类型转换

还有一个配置,使用conversionService接口完成将前端客户端的一些数据进行转换后,再传入后端服务器端。为什么需要数据类型转换?你看再前端页面中用户输入的数据通常是基本数据类型,整数或者字符串,但传到后端Controller进行业务逻辑处理时,通常我们是用Java类来保存数据。前面的数据参数绑定我们就可以看到,我们的查询用户页面,用户名和性别在前端页面都是字符串类型,传到后端进行查询时,是要用Java类型保存,再传入Controller方法中。所以有时候需要对前端数据进行转换后,再传入后端。我们可以在annotation-driven标签中,配置conversionService属性:

<mvc:annotation-driven  conversion-service="conversionService" 
    validator="validator">
</mvc:annotation-driven>

然后配置conversionService使用FromattingConversionServiceFactoryBean类,该类可以完成字符串和Date类型的转换。

<!-- 字符串转为Date类型的Java类 -->
		<bean id="conversionService" 
			class="org.springframework.format.support.FormattingConversionService-FactoryBean">
		</bean>

      其实,如果我们不在annotation-driven标签中配置conversion-service属性,还是会默认加载一个ConversionService,而且使用的就是FromattingConversionServiceFactoryBean类,在这里我是想展示出有这么一个配置,可以完成前端的数据类型转换。

测试用例

添加校验注解

来到校验注解配置,在这里就要指定我们要对哪些数据进行校验了,例如我们的Controller方法完成对用户数据的查询,Java实体类是User,要求前端填入的查询数据,姓名长度范围在1-20个字符之间,性别不能为空。那么在User类中我们就可以这么配置:

public class User {
    
    private Integer id;
    @Size(min=1, max=20, message="{user.name.length.error}")
    private String username;

    private String password;
    @NotEmpty(message="{user.gender.isEmpty}")
    private String gender;

    private String email;

    private String province;

    private String city;

    private Date birthday;

    private Integer age;

    // 省略get()和set()方法
}

在username属性上面,我么使用@Size注解,指定min=1和max=20,校验的错误信息为user.name.length.error。在gender属性上,使用@NotEmpty注解完成非空校验,错误信息为user.gender.isEmpty。由于我们在前面配置了校验资源文件,所以输出的会是我们的自定义错误提示。

      最后的配置,就是在我们的Controller方法形参列表中,使用@Validated注解,指定哪些参数数据需要进行校验:

@Controller
@RequestMapping("user")
public class validationControllerTest {
	private UserServiceImpl userService = new UserServiceImpl();
	@RequestMapping("validationByCondition")
	public String queryUserByCondition(Model model, 
			@Validated User user, 
			BindingResult bindingResult) {
		// 保存校验错误信息
		List<ObjectError> ErrorList = null;
		if (bindingResult.hasErrors()) {
			ErrorList = bindingResult.getAllErrors();
			for (ObjectError objectError : ErrorList) {
				System.out.println(objectError.getDefaultMessage());
			}
		}
		
		List<User> userList = null;
		
		if(user == null || (user.getUsername() == null && user.getGender() == null)) {
			// 如果查询框中两个查询条件都为空,则默认查询所有顾客数据
			userList = userService.queryUserList();
		} else { 
			// 否则进行条件查询
			userList = userService.queryUserByCondition(user);
		}
		
		// model数据传到页面
		model.addAttribute("userList", userList);
		model.addAttribute("ErrorList", ErrorList);
		
		return "users/validateUser"; // 返回视图
	}
}

可以看到,我们要对传入方法的User类实例进行数据校验,并且传入BindingResult类对象,它存放了校验错误信息。第9行首先定义一个ErrorList来存放校验错误信息。第11行获取所有的错误信息放到ErrorList中,然后做输出,如果只是这样输出,错误信息是显示在了控制台上,所以第29行我们通过model对象,将这个ErrorList传到前端页面去显示。

前端视图

为了在前端页面中显示错误信息提示,我们通过model对象把错误信息传到了前端视图中显示:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户查询列表</title>
</head>
<body>
	<form action="validationByCondition.action" method="post">
		用户名:<input type="text" name="username" />&nbsp;&nbsp;
		性别:<input type="text" name="gender" />
		<input type="submit" value="查找" />
	</form>
	<!-- 显示错误信息 -->
	<c:if test="${ErrorList != null}">
		<c:forEach items="${ErrorList}" var="error">
			<font color="red">${error.defaultMessage}</font><br/>
		</c:forEach>
	</c:if>
	<hr/>
	<h2>搜索结果</h2>
	<table width="300px;" border=1>
		<tr>
			<td>顾客名</td>
			<td>性别</td>
			<td>电子邮箱</td>
			<td>省会</td>
			<td>城市</td>
		</tr>
		
		<c:forEach items="${userList }" var="user">
			<tr>
				<td>${user.username }</td>
				<td>${user.gender }</td>
				<td>${user.email }</td>
				<td>${user.province }</td>
				<td>${user.city }</td>
			</tr>
		</c:forEach>	
	</table>
</body>
</html>

第15行我们接收传来的ErrorList,并遍历错误信息,将其显示出来:

 

Bean Validation分组校验

使用场景

分组校验的使用场景是这样的,假设我们的多个Controller方法都用到了同一个Java类作为数据存储并传入,我们需要对类中哪些属性进行校验,是在实体类里面使用注解标识的,但是如果我们一个Controller方法只需对类中的某一个属性做校验,而另一个Controller方法需要对类中的两个属性做校验,怎么办?方法是使用分组校验,在Java实体类中的属性里标注分组信息,然后在不同的Controller方法中形参列表里的@Validated注解添加分组属性,根据不同的分组,实现不同的校验。

配置分组信息

首先创建两个空接口,用来标识不同的分组:

然后在Java实体类中,为需要校验的属性username和gender,在其注解里添加分组:

public class User {
    
    private Integer id;
    @Size(min=1, max=20, message="{user.name.length.error}", 
    		groups= {UserGroup1.class})
    private String username;

    private String password;
    @NotEmpty(message="{user.gender.isEmpty}", groups= {UserGroup2.class})
    private String gender;

    private String email;

    private String province;

    private String city;

    private Date birthday;

    private Integer age;

    //省略get()和set()方法
}

我们将username属性的校验放在了UserGroup1,gender属性的校验放在了UserGroup2。

最后,我们在Controller方法里,根据不同的业务逻辑,配置校验分组,实现对共用数据类型的不同校验方式:

@Controller
@RequestMapping("user")
public class validationControllerTest {
	private UserServiceImpl userService = new UserServiceImpl();
	@RequestMapping("validationByCondition")
	public String queryUserByCondition(Model model, 
			@Validated(value=UserGroup1.class) User user, 
			BindingResult bindingResult) {
		// 保存校验错误信息
		List<ObjectError> ErrorList = null;
		if (bindingResult.hasErrors()) {
			ErrorList = bindingResult.getAllErrors();
			for (ObjectError objectError : ErrorList) {
				System.out.println(objectError.getDefaultMessage());
			}
		}
		
		List<User> userList = null;
		
		if(user == null || (user.getUsername() == null && user.getGender() == null)) {
			// 如果查询框中两个查询条件都为空,则默认查询所有顾客数据
			userList = userService.queryUserList();
		} else { 
			// 否则进行条件查询
			userList = userService.queryUserByCondition(user);
		}
		
		// model数据传到页面
		model.addAttribute("userList", userList);
		model.addAttribute("ErrorList", ErrorList);
		
		return "users/validateUser"; // 返回视图
	}
}

在@Validated注解中,添加UserGroup1分组,这样,在调用该方法进行业务逻辑处理时,只会对该分组标识的username属性进行校验:

 

完整实现GitHub已上传:

https://github.com/justinzengtm/SSM-Framework/tree/master/SpringMVC_Project

发布了97 篇原创文章 · 获赞 71 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/justinzengTM/article/details/102885103
今日推荐