數據綁定是將用戶輸入綁定到領域模型的一種特性。有了數據綁定,類型老是爲String的HTTP請求參數,可用於填充不一樣類型的對象屬性(或者說字段)。數據綁定使得form bean(在前面幾篇博客案例中的表單類ProductForm實例)變得多餘的。css
爲了高效的使用數據綁定,還須要Spring的表單標籤庫。本篇將重點介紹數據綁定和表單標籤庫,並提供範例,展現表單標籤庫中這些標籤的用法。html
基於HTTP的特性,全部HTTP請求參數的類型均爲字符串String類型。在博客Spring MVC -- 基於註解的控制器中的annotated1應用中,咱們使用表單提交產品的信息(name,description,price)。java
爲了獲取正確的產品價格,Spring MVC首先建立一個ProductForm實例,用來接受表單中提交的字段信息(ProductForm類字段都是String類型,與表單元素對應),而後在方法中建立一個Product實例用於保存產品信息(其中商品價格price是BigDecimal)類型。這裏將annotated1應用中ProductController類中的saveProduct()方法的部分代碼複製過來:web
@RequestMapping(value="/save-product") public String saveProduct(ProductForm productForm, Model model) { logger.info("saveProduct called"); // no need to create and instantiate a ProductForm // create Product Product product = new Product(); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); try { product.setPrice(new BigDecimal(productForm.getPrice())); } catch (NumberFormatException e) { } // add product model.addAttribute("product", product); return "ProductDetails"; }
之因此須要解析ProductForm中的price屬性,是由於它是一個String,卻須要用BigDecimal來填充Product的price。有了數據綁定,就能夠用下面的代碼取代上面的saveProduct()方法部分:spring
@RequestMapping(value="/save-product") public String saveProduct(Product product, Model model)
有了數據綁定,就再也不須要ProductForm類,也不須要解析Product對象的price屬性。apache
數據綁定的另外一個好處就是:當輸入驗證失敗時(或者類型轉換失敗),它會從新生成一個HTML表單。手工編寫HTML代碼時,必須記着用戶以前輸入的值,從新填充輸入字段。有了Spring數據綁定和表單標籤庫後,它們就會替你完成這些工做。瀏覽器
表單標籤庫中包含了能夠用在jsp頁面渲染HTML元素的標籤,爲了使用這些標籤,必須在jsp頁面的開頭處聲明這個tablib指令:spring-mvc
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
注意:這裏須要引入taglibs-standard-impl-1.2.5.jar和taglibs-standard-spec-1.2.5.jar包。下載地址:http://tomcat.apache.org/download-taglibs.cgi。tomcat
下表展現了表單標籤庫中的標籤。服務器
在接下來的小節中,將逐一介紹這些標籤。並會展現一個示例應用程序,展現了數據綁定結合表單標籤庫的使用方法。
標籤 | 描述 |
form | 渲染表元素 |
input | 渲染<input type="text"/>元素 |
password | 渲染<input type="password"/>元素 |
hidden | 渲染<input type="hidden"/>元素 |
textarea | 渲染textarea元素 |
checkbox | 渲染一個<input type="checkbox"/>元素 |
checkboxs | 渲染多個<input type="checkboxs"/>元素 |
radiobutton | 渲染一個<input type="radio"/>元素 |
radiobuttons | 渲染多個<input type="radio"/>元素 |
select | 渲染一個選擇元素 |
option | 渲染一個可選元素 |
options | 渲染一個可選元素列表 |
errors | 在span元素中渲染字段錯誤 |
表單標籤用於渲染HTML表單元素。要使用渲染一個表單輸入字段的任何其它標籤,必須有一個form標籤。表單標籤的屬性以下表:
屬性 | 描述 |
acceptCharset | 定義服務器接受的字符編碼列表 |
modelAttribute | 暴露表單對象之模型(org.springframework.ui.Model)屬性的名字,默認爲command |
cssClass | 定義要應用到被渲染form元素的CSS類 |
cssStyle | 定義要應用到被渲染form元素的CSS樣式 |
htmlEscape | 接受true或者false,表示被渲染的值是否應該進行HTML轉義 |
modelAttribute | 暴露表單支持對象的模型屬性名稱,默認是command |
表中的全部標籤都是可選的。這個表中沒有包含HTML屬性,如method和action。
modelAttribute屬性或許是其中最重要的屬性,由於它定義了模型屬性的名稱,該屬性保存一個表單支持對象(form backing object),表單支持對象的屬性將用於填充所生成的表單。
若是modelAttribute屬性存在,則必須在返回包含該表單的視圖的請求處理方法中添加相應的模型屬性。在後面會介紹一個tags-demo應用,其中表單標籤是在BookAddForm.jsp中定義的:
<form:form modelAttribute="book" action="save-book" method="post"> ... </form:form>
form標籤中指定了modelAttribute="book",也就是說這個表單中各個字段信息將會保存在Model實例的"book"屬性中。
BookController類中的inputBook()方法,是返回BookAddForm.jsp的請求處理方法。下面就是inputBook()方法:
@RequestMapping(value = "/input-book") public String inputBook(Model model) { List<Category> categories = bookService.getAllCategories(); model.addAttribute("categories", categories); model.addAttribute("book", new Book()); return "BookAddForm"; }
因爲Spring MVC會在每個請求處理方法被調用時建立一個Model實例,所以當訪問/input-book時,將會建立一個Model實例,咱們能夠向其增長鬚要顯示在視圖中的屬性。
此處向Model中添加了一個"book"屬性,保存一個Book對象,這個"book"屬性就是form標籤中modelAttribute所指定的。若是沒有Model屬性,BookAddForm.jsp頁面就會拋出異常,由於表單標籤沒法找到在其modelAttribute屬性中指定的form backing object。
此外,通常來講,仍然須要action和method屬性。這兩個屬性都是HTML屬性,所以不在上面的表中。
input標籤渲染<input type="text"/>元素。這個標籤最重要的屬性是path,它將這個input標籤綁定到表單支持對象的一個屬性。例如:若隨附form標籤的modelAttribute屬性值爲book,而且input標籤的path屬性值爲isbn,那麼input標籤將被綁定到Book對象的isbn屬性。
下表展現了input標籤的屬性,表中的屬性都是可選的,其中不包含HTML屬性。
屬性 | 描述 |
cssClass | 定義要應用到被渲染input 元素的CSS類 |
cssStyle | 定義要應用到被渲染input 元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染input元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
htmlEscape | 接受true或者false,表示被渲染的值是否應該進行HTML轉義 |
path | 要綁定的屬性路徑 |
舉個例子,下面這個input標籤被綁定到表單支持對象的isbn屬性:
<form:input id="isbn" path="isbn" cssErrorClass="errorBox"/>
它將會被渲染成下面的<input/>元素:
<input type="text" id="isbn" name="isbn">
cssErrorClass屬性不起做用,除非isbn屬性中有輸入驗證錯誤,而且採用同一個表單從新顯示用戶輸入,在這種狀況下,input標籤就會被渲染成下面這個input元素:
<input type="text" id="isbn" name="isbn" class="errorBox">
input標籤也能夠綁定到嵌入對象的屬性。例如,下面的input標籤綁定到表單支持對象的catagory屬性的id屬性,其中catagory屬性是Category類型:
<form:input path="category.id" >
password標籤渲染<input type="password"/>元素,其屬性見下表,password標籤和input標籤類似,只不過它有一個showPassword屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染input 元素的CSS類 |
cssStyle | 定義要應用到被渲染input 元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染input元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
htmlEscape | 接受true或者false,表示被渲染的值是否應該進行HTML轉義 |
path | 要綁定的屬性路徑 |
showPassword | 表示應該顯示或遮蓋密碼,默認值是false |
表中的全部屬性都是可選的,這個表中不包含HTML屬性。下面是一個password標籤的例子:
<form:password id="pwd" path="password" cssClass="normal"/>
hidden標籤渲染<input type="hidden"/>元素,其屬性見下表。hidden標籤和input標籤類似,只不過它沒有可視的外觀,所以不支持cssClass和cssStyle屬性:
屬性 | 描述 |
htmlEscape | 接受true或者false,表示被渲染的值是否應該進行HTML轉義 |
path | 要綁定的屬性路徑 |
表中的全部屬性都是可選的,這個表中不包含HTML屬性。下面是一個hidden標籤的例子:
<form:hidden path="productId"/>
hidden標籤渲染HTML的<textarea ></textarea >元素。textarea實際上就是支持多行輸入的一個input元素。textarea標籤的屬性見下表,表中的全部屬性都是可選的,其中不包含HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染textarea 元素的CSS類 |
cssStyle | 定義要應用到被渲染textarea 元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染textarea 元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
htmlEscape | 接受true或者false,表示被渲染的值是否應該進行HTML轉義 |
path | 要綁定的屬性路徑 |
例如,下面的textarea標籤就是被綁定到表單支持對象的note屬性:
<form:textarea path="note" tabindex="4" rows="5" cols="80"/>
checkbox標籤渲染<input type="checkbox"/>元素,其屬性見下表。表中的全部屬性都是可選的,其中不包含HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染input 元素的CSS類 |
cssStyle | 定義要應用到被渲染input 元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染input元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
htmlEscape | 接受true或者false,表示是否應該對被被渲染的(多個)值是否應該進行HTML轉義 |
path | 要綁定的屬性路徑 |
value | 要做爲標籤用於被渲染複選框的值 |
例如,下面的checkbox標籤就是被綁定到表單支持對象的outOffStock屬性:
<form:checkbox path="outOfStock" value="Out of Stock"/>
radiobutton標籤渲染<input type="radio"/>元素,其屬性見下表。表中的全部屬性都是可選的,其中不包含HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染input 元素的CSS類 |
cssStyle | 定義要應用到被渲染input 元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染input元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
htmlEscape | 接受true或者false,表示是否應該對被被渲染的(多個)值是否應該進行HTML轉義 |
path | 要綁定的屬性路徑 |
value | 要做爲標籤用於被渲染複選框的值 |
例如,下面的radiobutton標籤就是被綁定到表單支持對象的newsletter屬性:
Computing Now <form:radiobutton path="newsletter" value="Computing Now"/> <br/> Modern Health <form:radiobutton path="newsletter" value="Modern Health"/>
checkboxs標籤渲染多個<input type="checkbox"/>元素,其屬性見下表。表中的全部屬性都是可選的,其中不包含HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染input 元素的CSS類 |
cssStyle | 定義要應用到被渲染input 元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染input元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
delimiter | 定義兩個input元素之間的分隔符,默認沒有分隔符 |
element | 給每一個被渲染的input元素都定義一個HTML元素,默認是「span」 |
htmlEscape | 接受true或者false,表示是否應該對被被渲染的(多個)值是否應該進行HTML轉義 |
items | 用於生產input元素的對象的Collection、Map或者Array |
itemLabel | item屬性中定義的Collection、Map、或者Array中的對象屬性,爲每一個inpput元素提供標籤(用於顯示) |
itemValue | item屬性中定義的Collection、Map、或者Array中的對象屬性,爲每一個inpput元素提供值 |
path | 要綁定的屬性路徑 |
例如,下面的checkboxs標籤將Model屬性categories(這是一個List<Category>類型)內容渲染爲複選框。checkboxs標籤容許進行多個選擇:
<form:checkboxs path="category" items="${categories}"/>
radiobuttons標籤渲染多個<input type="radio"/>元素,其屬性見下表。表中的全部屬性都是可選的,其中不包含HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染input 元素的CSS類 |
cssStyle | 定義要應用到被渲染input 元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染input元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
delimiter | 定義兩個input元素之間的分隔符,默認沒有分隔符 |
element | 給每一個被渲染的input元素都定義一個HTML元素,默認是「span」 |
htmlEscape | 接受true或者false,表示是否應該對被被渲染的(多個)值是否應該進行HTML轉義 |
items | 用於生產input元素的對象的Collection、Map或者Array |
itemLabel | item屬性中定義的Collection、Map、或者Array中的對象屬性,爲每一個inpput元素提供標籤(用於顯示) |
itemValue | item屬性中定義的Collection、Map、或者Array中的對象屬性,爲每一個inpput元素提供值 |
path | 要綁定的屬性路徑 |
例如,下面的radiobuttons標籤將Model屬性categories(這是一個List<Category>類型)內容渲染成單選按鈕。每次只能選擇一個單選按鈕:
<form:radiobuttons path="category" items="${categories}"/>
select標籤渲染一個HTML的<select></select>元素。被渲染元素的選項可能來自賦予其items屬性的一個Collection、Map、Array,或者來自一個嵌套的option或者options標籤。select標籤的屬性見下表,表中的全部屬性都是可選的,其中不包含HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染select元素的CSS類 |
cssStyle | 定義要應用到被渲染select元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染select元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
htmlEscape | 接受true或者false,表示是否應該對被被渲染的(多個)值是否應該進行HTML轉義 |
items | 用於生產input元素的對象的Collection、Map或者Array |
itemLabel | item屬性中定義的Collection、Map、或者Array中的對象屬性,爲每一個option元素提供標籤(用於顯示) |
itemValue | item屬性中定義的Collection、Map、或者Array中的對象屬性,爲每一個option元素提供值 |
path | 要綁定的屬性路徑 |
items屬性特別有用,由於它能夠綁定到對象的Collection、Map、Array,爲select元素生成選項。
例如:下面的select標籤綁定到表單支持對象的category屬性的id屬性。它的選項來自Model屬性categories(這是一個List<Category>類型)。每一個選項對應categories 中的一個元素,每一個選項的標籤對應元素的name屬性,每一個選項的值對應元素的value屬性。
<form:select id="category" path="category.id" items="${categories}" itemLabel="name" itemValue="id"/>
option標籤渲染select元素中使用的一個HTML的<option></option>元素,其屬性見下表,表中的全部屬性都是可選的,其中不包含HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染option元素的CSS類 |
cssStyle | 定義要應用到被渲染option元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染option元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
htmlEscape | 接受true或者false,表示是否應該對被被渲染的(多個)值是否應該進行HTML轉義 |
例如,下面是一個option標籤的範例:
<!-- option標籤,這裏form:option並不會起做用 --> <p> <label>option: </label> <form:select id="option" path="option.id" items="${categories}" itemLabel="name" itemValue="id"> <form:option value="0">--please select--</form:option> </form:select> </p>
這個代碼片斷是渲染一個select元素,其選項來自Model屬性categories,以及option標籤;可是當同時設置option標籤和select標籤的items設時,option標籤不會其任何做用。能夠更改爲以下代碼:
<!-- option標籤 --> <p> <label>option: </label> <form:select path="option.id" > <form:option value="0">--please select--</form:option> <form:option value="1">--please select 1--</form:option> </form:select> </p>
options標籤生成一個HTML的<option></option>...<option></option>元素列表,其屬性見下表,表中的全部屬性都是可選的,其中不包含HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染option元素的CSS類 |
cssStyle | 定義要應用到被渲染option元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染option元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
htmlEscape | 接受true或者false,表示是否應該對被被渲染的(多個)值是否應該進行HTML轉義 |
items | 用於生產input元素的對象的Collection、Map或者Array |
itemLabel | item屬性中定義的Collection、Map、或者Array中的對象屬性,爲每一個option元素提供標籤(用於顯示) |
itemValue | item屬性中定義的Collection、Map、或者Array中的對象屬性,爲每一個option元素提供值 |
例如,下面是一個options標籤的範例:
<!-- options標籤 --> <p> <label>options: </label> <form:select path="options.id" > <form:option value="0">--please select--</form:option> <form:options items="${categories}" itemLabel="name" itemValue="id"/> </form:select> </p>
errors標籤渲染一個或者多個HTML的<span></span>元素,每一個span元素中都包含一個字段錯誤。這個標籤能夠用於顯示一個特定的字段錯誤,或者全部字段錯誤。
errors標籤的屬性見下表,表中的全部屬性都是可選的,其中不包含可能在HTML的span元素中出現的HTML屬性:
屬性 | 描述 |
cssClass | 定義要應用到被渲染span元素的CSS類 |
cssStyle | 定義要應用到被渲染span元素的CSS樣式 |
cssErrorClass | 定義要應用到被渲染span元素的CSS類,若是bound屬性中包含錯誤,則覆蓋cssClass屬性值 |
delimiter | 定義多個錯誤消息的分隔符 |
element | 定義一個包含錯誤消息的HTML元素 |
htmlEscape | 接受true或者false,表示是否應該對被被渲染的(多個)值是否應該進行HTML轉義 |
path | 要綁定的錯誤對象路徑 |
例如,下面這個errors標籤顯示了全部字段錯誤:
<form:errors path="*"/>
下面的 errors標籤顯示了一個與表單支持對象的author屬性相關的字段錯誤:
<form:errors path="author"/>
errors標籤的使用較爲複雜,咱們會在Spring MVC -- 轉換器和格式化中展現一個具體案例。
本節將建立一個tags-demo的應用程序,在表單標籤庫中利用標籤進行數據綁定。這個應用圍繞domain包Book類進行,這個類中有幾個屬性,包括一個類型爲Category的category屬性,Category有id和name兩個屬性。
這個應用程序具備如下幾個動做:列出書目、添加新書、編輯書目。
domain包表明領域層,domain包中包含Book類和Category類:
Book類:
package domain; import java.io.Serializable; import java.math.BigDecimal; public class Book implements Serializable { private static final long serialVersionUID = 1520961851058396786L; private long id; private String isbn; private String title; private Category category; private String author; private BigDecimal price; public Book() { } public Book(long id, String isbn, String title, Category category, String author, BigDecimal price) { this.id = id; this.isbn = isbn; this.title = title; this.category = category; this.author = author; this.price = price; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } }
Category類:
package domain; import java.io.Serializable; public class Category implements Serializable { private static final long serialVersionUID = 5658716793957904104L; private int id; private String name; public Category() { } public Category(int id, String name) { this.id = id; this.name = name; } 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; } }
tags-demo應用提供了一個控制器:BookController類。它容許用戶建立新書目、更新新書的詳細信息、並在系統中列出全部書目:
package controller; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import service.BookService; import domain.Book; import domain.Category; @Controller public class BookController { //使用@Autowired和@Service進行依賴注入 BookService:爲Book提供服務 @Autowired private BookService bookService; private static final Log logger = LogFactory.getLog(BookController.class); //訪問URL:/input-book @RequestMapping(value = "/input-book") public String inputBook(Model model) { List<Category> categories = bookService.getAllCategories(); model.addAttribute("categories", categories); model.addAttribute("book", new Book()); return "BookAddForm"; } //路徑變量 訪問URL:/edit-book/id @RequestMapping(value = "/edit-book/{id}") public String editBook(Model model, @PathVariable long id) { List<Category> categories = bookService.getAllCategories(); model.addAttribute("categories", categories); Book book = bookService.get(id); model.addAttribute("book", book); return "BookEditForm"; } //訪問URL:/save-book 每次調用該請求處理方法時,會建立Book實例,接收表單提交的數據。 //同時經過指定@ModelAttribute註解,代表將Book實例做爲Model對象的屬性 @RequestMapping(value = "/save-book") public String saveBook(@ModelAttribute Book book) { Category category = bookService.getCategory(book.getCategory().getId()); book.setCategory(category); bookService.save(book); //重定向 return "redirect:/list-books"; } @RequestMapping(value = "/update-book") public String updateBook(@ModelAttribute Book book) { Category category = bookService.getCategory(book.getCategory().getId()); book.setCategory(category); bookService.update(book); return "redirect:/list-books"; } @RequestMapping(value = "/list-books") public String listBooks(Model model) { logger.info("list-book"); List<Book> books = bookService.getAllBooks(); model.addAttribute("books", books); //請求轉發 能夠在BookList.jsp中訪問到books return "BookList"; } }
BookController依賴BookService進行一些後臺處理。@Autowired註解用於給BookController注入一個BookService實例:
@Autowired private BookService bookService;
tags-demo應用提供了一個BookService接口和實現類BookServiceImpl:
BookService接口:
package service; import java.util.List; import domain.Book; import domain.Category; public interface BookService { List<Category> getAllCategories(); Category getCategory(int id); List<Book> getAllBooks(); Book save(Book book); Book update(Book book); Book get(long id); long getNextId(); }
BookServiceImpl類:
package service; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; import domain.Book; import domain.Category; @Service public class BookServiceImpl implements BookService { /* * this implementation is not thread-safe */ private List<Category> categories; private List<Book> books; public BookServiceImpl() { categories = new ArrayList<Category>(); Category category1 = new Category(1, "Computer"); Category category2 = new Category(2, "Travel"); Category category3 = new Category(3, "Health"); categories.add(category1); categories.add(category2); categories.add(category3); books = new ArrayList<Book>(); books.add(new Book(1L, "9781771970273", "Servlet & JSP: A Tutorial (2nd Edition)", category1, "Budi Kurniawan", new BigDecimal("54.99"))); books.add(new Book(2L, "9781771970297", "C#: A Beginner's Tutorial (2nd Edition)", category1, "Jayden Ky", new BigDecimal("39.99"))); } @Override public List<Category> getAllCategories() { return categories; } @Override public Category getCategory(int id) { for (Category category : categories) { if (id == category.getId()) { return category; } } return null; } @Override public List<Book> getAllBooks() { return books; } @Override public Book save(Book book) { book.setId(getNextId()); books.add(book); return book; } @Override public Book get(long id) { for (Book book : books) { if (id == book.getId()) { return book; } } return null; } @Override public Book update(Book book) { int bookCount = books.size(); for (int i = 0; i < bookCount; i++) { Book savedBook = books.get(i); if (savedBook.getId() == book.getId()) { books.set(i, book); return book; } } return book; } @Override public long getNextId() { // needs to be locked long id = 0L; for (Book book : books) { long bookId = book.getId(); if (bookId > id) { id = bookId; } } return id + 1; } }
BookServiceImpl類中包含了一個Book對象的List和一個Category對象的List。這兩個List都是在實例化類時生成的。這個類中還包含了獲取全部書目、獲取單個書目、以及添加和更新書目的方法。
tags-demo應用由兩個配置文件:
部署描述符(web.xml文件):
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
Spring MVC配置文件(springmvc-servlet.xml):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="controller"/> <context:component-scan base-package="service"/> <mvc:annotation-driven/> <mvc:resources mapping="/css/*" location="/css/"/> <mvc:resources mapping="/*.html" location="/"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
tags-demo的Spring MVC配置文件中有兩個<component-scan/>元素,一個用於掃描控制器類@Controller,另外一個用於掃描服務類@Service。
tags-demo中使用了3個jsp頁面,分別是BookAddForm.jsp、BookEditForm.jsp、BookList.jsp。其中前兩個頁面使用的是來自表單標籤庫的標籤。
BookAddForm.jsp:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML> <html> <head> <title>Add Book Form</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <form:form modelAttribute="book" action="save-book" method="post"> <fieldset> <legend>Add a book</legend> <p> <label for="category">Category: </label> <form:select id="category" path="category.id" items="${categories}" itemLabel="name" itemValue="id"/> </p> <p> <label for="title">Title: </label> <form:input id="title" path="title"/> </p> <p> <label for="author">Author: </label> <form:input id="author" path="author"/> </p> <p> <label for="isbn">ISBN: </label> <form:input id="isbn" path="isbn"/> </p> <p> <label for="price">Price: </label> $<form:input id="price" path="price"/> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4"> <input id="submit" type="submit" tabindex="5" value="Add Book"> </p> </fieldset> </form:form> </div> </body> </html>
BookEditForm.jsp:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <title>Edit Book Form</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <c:url var="formAction" value="/update-book"/> <form:form modelAttribute="book" action="${formAction}" method="post"> <fieldset> <legend>Edit a book</legend> <form:hidden path="id"/> <p> <label for="category">Category: </label> <form:select id="category" path="category.id" items="${categories}" itemLabel="name" itemValue="id"/> </p> <p> <label for="title">Title: </label> <form:input id="title" path="title"/> </p> <p> <label for="author">Author: </label> <form:input id="author" path="author"/> </p> <p> <label for="isbn">ISBN: </label> <form:input id="isbn" path="isbn"/> </p> <p> <label for="price">Price: </label> $<form:input id="price" path="price"/> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4"> <input id="submit" type="submit" tabindex="5" value="Update Book"> </p> </fieldset> </form:form> </div> </body> </html>
注意,在BookEditForm.jsp頁面中,表單的action屬性爲<c:url/>的值:
<c:url var="formAction" value="/update-book"/> <form:form commandName="book" action="${formAction}" method="post">
這是由於表單須要定位/update-book映射(根路徑/:指的是當前項目路徑http://localhost:8008/tags-demo/),若是給action屬性一個靜態值「update-book」是有問題的。緣由以下:
1)假設圖書id做爲請求參數發送到編輯圖書頁面,則頁面URL將以下所示:
http://domain/context/edit-book?id=1
該表單將提交到http://domain/context/update-book,這個訪問路徑是沒有問題的。
2)可是若是圖書id做爲路徑發送(程序中採用這種方式),頁面網址將以下所示:
http://domain/context/edit-book/id
那麼表單將被提交到:
http://domain/context/edit-book/update-book
所以,應該使用<c:url/>來確保表單目標路徑始終一致,不管網頁網址如何。遺憾的是,表單的action屬性不能取<c:url>。所以,您須要建立一個變量fromAction並從action屬性引用它。
BookList.jsp:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML> <html> <head> <title>Book List</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <h1>Book List</h1> <a href="<c:url value="/input-book"/>">Add Book</a> <table> <tr> <th>Category</th> <th>Title</th> <th>ISBN</th> <th>Author</th> <th>Price</th> <th> </th> </tr> <c:forEach items="${books}" var="book"> <tr> <td>${book.category.name}</td> <td>${book.title}</td> <td>${book.isbn}</td> <td>${book.author}</td> <td>${book.price}</td> <td><a href="edit-book/${book.id}">Edit</a></td> </tr> </c:forEach> </table> </div> </body> </html>
main.css:
#global { text-align: left; border: 1px solid #dedede; background: #efefef; width: 560px; padding: 20px; margin: 100px auto; } form { font:100% verdana; min-width: 500px; max-width: 600px; width: 560px; } form fieldset { border-color: #bdbebf; border-width: 3px; margin: 0; } legend { font-size: 1.3em; } form label { width: 250px; display: block; float: left; text-align: right; padding: 2px; } table td { border: 1px solid #dedede; background: MistyRose; /* for web colors visit http://en.wikipedia.org/wiki/Web_colors */ } #buttons { text-align: right; } #errors, li { color: red; } .error { color: red; font-size: 9pt; }
部署項目,並在瀏覽器輸入:
http://localhost:8008/tags-demo/list-books
第一次啓動這個應用程序時顯示的數目列表:
單擊「Add Book」連接添加數目
單擊"Edit"來編輯書目:
在tags-demo應用中,咱們並無使用到表單標籤庫中的全部標籤,這一節,將在tags-demo應用的基礎上演示更多表單標籤的使用。
咱們須要建立兩個jsp頁面:FormDemo.jsp、ShowMessge.jsp一樣都放在/WEB-INF/jsp路徑下:
FormDemo.jsp代碼以下:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style type="text/css">@import url("<c:url value="/css/main2.css"/>");</style> <title>表單標籤庫測試</title> </head> <body> <div id="global"> <c:url var="formAction" value="/show-message"/> <form:form modelAttribute="formElements" action="${formAction}" method="post"> <fieldset> <legend>表單標籤庫測試</legend> <p style="color:red" align="center">這些表單標籤會把其value值提交到formElements對象path指定的屬性上</p> <!-- input標籤 --> <p> <label>input:</label> <form:input id="input" path="input"/> </p> <!-- password標籤 --> <p> <label>password:</label> <form:password id="password" path="password" maxlength="20"/> </p> <!-- hidden標籤 --> <p> <label>hidden:</label> <form:password id="hidden" path="hidden"/> </p> <!-- textarea標籤 --> <p> <label>textarea:</label> <form:textarea id="textarea" path="textarea" rows="5" /> </p> <!-- checkbox標籤 --> <p> <label>checkbox:</label> <form:checkbox path="checkbox" value="checkbox-checked"/>蘋果 <form:checkbox path="checkbox2" value="checkbox2-checked"/>香蕉 </p> <!-- radiobutton標籤 --> <p> <label>radiobutton:</label> <form:radiobutton path="radiobutton" value="radiobutton-checked"/>籃球 <form:radiobutton path="radiobutton2" value="radiobutton2-checked "/>足球 </p> <!-- checkboxs標籤 能夠多選 --> <p> <label>checkboxs:</label> <form:checkboxes path="checkboxs" items="${categories}" itemLabel="name" itemValue="id" /> </p> <!-- radiobuttons標籤 單選--> <p> <label>radiobuttons:</label> <form:radiobuttons path="radiobuttons" items="${categories}" itemLabel="name" itemValue="id"/> </p> <!-- select標籤 注意:不能將標籤直接綁定到select上,由於select是Category類型,沒法將String類型轉換爲Category類型--> <p> <label>select: </label> <form:select id="select" path="select.id" items="${categories}" itemLabel="name" itemValue="id"/> </p> <!-- option標籤,這裏form:option並不會起做用 --> <p> <label>option: </label> <form:select id="option" path="option.id" items="${categories}" itemLabel="name" itemValue="id"> <form:option value="0">--please select--</form:option> </form:select> </p> <!-- option標籤 --> <p> <label>option: </label> <form:select path="option2.id" > <form:option value="0">--please select--</form:option> <form:option value="1">--please select 1--</form:option> </form:select> </p> <!-- options標籤 --> <p> <label>options: </label> <form:select path="options.id" > <form:option value="0">--please select--</form:option> <form:options items="${categories}" itemLabel="name" itemValue="id"/> </form:select> </p> <!-- errors標籤的使用將會在下一篇博客介紹 --> <p id="buttons"> <input id="reset" type="reset" > <input id="submit" type="submit" value="提交"> </p> </fieldset> </form:form> </div> </body> </html>
FormDemo.jsp頁面中使用了大量的表單標籤,咱們將表單綁定到了Model的formElements屬性上。
在BookController類中追加了/form-demo的請求訪問函數formDemo():
//訪問URL:/form-demo @RequestMapping("form-demo") public String formDemo(Model model) { List<Category> categories = bookService.getAllCategories(); model.addAttribute("categories", categories); model.addAttribute("formElements",new FormElements()); return "FormDemo"; }
當在瀏覽器中訪問以下URL時:
http://localhost:8008/tags-demo/form-demo
將會執行formDemo()函數,該函數在Model對象中加入了"categories"和"formElements"屬性,而後將FormDemo.jsp頁面返回,這樣在該jsp頁面中就能夠訪問到Model對象中的"categories"和"formElements"屬性。
其中「formElements」屬性保存的是FormElements對象,"categories"屬性保存的是List<Category>對象。
FormElements類(位於domain包下):
package domain; import java.util.*; //用來測試全部的表單標籤 public class FormElements { public String input; public String password; public String hidden; public String textarea; public String checkbox; public String checkbox2; public String checkboxs; public String radiobutton; public String radiobutton2; public String radiobuttons; public Category select; public Category option; public Category option2; public Category getOption2() { return option2; } public void setOption2(Category option2) { this.option2 = option2; } public Category options; public String getInput() { return input; } public void setInput(String input) { this.input = input; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getHidden() { return hidden; } public void setHidden(String hidden) { this.hidden = hidden; } public String getTextarea() { return textarea; } public void setTextarea(String textarea) { this.textarea = textarea; } public String getCheckbox() { return checkbox; } public void setCheckbox(String checkbox) { this.checkbox = checkbox; } public String getCheckbox2() { return checkbox2; } public void setCheckbox2(String checkbox2) { this.checkbox2 = checkbox2; } public String getCheckboxs() { return checkboxs; } public void setCheckboxs(String checkboxs) { this.checkboxs = checkboxs; } public String getRadiobutton() { return radiobutton; } public void setRadiobutton(String radiobutton) { this.radiobutton = radiobutton; } public String getRadiobutton2() { return radiobutton2; } public void setRadiobutton2(String radiobutton2) { this.radiobutton2 = radiobutton2; } public String getRadiobuttons() { return radiobuttons; } public void setRadiobuttons(String radiobuttons) { this.radiobuttons = radiobuttons; } public Category getSelect() { return select; } public void setSelect(Category select) { this.select = select; } public Category getOption() { return option; } public void setOption(Category option) { this.option = option; } public Category getOptions() { return options; } public void setOptions(Category options) { this.options = options; } public String getErrors() { return errors; } public void setErrors(String errors) { this.errors = errors; } public String errors; public FormElements() { } }
經過查看頁面的源代碼,會發現jsp頁面已經被解釋成html,而且表單元素的name值實際就是jsp中咱們設置的path值:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style type="text/css">@import url("/tags-demo/css/main2.css");</style> <title>表單標籤庫測試</title> </head> <body> <div id="global"> <form id="formElements" action="/tags-demo/show-message" method="post"> <fieldset> <legend>表單標籤庫測試</legend> <p style="color:red" align="center">這些表單標籤會把其value值提交到formElements對象path指定的屬性上</p> <!-- input標籤 --> <p> <label>input:</label> <input id="input" name="input" type="text" value=""/> </p> <!-- password標籤 --> <p> <label>password:</label> <input id="password" name="password" type="password" value="" maxlength="20"/> </p> <!-- hidden標籤 --> <p> <label>hidden:</label> <input id="hidden" name="hidden" type="password" value=""/> </p> <!-- textarea標籤 --> <p> <label>textarea:</label> <textarea id="textarea" name="textarea" rows="5"> </textarea> </p> <!-- checkbox標籤 --> <p> <label>checkbox:</label> <input id="checkbox1" name="checkbox" type="checkbox" value="checkbox-checked"/><input type="hidden" name="_checkbox" value="on"/>蘋果 <input id="checkbox21" name="checkbox2" type="checkbox" value="checkbox2-checked"/><input type="hidden" name="_checkbox2" value="on"/>香蕉 </p> <!-- radiobutton標籤 --> <p> <label>radiobutton:</label> <input id="radiobutton1" name="radiobutton" type="radio" value="radiobutton-checked"/>籃球 <input id="radiobutton21" name="radiobutton2" type="radio" value="radiobutton2-checked "/>足球 </p> <!-- checkboxs標籤 能夠多選 --> <p> <label>checkboxs:</label> <span><input id="checkboxs1" name="checkboxs" type="checkbox" value="1"/><label for="checkboxs1">Computer</label></span><span><input id="checkboxs2" name="checkboxs" type="checkbox" value="2"/><label for="checkboxs2">Travel</label></span><span><input id="checkboxs3" name="checkboxs" type="checkbox" value="3"/><label for="checkboxs3">Health</label></span><input type="hidden" name="_checkboxs" value="on"/> </p> <!-- radiobuttons標籤 單選--> <p> <label>radiobuttons:</label> <span><input id="radiobuttons1" name="radiobuttons" type="radio" value="1"/><label for="radiobuttons1">Computer</label></span><span><input id="radiobuttons2" name="radiobuttons" type="radio" value="2"/><label for="radiobuttons2">Travel</label></span><span><input id="radiobuttons3" name="radiobuttons" type="radio" value="3"/><label for="radiobuttons3">Health</label></span> </p> <!-- select標籤 注意:不能將標籤直接綁定到select上,由於select是Category類型,沒法將String類型轉換爲Category類型--> <p> <label>select: </label> <select id="select" name="select.id"><option value="1">Computer</option><option value="2">Travel</option><option value="3">Health</option></select> </p> <!-- option標籤,這裏form:option並不會起做用 --> <p> <label>option: </label> <select id="option" name="option.id"><option value="1">Computer</option><option value="2">Travel</option><option value="3">Health</option></select> </p> <!-- option標籤 --> <p> <label>option: </label> <select id="option.id" name="option.id"> <option value="0" selected="selected">--please select--</option> <option value="1">--please select 1--</option> </select> </p> <!-- options標籤 --> <p> <label>options: </label> <select id="options.id" name="options.id"> <option value="0" selected="selected">--please select--</option> <option value="1">Computer</option><option value="2">Travel</option><option value="3">Health</option> </select> </p> <p id="buttons"> <input id="reset" type="reset" > <input id="submit" type="submit" value="提交"> </p> </fieldset> </form> </div> </body> </html>
在表單中輸入上圖所示的內容,提交表單,將會跳轉到http://localhost:8008/tags-demo/show-message頁面。/show-message對應着BookController類中請求訪問函數showMessage():
//訪問URL:/show-message @RequestMapping("show-message") public String showMessage(@ModelAttribute("formElements") FormElements formElements,Model model) { return "ShowMessge"; }
執行formDemo()函數,而後將ShowMessge.jsp頁面返回:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> <title>Insert title here</title> </head> <div id="global"> <form:form modelAttribute="formElements" method="post"> <fieldset> <legend>表單標籤庫測試</legend> <!-- input標籤 --> <p> <label>input:</label> ${formElements.input} </p> <!-- password標籤 --> <p> <label>password:</label> ${formElements.password} </p> <!-- hidden標籤 --> <p> <label>hidden:</label> ${formElements.hidden} </p> <!-- textarea標籤 --> <p> <label>textarea:</label> ${formElements.textarea} </p> <!-- checkbox標籤 --> <p> <label>checkbox:</label> ${formElements.checkbox} ${formElements.checkbox2} </p> <!-- radiobutton標籤 --> <p> <label>radiobutton:</label> ${formElements.radiobutton} ${formElements.radiobutton2} </p> <!-- checkboxs標籤 能夠多選 --> <p> <label>checkboxs:</label> ${formElements.checkboxs} </p> <!-- radiobuttons標籤 單選--> <p> <label>radiobuttons:</label> ${formElements.radiobuttons} </p> <!-- select標籤 --> <p> <label>select: </label> ${formElements.select.id} </p> <!-- option標籤 --> <p> <label>option: </label> ${formElements.option.id} </p> <p> <label>option: </label> ${formElements.option2.id} </p> <!-- options標籤 --> <p> <label>options: </label> ${formElements.options.id} </p> </fieldset> </form:form> </div> </html>
該jsp頁面將會顯示FormDemo.jsp頁面中表單中輸入的內容:
此外程序中還涉及到了如下文件:
main2.css:
#global { text-align: left; border: 1px solid #dedede; background: #efefef; width: 600px; padding: 20px; margin: 100px auto; } form { font:100% verdana; width: 600px; } form fieldset { border-color: #bdbebf; border-width: 3px; margin: 0; } legend { font-size: 1.3em; } form label { width: 250px; text-align: right; padding: 2px; } table td { border: 1px solid #dedede; background: MistyRose; /* for web colors visit http://en.wikipedia.org/wiki/Web_colors */ } #buttons { text-align: right; } #errors, li { color: red; } .error { color: red; font-size: 9pt; }
參考文獻
[1]領域模型淺析
[2]jsp項目使用jstl(c標籤)及jstl.jar和standard.jar
[3]Unable to find setter method for attribute: [commandName]
[4]Spring MVC學習指南