Spring MVC 学习笔记3 验证Validator

Spring Framework 验证有两种方式,一是 Spring 传统的,对需要验证的 POJO 对象编写一个对应的验证类(实现Validator接口);二是标准的 JSR-303/349 Bean Validation API,需要第三方的实现(Hibernate-validator);三是使用 JSR-349 中的 ScriptAssert。

下面以注册页面验证为例,用 JSR Bean Validation 实现基本字段验证,应用不同手段实现密码校验。

要实现验证的注册页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<html>
<head>
<meta charset="utf-8">
<title>Register</title>
<style type="text/css">
.error { color: red; font-size: 0.9em; font-weight: bold; }
</style>
</head>
<body>
<h1>Register</h1>
<sf:form method="POST" action="register.do" modelAttribute="registerCommand">
<fieldset>
<table cellspacing="0">
<tr>
<th><label for="username">Username</label></th>
<td><sf:input path="username" id="username" size="15" maxlength="15"/>
<sf:errors path="username" cssClass="error" />
</td>
</tr>
<tr>
<th><label for="fullname">Nickname</label></th>
<td><sf:input path="fullname" id="fullname" size="15"/>
<sf:errors path="fullname" cssClass="error" />
</td>
</tr>
<tr>
<th><label for="password">Password</label></th>
<td><sf:password path="password" id="password" size="30"/>
<sf:errors path="password" cssClass="error" />
</td>
</tr>
<tr>
<th><label for="confirmation">Password</label></th>
<td><sf:password path="confirmation" id="confirmation" size="30"/>
<sf:errors path="confirmation" cssClass="error" />
</td>
</tr>
<tr>
<th><label for="email">Email</label></th>
<td><sf:input path="email" id="email" size="30"/>
<sf:errors path="email" cssClass="error" />
</td>
</tr>
<tr>
<th></th>
<td><input name="commit" type="submit" value="I accept. Create my account."></td>
</tr>
</table>
</fieldset>
</sf:form>

</body>
</html>

基本配置

pom.xml

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.0.Final</version>
</dependency>

servlet.xml

1
2
3
4
5
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="errormess"/>
</bean>

resources 目录下 errormess.properties

1
2
3
4
5
6
7
8
Size=the {0} field must be between {2} and {1} characters long
Size.loginCommand.name=Name must be between {2} and {1} characters
password.confirmation.error=Password don't confirm
password.null=Field cannot be left blank
password.confirmation.null=Field cannot be left blank

NotEmpty=Field cannot be left blank
NotNull=Field cannot be left blank

验证对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RegisterCommand {
// omit getter and setter

@Size(min=5, max=20)
@Pattern(regexp="^[a-zA-Z0-9]+$", message="Username must be alphanumeric with no spaces")
private String username;

private String fullname;

@Size(min=6, max=20)
private String password;

@Size(min=6, max=20)
private String confirmation;

@Email
private String email;
}

方法一,编写自定义 Validator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RegistrationValidator implements Validator{
@Override
public boolean supports(Class<?> aClass) {
return RegistrationValidator.class.isAssignableFrom(aClass);
}

@Override
public void validate(Object o, Errors errors) {
RegisterCommand registerCommand = (RegisterCommand) o;
if (!registerCommand.getConfirmation().equals(registerCommand.getPassword()))
errors.rejectValue("confirmation", "password.confirmation.error");

}
}

控制器注入自定义的Validator,HomeController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@Controller
public class HomeController {
private static String salt = "SECRET";
//private static final int DEFAULT_SPITTERS_PER_PAGE = 25;

@Autowired
private UserService userService;

@Autowired
private RegistrationValidator registrationValidator;

@RequestMapping(value="/", method = RequestMethod.GET)
public String loginCommand(Model model){
model.addAttribute("loginCommand", new LoginCommand());
return "home";
}

@RequestMapping(value="/", method = RequestMethod.POST)
public ModelAndView loginCheck(@ModelAttribute("loginCommand") @Valid LoginCommand loginCommand,
BindingResult result,
HttpServletRequest request, Model model)
{
String name = loginCommand.getUsername();
String pass = loginCommand.getPassword();

if (result.hasErrors()) {
return new ModelAndView("home");
}

String secretPass = DigestUtils.md5Hex(salt + pass);
boolean valid = userService.hasMatchUser(name, secretPass);
if (!valid) {
return new ModelAndView("redirect:/", "fatal", "Username or password error!");
} else {
User user = userService.findUserByName(name);
user.setLastIp(request.getRemoteAddr());
user.setLastVisit(new Date());
userService.loginSuccess(user);
request.getSession().setAttribute("user", user);
String userPage = "redirect:user/" + name;
return new ModelAndView(userPage);
}

}

@RequestMapping(value="/user/{name}")
public ModelAndView userPage(@PathVariable String name, HttpServletRequest request, Model model) {
model.addAttribute("name", name);
return new ModelAndView("sucess");
}

@RequestMapping(value="register.html", method=RequestMethod.GET)
public String register(Model model) {
model.addAttribute("registerCommand", new RegisterCommand());
return "register";
}

@RequestMapping(value="register.do", method=RequestMethod.POST)
public String register(@ModelAttribute("registerCommand") @Valid RegisterCommand registerCommand,
BindingResult result,
HttpServletRequest request, Model model)
{
registrationValidator.validate(registerCommand, result);

if (result.hasErrors()) {
return "register";
}

User user = new User();
user.setUserName(registerCommand.getUsername());
user.setPassword(DigestUtils.md5Hex(salt + registerCommand.getPassword()));
user.setLastIp(request.getRemoteAddr());
user.setLastVisit(new Date());
userService.insertUser(user);

model.addAttribute("loginCommand", new LoginCommand());
return "home";
}
}

方法二,编写注解类

CheckPassword

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Target({ TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = CheckPasswordValidator.class)
@Documented
public @interface CheckPassword {

//默认错误消息
String message() default "";

//分组
Class<?>[] groups() default { };

//负载
Class<? extends Payload>[] payload() default { };

//指定多个时使用
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
@interface List {
CheckPassword[] value();
}
}

public class CheckPasswordValidator implements ConstraintValidator<CheckPassword, RegisterCommand> {

@Override
public void initialize(CheckPassword constraintAnnotation) {
}

@Override
public boolean isValid(RegisterCommand user, ConstraintValidatorContext context) {
if(user == null) {
return true;
}

//两次密码不一样
if (!user.getPassword().trim().equals(user.getConfirmation().trim())) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("{password.confirmation.error}")
.addPropertyNode("confirmation")
.addConstraintViolation();
return false;
}
return true;
}
}

现在只需要在验证对象 RegisterCommand 上加上注解 @CheckPassword 即可

这里 https://github.com/jirutka/validator-spring ,有人写了一个更加通用的解决方案,可以在注解中自定义验证方法

1
2
3
4
5
6
7
8
@SpELAssert(value = "password.equals(passwordVerify)",
applyIf = "password || passwordVerify",
message = "{validator.passwords_not_same}")
public class User {

private String password;
private String passwordVerify;
}

方法三,使用SpELAssert

JSR-349 Bean Validation API 1.1

1
2
@ScriptAssert(script = "_this.confirmation==_this.password", lang = "javascript", alias = "_this",
message="Password don't confirm")

参考

http://codetutr.com/2013/05/28/spring-mvc-form-validation/

http://haohaoxuexi.iteye.com/blog/1812584

http://www.javacodegeeks.com/2013/06/spring-mvc-validator-and-initbinder.html

https://github.com/jirutka/validator-spring