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
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@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
```java
@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