👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
📖 本文概述
本文是SSM框架系列SpringMVC基础篇的第三篇,将深入探讨SpringMVC的数据绑定机制和数据验证功能。通过详细的代码示例和最佳实践,帮助读者掌握数据绑定的原理和验证框架的使用。
🎯 学习目标
- 深入理解SpringMVC的数据绑定原理
- 掌握各种数据类型的绑定方法
- 学会使用Bean Validation进行数据验证
- 了解自定义验证器的实现
- 掌握数据转换和格式化技巧
1. 数据绑定原理
1.1 数据绑定概述
SpringMVC的数据绑定是将HTTP请求参数自动转换为Java对象的过程:
java
/**
* 数据绑定原理演示
*/
public class DataBindingPrinciple {
/**
* 传统Servlet方式处理表单数据
*/
public void traditionalWay(HttpServletRequest request) {
// 手动获取参数
String username = request.getParameter("username");
String email = request.getParameter("email");
String ageStr = request.getParameter("age");
// 手动类型转换
Integer age = null;
if (ageStr != null && !ageStr.isEmpty()) {
try {
age = Integer.parseInt(ageStr);
} catch (NumberFormatException e) {
// 处理转换异常
}
}
// 手动创建对象
User user = new User();
user.setUsername(username);
user.setEmail(email);
user.setAge(age);
// 手动验证
if (username == null || username.trim().isEmpty()) {
// 处理验证错误
}
}
/**
* SpringMVC数据绑定方式
*/
@PostMapping("/user")
public String springMvcWay(@Valid @ModelAttribute User user,
BindingResult bindingResult) {
// SpringMVC自动完成:
// 1. 参数获取
// 2. 类型转换
// 3. 对象创建和属性设置
// 4. 数据验证
if (bindingResult.hasErrors()) {
return "user/form";
}
// 直接使用绑定好的对象
userService.save(user);
return "redirect:/user/list";
}
}
1.2 数据绑定流程
java
/**
* SpringMVC数据绑定流程详解
*/
public class DataBindingFlow {
/**
* 数据绑定的完整流程
*/
public void bindingProcess() {
/*
* 1. 请求参数解析
* HTTP请求: POST /user
* Content-Type: application/x-www-form-urlencoded
* Body: username=john&email=john@example.com&age=25&birthday=2023-01-01
*/
/*
* 2. 参数名称解析
* SpringMVC解析出参数名称:
* - username
* - email
* - age
* - birthday
*/
/*
* 3. 目标对象创建
* 根据方法参数类型创建User对象实例
*/
/*
* 4. 属性路径解析
* 将参数名称映射到对象属性路径:
* - username -> user.username
* - email -> user.email
* - age -> user.age
* - birthday -> user.birthday
*/
/*
* 5. 类型转换
* 将字符串参数转换为目标类型:
* - "john" -> String (无需转换)
* - "25" -> Integer
* - "2023-01-01" -> Date
*/
/*
* 6. 属性设置
* 调用setter方法设置属性值:
* - user.setUsername("john")
* - user.setEmail("john@example.com")
* - user.setAge(25)
* - user.setBirthday(Date对象)
*/
/*
* 7. 数据验证
* 如果有@Valid注解,执行Bean Validation
*/
/*
* 8. 绑定结果
* 将绑定过程中的错误信息存储到BindingResult中
*/
}
}
2. 基本数据类型绑定
2.1 简单类型绑定
java
/**
* 简单数据类型绑定演示
*/
@Controller
@RequestMapping("/binding")
public class SimpleTypeBindingController {
/**
* 基本数据类型绑定
*/
@GetMapping("/simple")
public String simpleTypes(
String name, // 字符串类型
int age, // 基本类型int
Integer score, // 包装类型Integer
boolean active, // 布尔类型
Boolean enabled, // 包装类型Boolean
double salary, // 双精度浮点
BigDecimal amount, // 大数值类型
Model model) {
model.addAttribute("name", name);
model.addAttribute("age", age);
model.addAttribute("score", score);
model.addAttribute("active", active);
model.addAttribute("enabled", enabled);
model.addAttribute("salary", salary);
model.addAttribute("amount", amount);
return "binding/simple";
}
/**
* 数组类型绑定
*/
@GetMapping("/array")
public String arrayTypes(
String[] names, // 字符串数组
int[] scores, // 基本类型数组
Integer[] ages, // 包装类型数组
Model model) {
model.addAttribute("names", names);
model.addAttribute("scores", scores);
model.addAttribute("ages", ages);
return "binding/array";
}
/**
* 集合类型绑定
*/
@GetMapping("/collection")
public String collectionTypes(
@RequestParam List<String> hobbies, // List集合
@RequestParam Set<String> skills, // Set集合
@RequestParam Map<String, String> params, // Map集合
Model model) {
model.addAttribute("hobbies", hobbies);
model.addAttribute("skills", skills);
model.addAttribute("params", params);
return "binding/collection";
}
}
2.2 日期时间类型绑定
java
/**
* 日期时间类型绑定演示
*/
@Controller
@RequestMapping("/date")
public class DateTimeBindingController {
/**
* 全局日期格式配置
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
// 时间戳格式
SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
timestampFormat.setLenient(false);
binder.registerCustomEditor(Date.class, "createTime",
new CustomDateEditor(timestampFormat, false));
}
/**
* 日期类型绑定
*/
@PostMapping("/submit")
public String dateBinding(
@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday, // 使用注解格式化
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date createTime, // 时间戳格式
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate localDate, // ISO日期格式
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime localDateTime, // ISO时间格式
Model model) {
model.addAttribute("birthday", birthday);
model.addAttribute("createTime", createTime);
model.addAttribute("localDate", localDate);
model.addAttribute("localDateTime", localDateTime);
return "date/result";
}
/**
* 自定义日期转换器
*/
@Component
public static class StringToDateConverter implements Converter<String, Date> {
private static final String[] DATE_PATTERNS = {
"yyyy-MM-dd",
"yyyy/MM/dd",
"yyyy-MM-dd HH:mm:ss",
"yyyy/MM/dd HH:mm:ss"
};
@Override
public Date convert(String source) {
if (source == null || source.trim().isEmpty()) {
return null;
}
for (String pattern : DATE_PATTERNS) {
try {
SimpleDateFormat format = new SimpleDateFormat(pattern);
format.setLenient(false);
return format.parse(source.trim());
} catch (ParseException e) {
// 尝试下一个格式
}
}
throw new IllegalArgumentException("无法解析日期: " + source);
}
}
}
3. 复杂对象绑定
3.1 嵌套对象绑定
java
/**
* 复杂对象绑定演示
*/
@Controller
@RequestMapping("/complex")
public class ComplexObjectBindingController {
/**
* 嵌套对象绑定
*/
@PostMapping("/nested")
public String nestedObjectBinding(@ModelAttribute UserForm userForm,
BindingResult bindingResult,
Model model) {
/*
* 请求参数示例:
* username=john
* email=john@example.com
* profile.realName=John Doe
* profile.phone=1234567890
* profile.address.province=Beijing
* profile.address.city=Beijing
* profile.address.street=Chaoyang Road
*/
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "complex/form";
}
model.addAttribute("userForm", userForm);
return "complex/result";
}
/**
* 集合对象绑定
*/
@PostMapping("/list")
public String listObjectBinding(@ModelAttribute UserListForm form,
BindingResult bindingResult,
Model model) {
/*
* 请求参数示例:
* users[0].username=user1
* users[0].email=user1@example.com
* users[0].age=25
* users[1].username=user2
* users[1].email=user2@example.com
* users[1].age=30
*/
if (bindingResult.hasErrors()) {
return "complex/list-form";
}
model.addAttribute("form", form);
return "complex/list-result";
}
/**
* Map对象绑定
*/
@PostMapping("/map")
public String mapObjectBinding(@ModelAttribute UserMapForm form,
Model model) {
/*
* 请求参数示例:
* userMap['admin'].username=admin
* userMap['admin'].email=admin@example.com
* userMap['user'].username=user
* userMap['user'].email=user@example.com
*/
model.addAttribute("form", form);
return "complex/map-result";
}
}
/**
* 表单对象定义
*/
public class UserForm {
private String username;
private String email;
private UserProfile profile;
// getter/setter...
}
public class UserProfile {
private String realName;
private String phone;
private Address address;
// getter/setter...
}
public class Address {
private String province;
private String city;
private String street;
// getter/setter...
}
public class UserListForm {
private List<User> users = new ArrayList<>();
// getter/setter...
}
public class UserMapForm {
private Map<String, User> userMap = new HashMap<>();
// getter/setter...
}
4. 数据验证
4.1 Bean Validation基础
java
/**
* Bean Validation基础验证
*/
public class User {
@NotNull(message = "用户ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
private String password;
@Min(value = 18, message = "年龄不能小于18岁")
@Max(value = 100, message = "年龄不能大于100岁")
private Integer age;
@DecimalMin(value = "0.0", message = "工资不能为负数")
@DecimalMax(value = "999999.99", message = "工资不能超过999999.99")
@Digits(integer = 6, fraction = 2, message = "工资格式不正确")
private BigDecimal salary;
@Past(message = "生日必须是过去的日期")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@Future(message = "过期时间必须是将来的日期")
private Date expireTime;
@AssertTrue(message = "必须同意用户协议")
private Boolean agreeTerms;
@Valid // 级联验证
private UserProfile profile;
@Valid
@Size(min = 1, message = "至少需要一个角色")
private List<Role> roles;
// getter/setter...
}
/**
* 用户资料验证
*/
public class UserProfile {
@NotBlank(message = "真实姓名不能为空")
@Size(max = 50, message = "真实姓名长度不能超过50个字符")
private String realName;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@URL(message = "头像URL格式不正确")
private String avatar;
@Valid
private Address address;
// getter/setter...
}
4.2 分组验证
java
/**
* 分组验证演示
*/
public class User {
// 验证分组接口
public interface Create {}
public interface Update {}
public interface Delete {}
@NotNull(groups = {Update.class, Delete.class}, message = "更新和删除时ID不能为空")
private Long id;
@NotBlank(groups = {Create.class, Update.class}, message = "用户名不能为空")
@Size(min = 3, max = 20, groups = {Create.class, Update.class},
message = "用户名长度必须在3-20个字符之间")
private String username;
@NotBlank(groups = Create.class, message = "创建用户时密码不能为空")
@Size(min = 6, max = 20, groups = Create.class, message = "密码长度必须在6-20个字符之间")
private String password;
@Email(groups = {Create.class, Update.class}, message = "邮箱格式不正确")
private String email;
// getter/setter...
}
/**
* 控制器中使用分组验证
*/
@Controller
@RequestMapping("/user")
public class UserValidationController {
/**
* 创建用户 - 使用Create分组
*/
@PostMapping("/create")
public String createUser(@Validated(User.Create.class) @ModelAttribute User user,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "user/create";
}
userService.save(user);
return "redirect:/user/list";
}
/**
* 更新用户 - 使用Update分组
*/
@PostMapping("/update")
public String updateUser(@Validated(User.Update.class) @ModelAttribute User user,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "user/edit";
}
userService.update(user);
return "redirect:/user/list";
}
/**
* 删除用户 - 使用Delete分组
*/
@PostMapping("/delete")
public String deleteUser(@Validated(User.Delete.class) @ModelAttribute User user,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("error", "删除失败:参数错误");
return "redirect:/user/list";
}
userService.deleteById(user.getId());
redirectAttributes.addFlashAttribute("message", "删除成功");
return "redirect:/user/list";
}
}
4.3 自定义验证器
java
/**
* 自定义验证注解
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
@Documented
public @interface UniqueUsername {
String message() default "用户名已存在";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
/**
* 自定义验证器实现
*/
@Component
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
@Autowired
private UserService userService;
@Override
public void initialize(UniqueUsername constraintAnnotation) {
// 初始化方法,可以获取注解参数
}
@Override
public boolean isValid(String username, ConstraintValidatorContext context) {
if (username == null || username.trim().isEmpty()) {
return true; // 空值由@NotBlank验证
}
// 检查用户名是否已存在
return !userService.existsByUsername(username);
}
}
/**
* 复杂自定义验证注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordMatchValidator.class)
@Documented
public @interface PasswordMatch {
String message() default "密码和确认密码不匹配";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String password();
String confirmPassword();
}
/**
* 密码匹配验证器
*/
public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, Object> {
private String passwordField;
private String confirmPasswordField;
@Override
public void initialize(PasswordMatch constraintAnnotation) {
this.passwordField = constraintAnnotation.password();
this.confirmPasswordField = constraintAnnotation.confirmPassword();
}
@Override
public boolean isValid(Object obj, ConstraintValidatorContext context) {
try {
Object password = getFieldValue(obj, passwordField);
Object confirmPassword = getFieldValue(obj, confirmPasswordField);
if (password == null && confirmPassword == null) {
return true;
}
if (password != null && password.equals(confirmPassword)) {
return true;
}
// 自定义错误消息
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode(confirmPasswordField)
.addConstraintViolation();
return false;
} catch (Exception e) {
return false;
}
}
private Object getFieldValue(Object obj, String fieldName) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}
}
/**
* 使用自定义验证的表单对象
*/
@PasswordMatch(password = "password", confirmPassword = "confirmPassword")
public class UserRegistrationForm {
@UniqueUsername
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
private String password;
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
@Email(message = "邮箱格式不正确")
private String email;
// getter/setter...
}
5. 数据转换和格式化
5.1 类型转换器
java
/**
* 自定义类型转换器
*/
@Component
public class StringToUserConverter implements Converter<String, User> {
@Autowired
private UserService userService;
@Override
public User convert(String source) {
if (source == null || source.trim().isEmpty()) {
return null;
}
try {
Long userId = Long.parseLong(source);
return userService.findById(userId);
} catch (NumberFormatException e) {
// 尝试按用户名查找
return userService.findByUsername(source);
}
}
}
/**
* 枚举转换器
*/
@Component
public class StringToUserStatusConverter implements Converter<String, UserStatus> {
@Override
public UserStatus convert(String source) {
if (source == null || source.trim().isEmpty()) {
return null;
}
try {
// 尝试按序号转换
int ordinal = Integer.parseInt(source);
UserStatus[] values = UserStatus.values();
if (ordinal >= 0 && ordinal < values.length) {
return values[ordinal];
}
} catch (NumberFormatException e) {
// 尝试按名称转换
try {
return UserStatus.valueOf(source.toUpperCase());
} catch (IllegalArgumentException ex) {
// 忽略异常,返回null
}
}
return null;
}
}
/**
* 转换器配置
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToUserConverter());
registry.addConverter(new StringToUserStatusConverter());
registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
registry.addFormatter(new NumberStyleFormatter("#,##0.00"));
}
}
5.2 格式化器
java
/**
* 自定义格式化器
*/
@Component
public class MoneyFormatter implements Formatter<BigDecimal> {
@Override
public BigDecimal parse(String text, Locale locale) throws ParseException {
if (text == null || text.trim().isEmpty()) {
return null;
}
// 移除货币符号和千分位分隔符
String cleanText = text.replaceAll("[¥$,]", "");
try {
return new BigDecimal(cleanText);
} catch (NumberFormatException e) {
throw new ParseException("无法解析金额: " + text, 0);
}
}
@Override
public String print(BigDecimal money, Locale locale) {
if (money == null) {
return "";
}
NumberFormat formatter = NumberFormat.getCurrencyInstance(locale);
return formatter.format(money);
}
}
/**
* 使用格式化注解
*/
public class Product {
@NumberFormat(style = NumberFormat.Style.CURRENCY)
private BigDecimal price;
@NumberFormat(pattern = "#,##0.00")
private BigDecimal weight;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date createDate;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime updateTime;
// getter/setter...
}
6. 错误处理和国际化
6.1 验证错误处理
java
/**
* 验证错误处理
*/
@Controller
@RequestMapping("/validation")
public class ValidationErrorController {
/**
* 处理验证错误
*/
@PostMapping("/submit")
public String handleValidation(@Valid @ModelAttribute UserForm userForm,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
// 获取所有错误
List<ObjectError> allErrors = bindingResult.getAllErrors();
// 获取字段错误
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
// 获取全局错误
List<ObjectError> globalErrors = bindingResult.getGlobalErrors();
// 构建错误信息
Map<String, String> errorMap = new HashMap<>();
for (FieldError error : fieldErrors) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
model.addAttribute("errors", errorMap);
model.addAttribute("globalErrors", globalErrors);
return "validation/form";
}
return "validation/success";
}
/**
* AJAX验证错误处理
*/
@PostMapping("/ajax-submit")
@ResponseBody
public ResponseEntity<?> handleAjaxValidation(@Valid @RequestBody UserForm userForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String, Object> response = new HashMap<>();
Map<String, String> errors = new HashMap<>();
for (FieldError error : bindingResult.getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
response.put("success", false);
response.put("errors", errors);
return ResponseEntity.badRequest().body(response);
}
// 处理成功
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "提交成功");
return ResponseEntity.ok(response);
}
}
6.2 国际化支持
properties
# messages.properties (默认)
user.username.notblank=用户名不能为空
user.username.size=用户名长度必须在{min}-{max}个字符之间
user.email.email=邮箱格式不正确
user.age.min=年龄不能小于{value}岁
# messages_en.properties (英文)
user.username.notblank=Username cannot be blank
user.username.size=Username length must be between {min}-{max} characters
user.email.email=Email format is incorrect
user.age.min=Age cannot be less than {value} years old
# messages_zh_CN.properties (中文)
user.username.notblank=用户名不能为空
user.username.size=用户名长度必须在{min}-{max}个字符之间
user.email.email=邮箱格式不正确
user.age.min=年龄不能小于{value}岁
java
/**
* 国际化配置
*/
@Configuration
public class InternationalizationConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return resolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
return interceptor;
}
}
/**
* 使用国际化的验证
*/
public class User {
@NotBlank(message = "{user.username.notblank}")
@Size(min = 3, max = 20, message = "{user.username.size}")
private String username;
@Email(message = "{user.email.email}")
private String email;
@Min(value = 18, message = "{user.age.min}")
private Integer age;
// getter/setter...
}
7. 小结
本文深入介绍了SpringMVC的数据绑定和验证机制:
- 数据绑定原理:从HTTP参数到Java对象的自动转换过程
- 基本类型绑定:简单类型、数组、集合、日期时间的绑定
- 复杂对象绑定:嵌套对象、集合对象、Map对象的绑定
- 数据验证:Bean Validation、分组验证、自定义验证器
- 类型转换:自定义转换器和格式化器
- 错误处理:验证错误处理和国际化支持
掌握数据绑定和验证的关键点:
- 理解数据绑定的完整流程
- 正确使用各种验证注解
- 合理设计验证分组
- 实现自定义验证逻辑
- 处理验证错误和用户体验
🔗 下一篇预告
下一篇文章将介绍SpringMVC视图解析与模板引擎,学习如何处理视图渲染和模板技术的集成。
相关文章: