Spring MVC 不僅優化架構分層,內建校驗與文件上傳支持更大幅提升開發效率。本文系統講解 Spring Validator 接口、JSR-303 Bean Validation (Hibernate Validator)、前端 jQuery Validate 三層校驗體系,以及 Commons FileUpload 與 Servlet 3.0 原生 兩種文件上傳方案。
public class ProductValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Product.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Product p = (Product) target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required", "產品名稱必填");
if (p.getPrice() < 0) errors.rejectValue("price", "price.negative", "價格不能為負");
if (p.getProductType().getId() == 0) errors.rejectValue("productType.id", "type.required", "請選擇分類");
}
}
@RequestMapping("/addSave")
public String addSave(Model model, Product product, BindingResult bindingResult) {
new ProductValidator().validate(product, bindingResult);
if (!bindingResult.hasErrors()) {
productService.addProduct(product);
return "redirect:/";
}
model.addAttribute("product", product); // 回顯數據
model.addAttribute("productTypes", productTypeService.getAll());
return "product/add";
}
<form:input path="name"/> <form:errors path="name" cssClass="error"/> <!-- 渲染為 <span class="error">錯誤信息</span> -->
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.2.Final</version> </dependency>
public class Product implements Serializable {
@Size(min=1, max=50, message="名稱長度 1-50")
@Pattern(regexp="^[\\w\\u4e00-\\u9fa5]{0,10}$", message="只能字母數字中文")
private String name;
@Range(min=0, max=1000000, message="價格 0-100萬")
private double price;
@Valid // 級聯校驗關聯對象
private ProductType productType;
// getter/setter
}
| 分類 | 註解 | 說明 |
|---|---|---|
| 空值 | @NotNull | 不為 null(不檢查空字符串/集合) |
| @NotEmpty | 不為 null 且 size > 0 (集合/數組/字符串/Map) | |
| @NotBlank | 字符串 trim 後 length > 0 | |
| @Null | 必須為 null | |
| 布爾 | @AssertTrue / @AssertFalse | Boolean 必須 true/false |
| 長度 | @Size(min,max) | 字符串/集合/Map/數組長度範圍 |
| @Length(min,max) | 僅字符串長度 | |
| 日期 | @Past / @Future | 日期必須在當前時間之前/之後 |
| 正則 | @Pattern(regexp=,message=) | 字符串匹配正則 |
| 數值 | @Min / @Max | 數值下界/上界 |
| @DecimalMin / @DecimalMax | BigDecimal 精度比較 | |
| @Digits(integer=,fraction=) | 整數位/小數位限制 | |
| @Range(min,max) | 數值閉區間 [min,max] | |
| @Valid | 級聯校驗嵌套對象/集合元素 | |
| 其他 | @Email @CreditCardNumber @URL @ScriptAssert | 特定格式校驗 |
@RequestMapping("/addGoodsSave")
public String addSave(Model model, @Valid Product product, BindingResult bindingResult) {
if (!bindingResult.hasErrors()) { ... }
// 失敗自動回顯,錯誤信息已綁定
}
rules: {name: {required: true, maxlength: 50}}messages: {name: {required: "請輸入名稱"}}errorClass: "error", errorElement: "span"$.validator.addMethod("mobile", ...)原則:前端校驗提升體驗、減輕服務器壓力;後端校驗保證安全、數據完整性,**兩者缺一不可**。
| 維度 | Commons FileUpload (Servlet < 3.0) | Servlet 3.0 原生 (@MultipartConfig) |
|---|---|---|
| 依賴 | commons-fileupload + commons-io | 無第三方,容器內建 |
| 配置 | XML 定義 CommonsMultipartResolver bean | Servlet 類上 @MultipartConfig 或 web.xml <multipart-config> |
| 解析器 Bean | CommonsMultipartResolver | StandardServletMultipartResolver (id 必須為 multipartResolver) |
| Controller 參數 | MultipartFile[] files / MultipartHttpServletRequest | 相同,MultipartFile 來自 servlet-api |
| 適用版本 | 所有 Servlet 版本 | Tomcat 7+ / Jetty 8+ |
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8"/> <property name="maxUploadSize" value="104857600"/> <!-- 100MB --> <property name="maxInMemorySize" value="40960"/> <!-- 40KB 閾值 --> </bean>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<multipart-config>
<max-file-size>5242880</max-file-size> <!-- 5MB -->
<max-request-size>20971520</max-request-size> <!-- 20MB -->
<file-size-threshold>0</file-size-threshold>
</multipart-config>
</servlet>
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
@RequestMapping(value="/fileSave", method=RequestMethod.POST)
public String upload(MultipartFile[] files, HttpServletRequest request) {
String path = request.getServletContext().getRealPath("/files");
for (MultipartFile file : files) {
if (!file.isEmpty()) {
String original = file.getOriginalFilename();
File dest = new File(path, UUID.randomUUID() + original.substring(original.lastIndexOf('.')));
file.transferTo(dest);
}
}
return "success";
}
contentType + 文件頭魔數(如 JPEG: FF D8 FF),雙重校驗。maxUploadSize / max-file-size。