👋 大家好,我是 阿問學長
!專注于分享優質開源項目
解析、畢業設計項目指導
支持、幼小初高
的教輔資料
推薦等,歡迎關注交流!🚀
📖 本文概述
本文是SSM框架系列SpringMVC基礎篇的第三篇,將深入探討SpringMVC的數據綁定機制和數據驗證功能。通過詳細的代碼示例和最佳實踐,幫助讀者掌握數據綁定的原理和驗證框架的使用。
🎯 學習目標
- 深入理解SpringMVC的數據綁定原理
- 掌握各種數據類型的綁定方法
- 學會使用Bean Validation進行數據驗證
- 了解自定義驗證器的實現
- 掌握數據轉換和格式化技巧
1. 數據綁定原理
1.1 數據綁定概述
SpringMVC的數據綁定是將HTTP請求參數自動轉換為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 數據綁定流程
/*** 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 簡單類型綁定
/*** 簡單數據類型綁定演示*/
@Controller
@RequestMapping("/binding")
public class SimpleTypeBindingController {/*** 基本數據類型綁定*/@GetMapping("/simple")public String simpleTypes(String name, // 字符串類型int age, // 基本類型intInteger score, // 包裝類型Integerboolean active, // 布爾類型Boolean enabled, // 包裝類型Booleandouble 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 日期時間類型綁定
/*** 日期時間類型綁定演示*/
@Controller
@RequestMapping("/date")
public class DateTimeBindingController {/*** 全局日期格式配置*/@InitBinderpublic 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";}/*** 自定義日期轉換器*/@Componentpublic 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"};@Overridepublic 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 嵌套對象綁定
/*** 復雜對象綁定演示*/
@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基礎
/*** 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;@Validprivate Address address;// getter/setter...
}
4.2 分組驗證
/*** 分組驗證演示*/
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 自定義驗證器
/*** 自定義驗證注解*/
@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> {@Autowiredprivate UserService userService;@Overridepublic void initialize(UniqueUsername constraintAnnotation) {// 初始化方法,可以獲取注解參數}@Overridepublic 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;@Overridepublic void initialize(PasswordMatch constraintAnnotation) {this.passwordField = constraintAnnotation.password();this.confirmPasswordField = constraintAnnotation.confirmPassword();}@Overridepublic 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 類型轉換器
/*** 自定義類型轉換器*/
@Component
public class StringToUserConverter implements Converter<String, User> {@Autowiredprivate UserService userService;@Overridepublic 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> {@Overridepublic 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 {@Overridepublic 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 格式化器
/*** 自定義格式化器*/
@Component
public class MoneyFormatter implements Formatter<BigDecimal> {@Overridepublic 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);}}@Overridepublic 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 驗證錯誤處理
/*** 驗證錯誤處理*/
@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")@ResponseBodypublic 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 國際化支持
# 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}歲
/*** 國際化配置*/
@Configuration
public class InternationalizationConfig {@Beanpublic MessageSource messageSource() {ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();messageSource.setBasename("messages");messageSource.setDefaultEncoding("UTF-8");return messageSource;}@Beanpublic LocaleResolver localeResolver() {SessionLocaleResolver resolver = new SessionLocaleResolver();resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);return resolver;}@Beanpublic 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視圖解析與模板引擎,學習如何處理視圖渲染和模板技術的集成。
相關文章:
- 上一篇:SpringMVC請求處理與控制器
- 下一篇:SpringMVC視圖解析與模板引擎
- 返回目錄