好的,没问题!咱们这就来聊聊 SpringMVC 数据校验这档子事儿,保证让你看完之后,腰不酸了,腿不疼了,数据校验也不再是难题了!
SpringMVC 数据校验:让你的数据不再“裸奔”
各位看官,在互联网这个花花世界里,数据就像我们的小秘密,要好好保护起来。如果数据随随便便就能被篡改、注入,那还得了?轻则显示错误,重则系统崩溃,甚至用户信息泄露,想想都可怕!所以,数据校验就显得尤为重要,它就像我们程序的一道安全防线,拦截那些不靠谱的数据,确保数据的完整性和安全性。
SpringMVC 作为 Web 开发的利器,自然也提供了强大的数据校验功能。今天,我们就来深入了解一下 SpringMVC 如何集成 JSR 303/349 规范和 Hibernate Validator,让我们的数据不再“裸奔”。
一、什么是 JSR 303/349?
JSR (Java Specification Requests) 是 Java 规范请求的缩写,简单来说,就是 Java 官方制定的一些标准。JSR 303 和 JSR 349 都是关于 Bean Validation 的规范,它们定义了一套标准的注解,用于声明 Java Bean 的属性约束。
-
JSR 303 (Bean Validation 1.0): 这是最初的 Bean Validation 规范,定义了一些基本的约束注解,例如
@NotNull
、@Size
、@Min
等。 -
JSR 349 (Bean Validation 1.1): 这是 JSR 303 的升级版,增加了一些新的特性,例如分组校验、容器元素校验等。
你可以把 JSR 303/349 想象成一套“数据校验说明书”,里面详细规定了各种数据应该符合什么样的规则。
二、Hibernate Validator:JSR 303/349 的得力干将
Hibernate Validator 是 Bean Validation 规范的一个具体实现。简单来说,它就是按照 JSR 303/349 的“说明书”来实现数据校验功能的。Hibernate Validator 提供了丰富的校验注解,并且可以方便地进行扩展,满足各种复杂的校验需求。
你可以把 Hibernate Validator 想象成一个“数据校验执行官”,它会严格按照 JSR 303/349 的规定,对数据进行校验。
三、SpringMVC 如何集成 JSR 303/349 和 Hibernate Validator?
SpringMVC 提供了非常方便的方式来集成 JSR 303/349 和 Hibernate Validator。下面我们通过一个简单的例子来说明如何实现:
1. 添加依赖
首先,我们需要在 pom.xml
文件中添加 Hibernate Validator 的依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
注意: Spring Boot 2.3+ 默认集成了 hibernate-validator
,可以省略此步骤。如果使用更早的版本,建议添加依赖。
2. 开启 SpringMVC 的校验功能
在 SpringMVC 的配置文件中(例如 spring-mvc.xml
),我们需要配置 LocalValidatorFactoryBean
来开启校验功能:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- 配置校验器,这里我们使用 Hibernate Validator -->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<!-- 配置快速失败模式,如果有一个校验不通过,立即返回错误 -->
<property name="validationMessageSource" ref="messageSource"/>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
这里解释一下:
LocalValidatorFactoryBean
:这是 Spring 提供的 Bean,用于集成 Bean Validation 规范。providerClass
:指定 Bean Validation 的实现类,这里我们使用 Hibernate Validator。validationMessageSource
: 指定国际化消息文件,用于显示校验错误信息。
3. 在 Controller 中使用校验
现在,我们就可以在 Controller 中使用校验功能了。首先,我们需要在需要校验的 Bean 参数上添加 @Valid
注解,然后在 Controller 方法中注入 BindingResult
对象,用于获取校验结果。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
@Controller
public class UserController {
@GetMapping("/register")
public String registerForm(Model model) {
model.addAttribute("user", new User());
return "register";
}
@PostMapping("/register")
public String registerSubmit(@Valid User user, BindingResult result, Model model) {
if (result.hasErrors()) {
// 如果有校验错误,返回注册页面,显示错误信息
return "register";
}
// 如果校验通过,处理注册逻辑
System.out.println("注册成功:" + user);
return "success";
}
}
在这个例子中,我们定义了一个 User
类,并在 registerSubmit
方法中使用 @Valid
注解来校验 User
对象。如果校验不通过,BindingResult
对象会包含错误信息,我们可以在页面上显示这些错误信息。
4. 定义需要校验的 Bean
接下来,我们需要定义 User
类,并在其属性上添加校验注解:
import javax.validation.constraints.*;
public class User {
@NotEmpty(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在 3 到 20 之间")
private String username;
@NotEmpty(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在 6 到 20 之间")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "必须年满 18 岁才能注册")
private int age;
// 省略 getter 和 setter 方法
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + ''' +
", password='" + password + ''' +
", email='" + email + ''' +
", age=" + age +
'}';
}
}
在这个例子中,我们使用了以下校验注解:
@NotEmpty
:验证字符串不为空。@Size
:验证字符串的长度在指定范围内。@Email
:验证字符串是否为有效的邮箱地址。@Min
:验证数字的最小值。
5. 显示错误信息
最后,我们需要在页面上显示错误信息。我们可以使用 Spring 的 form
标签库来实现:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>注册</title>
</head>
<body>
<h1>注册</h1>
<form:form modelAttribute="user" method="post" action="/register">
<p>
用户名:<form:input path="username"/><form:errors path="username"/>
</p>
<p>
密码:<form:password path="password"/><form:errors path="password"/>
</p>
<p>
邮箱:<form:input path="email"/><form:errors path="email"/>
</p>
<p>
年龄:<form:input path="age"/><form:errors path="age"/>
</p>
<button type="submit">注册</button>
</form:form>
</body>
</html>
在这个例子中,我们使用 <form:errors>
标签来显示每个字段的错误信息。
四、常用的校验注解
Hibernate Validator 提供了丰富的校验注解,可以满足各种常见的校验需求。下面是一些常用的校验注解:
注解 | 说明 |
---|---|
@Null |
验证对象是否为 null 。 |
@NotNull |
验证对象是否不为 null 。 |
@AssertTrue |
验证 Boolean 对象是否为 true 。 |
@AssertFalse |
验证 Boolean 对象是否为 false 。 |
@Min(value) |
验证 Number 或 String 对象是否大等于指定的值。 |
@Max(value) |
验证 Number 或 String 对象是否小等于指定的值。 |
@DecimalMin(value) |
验证 Number 或 String 对象是否大等于指定的值,value 是字符串形式。 |
@DecimalMax(value) |
验证 Number 或 String 对象是否小等于指定的值,value 是字符串形式。 |
@Size(min, max) |
验证 String、Collection、Map 或 Array 对象是否在指定大小之间。 |
@Digits(integer, fraction) |
验证 Number 或 String 对象是否为一个合法的数字,integer 指定整数部分的位数,fraction 指定小数部分的位数。 |
@Past |
验证 Date 或 Calendar 对象是否在当前时间之前。 |
@Future |
验证 Date 或 Calendar 对象是否在当前时间之后。 |
@Pattern(regexp) |
验证 String 对象是否符合指定的正则表达式。 |
@Email |
验证 String 对象是否为有效的邮箱地址。 |
@NotEmpty |
验证 String、Collection、Map 或 Array 对象是否不为空。 |
@NotBlank |
验证 String 对象是否不为空,并且去除前后空格后长度大于 0。 |
@Range(min, max) |
验证 Number 或 String 对象是否在指定范围内 (Hibernate Validator 扩展)。 |
@URL |
验证 String 对象是否为有效的 URL (Hibernate Validator 扩展)。 |
五、分组校验
有时候,我们需要对同一个 Bean 在不同的场景下进行不同的校验。例如,在注册时,我们需要校验密码是否为空,而在修改密码时,我们还需要校验旧密码是否正确。这时,我们就可以使用分组校验来实现。
首先,我们需要定义一个或多个分组接口:
public interface RegisterGroup {}
public interface UpdateGroup {}
然后,在校验注解中指定分组:
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
public class User {
@NotEmpty(message = "用户名不能为空", groups = {RegisterGroup.class})
@Size(min = 3, max = 20, message = "用户名长度必须在 3 到 20 之间", groups = {RegisterGroup.class, UpdateGroup.class})
private String username;
@NotEmpty(message = "密码不能为空", groups = {RegisterGroup.class})
@Size(min = 6, max = 20, message = "密码长度必须在 6 到 20 之间", groups = {RegisterGroup.class, UpdateGroup.class})
private String password;
// 省略 getter 和 setter 方法
}
在这个例子中,username
和 password
字段在 RegisterGroup
和 UpdateGroup
两个分组下都需要进行校验。
最后,在 Controller 中指定需要校验的分组:
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class UserController {
@PostMapping("/register")
public String registerSubmit(@Validated({RegisterGroup.class}) User user, BindingResult result) {
// ...
}
@PostMapping("/update")
public String updateSubmit(@Validated({UpdateGroup.class}) User user, BindingResult result) {
// ...
}
}
在这个例子中,registerSubmit
方法只校验 RegisterGroup
分组下的字段,而 updateSubmit
方法只校验 UpdateGroup
分组下的字段。
六、自定义校验
除了使用 Hibernate Validator 提供的校验注解之外,我们还可以自定义校验器来实现更复杂的校验逻辑。
首先,我们需要创建一个自定义的校验注解:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = {PasswordStrengthValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordStrength {
String message() default "密码强度不够";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
然后,我们需要创建一个自定义的校验器,实现 ConstraintValidator
接口:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordStrengthValidator implements ConstraintValidator<PasswordStrength, String> {
@Override
public void initialize(PasswordStrength constraintAnnotation) {
// 初始化逻辑,可以获取注解中的参数
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 校验逻辑
if (value == null || value.length() < 8) {
return false;
}
// 校验密码是否包含数字和字母
boolean hasDigit = false;
boolean hasLetter = false;
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (Character.isDigit(c)) {
hasDigit = true;
} else if (Character.isLetter(c)) {
hasLetter = true;
}
}
return hasDigit && hasLetter;
}
}
在这个例子中,PasswordStrengthValidator
校验器校验密码的长度是否大于等于 8,并且是否包含数字和字母。
最后,在需要校验的字段上添加自定义的校验注解:
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
public class User {
@NotEmpty(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在 3 到 20 之间")
private String username;
@PasswordStrength(message = "密码强度不够")
private String password;
// 省略 getter 和 setter 方法
}
七、国际化
我们可以使用国际化来显示不同语言的错误信息。首先,我们需要创建一个或多个国际化消息文件,例如 messages.properties
、messages_zh_CN.properties
、messages_en_US.properties
。
在 messages.properties
文件中,我们可以定义默认的错误信息:
javax.validation.constraints.NotEmpty.message = 不能为空
javax.validation.constraints.Size.message = 长度必须在 {min} 到 {max} 之间
com.example.PasswordStrength.message = 密码强度不够
在 messages_zh_CN.properties
文件中,我们可以定义中文的错误信息:
javax.validation.constraints.NotEmpty.message = 不能为空
javax.validation.constraints.Size.message = 长度必须在 {min} 到 {max} 之间
com.example.PasswordStrength.message = 密码强度不够
在 messages_en_US.properties
文件中,我们可以定义英文的错误信息:
javax.validation.constraints.NotEmpty.message = cannot be empty
javax.validation.constraints.Size.message = length must be between {min} and {max}
com.example.PasswordStrength.message = password strength is not enough
然后,在 SpringMVC 的配置文件中配置 messageSource
Bean:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
这样,SpringMVC 就会根据当前的 Locale 来选择合适的国际化消息文件,显示对应语言的错误信息。
八、总结
SpringMVC 集成 JSR 303/349 和 Hibernate Validator 可以让我们轻松地实现数据校验功能,保护我们的数据安全。通过本文的介绍,相信你已经掌握了 SpringMVC 数据校验的基本用法,可以灵活地运用到实际项目中。
记住,数据校验就像给你的程序穿上一件防弹衣,让你的数据不再“裸奔”,安全可靠!
希望这篇文章对你有所帮助!如果你还有其他问题,欢迎随时提问。