Spring MVC不只是在架構上改變了項目,使代碼變得可複用、可維護與可擴展,其實在功能上也增強了很多。 驗證與文件上傳是許多項目中不可缺乏的一部分。在項目中驗證很是重要,首先是安全性考慮,如防止注入攻擊,XSS等;其次還能夠確保數據的完整性,如輸入的格式,內容,長度,大小等。Spring MVC可使用驗證器Validator與JSR303完成後臺驗證功能。這裏也會介紹方便的前端驗證方法。css
Spring MVC驗證器Validator是一個接口,經過實現該接口來定義對實體對象的驗證,接口以下所示:html
package org.springframework.validation; /** * Spring MVC內置的驗證器接口 */ public interface Validator { /** * 是否能夠驗證該類型 */ boolean supports(Class<?> clazz); /** * 執行驗證 target表示要驗證的對象 error表示錯誤信息 */ void validate(Object target, Errors errors); }
package com.zhangguo.springmvc51.entities; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; /** * 產品驗證器 * */ 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類型 Product entity=(Product)target; //若是產品名稱爲空或爲空格,使用工具類 ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required", "產品名稱必須填寫"); //價格,手動判斷 if(entity.getPrice()<0){ errors.rejectValue("price", "product.price.gtZero", "產品價格必須大於等於0"); } //產品類型必須選擇 if(entity.getProductType().getId()==0){ errors.rejectValue("productType.id", "product.productType.id.required", "請選擇產品類型"); } } }
ValidationUtils是一個工具類,中間有一些方能夠用於判斷內容是否有誤。前端
// 新增保存,若是新增成功轉回列表頁,若是失敗回新增頁,保持頁面數據 @RequestMapping("/addSave") public String addSave(Model model, Product product, BindingResult bindingResult) { // 建立一個產品驗證器 ProductValidator validator = new ProductValidator(); // 執行驗證,將驗證的結果給bindingResult,該類型繼承Errors validator.validate(product, bindingResult); // 得到全部的字段錯誤信息,非必要 for (FieldError fielderror : bindingResult.getFieldErrors()) { System.out.println(fielderror.getField() + "," + fielderror.getCode() + "," + fielderror.getDefaultMessage()); } // 是否存在錯誤,若是沒有,執行添加 if (!bindingResult.hasErrors()) { // 根據類型的編號得到類型對象 product.setProductType(productTypeService.getProductTypeById(product.getProductType().getId())); productService.addProduct(product); return "redirect:/"; } else { // 與form綁定的模型 model.addAttribute("product", product); // 用於生成下拉列表 model.addAttribute("productTypes", productTypeService.getAllProductTypes()); return "product/add"; } }
注意在參數中增長了一個BindingResult類型的對象,該類型繼承自Errors,得到綁定結果,承載錯誤信息,該對象中有一些方法能夠得到完整的錯誤信息,可使用hasErrors方法判斷是否產生了錯誤。java
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <link href="styles/main.css" type="text/css" rel="stylesheet" /> <title>新增產品</title> </head> <body> <div class="main"> <h2 class="title"><span>新增產品</span></h2> <form:form action="addSave" modelAttribute="product"> <fieldset> <legend>產品</legend> <p> <label for="name">產品名稱:</label> <form:input path="name"/> <form:errors path="name" cssClass="error"></form:errors> </p> <p> <label for="title">產品類型:</label> <form:select path="productType.id"> <form:option value="0">--請選擇--</form:option> <form:options items="${productTypes}" itemLabel="name" itemValue="id"/> </form:select> <form:errors path="productType.id" cssClass="error"></form:errors> </p> <p> <label for="price">產品價格:</label> <form:input path="price"/> <form:errors path="price" cssClass="error"></form:errors> </p> <p> <input type="submit" value="保存" class="btn out"> </p> </fieldset> </form:form> <p style="color: red">${message}</p> <p> <a href="<c:url value="/" />" class="abtn out">返回列表</a> </p> </div> </body> </html>
發生錯誤時解析的結果:jquery
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <link href="styles/main.css" type="text/css" rel="stylesheet" /> <title>新增產品</title> </head> <body> <div class="main"> <h2 class="title"><span>新增產品</span></h2> <form id="product" action="addSave" method="post"> <fieldset> <legend>產品</legend> <p> <label for="name">產品名稱:</label> <input id="name" name="name" type="text" value=""/> <span id="name.errors" class="error">產品名稱必須填寫</span> </p> <p> <label for="title">產品類型:</label> <select id="productType.id" name="productType.id"> <option value="0" selected="selected">--請選擇--</option> <option value="11">數碼電子</option><option value="21">鞋帽服飾</option><option value="31">圖書音像</option><option value="41">五金家電</option><option value="51">生鮮水果</option> </select> <span id="productType.id.errors" class="error">請選擇產品類型</span> </p> <p> <label for="price">產品價格:</label> <input id="price" name="price" type="text" value="-10.0"/> <span id="price.errors" class="error">產品價格必須大於等於0</span> </p> <p> <input type="submit" value="保存" class="btn out"> </p> </fieldset> </form> <p style="color: red"></p> <p> <a href="/SpringMVC51/" class="abtn out">返回列表</a> </p> </div> </body> </html>
控制檯輸出:git
JSR是Java Specification Requests的縮寫,意思是Java 規範提案。是指向JCP(Java Community Process)提出新增一個標準化技術規範的正式請求。任何人均可以提交JSR,以向Java平臺增添新的API和服務。JSR已成爲Java界的一個重要標準。https://jcp.org/en/home/indexgithub
JSR 303 – Bean Validation 是一個數據驗證的規範。JSR303只是一個標準,是一驗證規範,對這個標準的實現有:web
hibernate-validator,Apache BVal等。這裏咱們使用hibernate-validator實現校驗。正則表達式
修改配置pom.xml配置文件,添加依賴。spring
<!--JSR303 Bean校驗--> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.2.Final</version> </dependency>
在bean中設置驗證規則,示例代碼以下:
package com.zhangguo.springmvc51.entities; import java.io.Serializable; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import org.hibernate.validator.constraints.Range; /** * 產品 */ public class Product implements Serializable { private static final long serialVersionUID = 1L; /* * 編號 */ private int id; /* * 名稱 */ @Size(min=1,max=50,message="名稱長度必須介於{2}-{1}之間") @Pattern(regexp="^[\\w\\u4e00-\\u9fa5]{0,10}$",message="格式錯誤,必須是字母數字與中文") private String name; /* * 價格 */ @Range(min=0,max=1000000,message="價格只容許在{2}-{1}之間") private double price; /* * 產品類型 */ private ProductType productType; public Product() { productType=new ProductType(); } public Product(String name, double price) { super(); this.name = name; this.price = price; } public Product(int id, String name, double price, ProductType type) { super(); this.id = id; this.name = name; this.price = price; this.productType = type; } @Override public String toString() { return "編號(id):" + this.getId() + ",名稱(name):" + this.getName() + ",價格(price):" + this.getPrice() + ",類型(productType.Name):" + this.getProductType().getName(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public ProductType getProductType() { return productType; } public void setProductType(ProductType productType) { this.productType = productType; } }
更多的驗證註解以下所示:
@Null 驗證對象是否爲null
@NotNull 驗證對象是否不爲null, 沒法查檢長度爲0的字符串
@NotBlank 檢查約束字符串是否是Null還有被Trim的長度是否大於0,只對字符串,且會去掉先後空格.
@NotEmpty 檢查約束元素是否爲NULL或者是EMPTY.
@AssertTrue 驗證 Boolean 對象是否爲 true
@AssertFalse 驗證 Boolean 對象是否爲 false
@Size(min=, max=) 驗證對象(Array,Collection,Map,String)長度是否在給定的範圍以內
@Length(min=, max=) Validates that the annotated string is between min and max included.
@Past 驗證 Date 和 Calendar 對象是否在當前時間以前
@Future 驗證 Date 和 Calendar 對象是否在當前時間以後
@Pattern 驗證 String 對象是否符合正則表達式的規則
建議使用在Stirng,Integer類型,不建議使用在int類型上,由於表單值爲「」時沒法轉換爲int,但能夠轉換爲String爲"",Integer爲null
@Min 驗證 Number 和 String 對象是否大等於指定的值
@Max 驗證 Number 和 String 對象是否小等於指定的值
@DecimalMax 被標註的值必須不大於約束中指定的最大值. 這個約束的參數是一個經過BigDecimal定義的最大值的字符串表示.小數存在精度
@DecimalMin 被標註的值必須不小於約束中指定的最小值. 這個約束的參數是一個經過BigDecimal定義的最小值的字符串表示.小數存在精度
@Digits 驗證 Number 和 String 的構成是否合法
@Digits(integer=,fraction=) 驗證字符串是不是符合指定格式的數字,interger指定整數精度,fraction指定小數精度。
@Range(min=, max=) 檢查被註解對象的值是否處於min與max之間,閉區間,包含min與max值
@Range(min=10000,max=50000,message="必須介於{2}-{1}之間")
@Valid 遞歸的對關聯對象進行校驗, 若是關聯對象是個集合或者數組,那麼對其中的元素進行遞歸校驗,若是是一個map,則對其中的值部分進行校驗.(是否進行遞歸驗證),該註解使用在Action的參數上。
@CreditCardNumber信用卡驗證
@Email 驗證是不是郵件地址,若是爲null,不進行驗證,算經過驗證。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
在須要使用Bean驗證的參數對象上註解@Valid,觸發驗證,示例代碼以下:
package com.zhangguo.springmvc51.controllers; import java.util.List; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.zhangguo.springmvc51.entities.Product; import com.zhangguo.springmvc51.services.ProductService; import com.zhangguo.springmvc51.services.ProductTypeService; @Controller @RequestMapping("/goods") public class GoodsController { @Autowired ProductService productService; @Autowired ProductTypeService productTypeService; // 新增,渲染出新增界面 @RequestMapping("/add") public String add(Model model) { // 與form綁定的模型 model.addAttribute("product", new Product()); // 用於生成下拉列表 model.addAttribute("productTypes", productTypeService.getAllProductTypes()); return "product/addGoods"; } // 新增保存,若是新增成功轉回列表頁,若是失敗回新增頁,保持頁面數據 @RequestMapping("/addGoodsSave") public String addSave(Model model, @Valid Product product, BindingResult bindingResult) { // 是否存在錯誤,若是沒有,執行添加 if (!bindingResult.hasErrors()) { // 根據類型的編號得到類型對象 product.setProductType(productTypeService.getProductTypeById(product.getProductType().getId())); productService.addProduct(product); return "redirect:/"; } else { // 與form綁定的模型 model.addAttribute("product", product); // 用於生成下拉列表 model.addAttribute("productTypes", productTypeService.getAllProductTypes()); return "product/addGoods"; } } @RequestMapping("/products") @ResponseBody public List<Product> getProduct(){ return productService.getAllProducts(); } }
這裏與Spring MVC Validator基本一致,在product目錄下新增一個名爲addGoods.jsp的頁面,腳本以下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <link href="<c:url value="/styles/main.css" />" type="text/css" rel="stylesheet" /> <title>新增產品</title> </head> <body> <div class="main"> <h2 class="title"><span>新增產品</span></h2> <form:form action="addGoodsSave" modelAttribute="product"> <fieldset> <legend>產品</legend> <p> <label for="name">產品名稱:</label> <form:input path="name"/> <form:errors path="name" cssClass="error"></form:errors> </p> <p> <label for="title">產品類型:</label> <form:select path="productType.id"> <form:option value="0">--請選擇--</form:option> <form:options items="${productTypes}" itemLabel="name" itemValue="id"/> </form:select> <form:errors path="productType.id" cssClass="error"></form:errors> </p> <p> <label for="price">產品價格:</label> <form:input path="price"/> <form:errors path="price" cssClass="error"></form:errors> </p> <p> <input type="submit" value="保存" class="btn out"> </p> </fieldset> </form:form> <p style="color: red">${message}</p> <p> <a href="<c:url value="/" />" class="abtn out">返回列表</a> </p> </div> </body> </html>
小結:從上面的示例能夠看出這種驗證更加方便直觀,一次定義反覆使用,以編輯更新時驗證一樣可使用;另外驗證的具體信息能夠存放在配置文件中,如message.properties,這樣便於國際化與修改。
jquery.validate是基於jQuery的一個B/S客戶端驗證插件,藉助jQuery的優點,咱們能夠迅速驗證一些常見的輸入,大大提升了開發效率,下面是不少年前本人作的學習筆記:
注意:validate只是使驗證變得方便,簡單,本質仍是使用js,不論多麼強大的js驗證,當用戶把js禁用或使用機器直接發起請求時都不能確保數據的完整性,全部不要把但願寄託在客戶端驗證,我的認爲每個客戶端驗證都要服務器進行再次驗證。
在Spring MVC中有兩種實現上傳文件的辦法,第一種是Servlet3.0如下的版本經過commons-fileupload與commons-io完成的通用上傳,第二種是Servlet3.0以上的版本的Spring內置標準上傳,不需藉助第3方組件。通用上傳也兼容Servlet3.0以上的版本。
由於須要藉助第三方上傳組件commons-fileupload與commons-io,因此要修改pom.xml文件添加依賴,依賴的內容以下:
<!--文件上傳 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
依賴成功後的結果:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>上傳文件</title> </head> <body> <h2>上傳文件</h2> <form action="fileSave" method="post" enctype="multipart/form-data"> <p> <label for="files">文件:</label> <input type="file" name="files" id="files" multiple="multiple" /> </p> <p> <button>提交</button> </p> <p> ${message} </p> </form> </body> </html>
若是有成功上傳,頁面中有幾個關鍵點要注意:method的值必爲Post;enctype必須爲multipart/form-data,該類型的編碼格式專門用於二進制數據類型;上傳表單元素必須擁有name屬性;
默認情總下Spring MVC對文件上傳的視圖內容是不能解析的,要配置一個特別的解析器解析上傳的內容,修改springmvc-servlet.xml配置文件,增長以下配置內容:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8" /> <property name="maxUploadSize" value="10485760000" /> <property name="maxInMemorySize" value="40960" /> </bean>
增長了一個類型爲CommonsMultipartResolver類型的解析器,各屬性的意義:
defaultEncoding:默認編碼格式
maxUploadSize:上傳文件最大限制(字節byte)
maxInMemorySize:緩衝區大小
當Spring的前置中心控制器檢查到客戶端發送了一個多分部請求,定義在上下文中的解析器將被激活並接手處理。解析器將當前的HttpServletRequest包裝成一個支持多部分文件上傳的MultipartHttpServletRequest對象。在控制器中能夠得到上傳的文件信息。
CommonsMultipartResolver用於通用的文件上傳,支持各類版本的Servlet。
StandardServletMultipartResolver用於Servlet3.0以上的版本上傳文件。
package com.zhangguo.springmvc51.controllers; import java.io.File; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.multipart.MultipartFile; @Controller @RequestMapping("/up") public class UpFileController { @RequestMapping("/file") public String file(Model model){ return "up/upfile"; } @RequestMapping(value="/fileSave",method=RequestMethod.POST) public String fileSave(Model model,MultipartFile[] files,HttpServletRequest request) throws Exception{ //文件存放的位置 String path=request.getServletContext().getRealPath("/files"); for (MultipartFile file : files) { System.out.println(file.getOriginalFilename()); System.out.println(file.getSize()); System.out.println("--------------------------"); File tempFile=new File(path, file.getOriginalFilename()); file.transferTo(tempFile); } System.out.println(path); return "up/upfile"; } }
注意這裏定義的是一個數組,能夠接受多個文件上傳,若是單文件上傳能夠修改成MultipartFile類型;另外上傳文件的細節在這裏並無花時間處理,好比文件重名的問題,路徑問題,關於重名最簡單的辦法是從新命名爲GUID文件名。
Servlet3.0以上的版本再也不須要第三方組件Commons.io和commons-fileupload,上傳的方式與4.1提到基本同樣,但配置稍有區別,可使用@MultipartConfig註解在Servlet上進行配置上傳,也能夠在web.xml上進行配置。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!--Servlet3.0以上文件上傳配置 --> <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> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
file-size-threshold:數字類型,當文件大小超過指定的大小後將寫入到硬盤上。默認是0,表示全部大小的文件上傳後都會做爲一個臨時文件寫入到硬盤上。
location:指定上傳文件存放的目錄。當咱們指定了location後,咱們在調用Part的write(String fileName)方法把文件寫入到硬盤的時候能夠,文件名稱能夠不用帶路徑,可是若是fileName帶了絕對路徑,那將以fileName所帶路徑爲準把文件寫入磁盤,不建議指定。
max-file-size:數值類型,表示單個文件的最大大小。默認爲-1,表示不限制。當有單個文件的大小超過了max-file-size指定的值時將拋出IllegalStateException異常。
max-request-size:數值類型,表示一次上傳文件的最大大小。默認爲-1,表示不限制。當上傳時全部文件的大小超過了max-request-size時也將拋出IllegalStateException異常。
把pom.xml中對文件上傳第三方的依賴刪除,刪除依賴保存後的結果:
將原有的文件上傳通用解析器更換爲標準解析器,修改後的配置以下所示:
<!--文件上傳解析器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean>
定義了一個標準的文件上傳解析器,更多屬性能夠查看這個類的源碼。這步很是關鍵,不然上傳會失敗。另外id不要換成別的名稱,更換後可能會上傳失敗。
在views/up/下定義名稱爲file3.jsp文件,內容以下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>上傳文件 - Servlet3.0</title> </head> <body> <h2>上傳文件 - Servlet3.0</h2> <form action="file3Save" method="post" enctype="multipart/form-data"> <p> <label for="files">文件:</label> <input type="file" name="files" id="files" multiple="multiple" /> </p> <p> <button>提交</button> </p> <p> ${message} </p> </form> </body> </html>
multiple="multiple"這個屬性是HTML5新增長的屬性,一些舊版的瀏覽器可能不支持,使用JavaScript能夠處理一下。
在UpFileController中定義兩個action,一個叫file3用於展現上傳頁面,一個叫file3Save用於處理上傳文,代碼以下:
package com.zhangguo.springmvc51.controllers; import java.io.File; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.multipart.MultipartFile; @Controller @RequestMapping("/up") public class UpFileController { @RequestMapping("/file") public String file(Model model){ return "up/upfile"; } @RequestMapping(value="/fileSave",method=RequestMethod.POST) public String fileSave(Model model,MultipartFile[] files,HttpServletRequest request) throws Exception{ //文件存放的位置 String path=request.getServletContext().getRealPath("/files"); for (MultipartFile file : files) { System.out.println(file.getOriginalFilename()); System.out.println(file.getSize()); System.out.println("--------------------------"); File tempFile=new File(path, file.getOriginalFilename()); file.transferTo(tempFile); } System.out.println(path); return "up/upfile"; } @RequestMapping("/file3") public String file3(Model model){ return "up/upfile3"; } @RequestMapping(value="/file3Save",method=RequestMethod.POST) public String file3Save(Model model,MultipartFile[] files,HttpServletRequest request) throws Exception{ //文件存放的位置 String path=request.getSession().getServletContext().getRealPath("/files"); System.out.println(path); String msg=""; for (MultipartFile file : files) { //保存文件 File tempFile=new File(path, file.getOriginalFilename()); file.transferTo(tempFile); msg+="<img src='../files/"+file.getOriginalFilename()+"' width='200' />"; } model.addAttribute("message", msg); return "up/upfile3"; } }