- 内容提要
- 作者简介
- 译者简介
- 前言
- HTTP
- Servlet 和 JSP
- 下载 Spring 或使用 STS 与 Maven/Gradle
- 手动下载 Spring
- 使用 STS 和 Maven/Gradle
- 下载 Spring 源码
- 本书内容简介
- 下载示例应用
- 第 1 章Spring 框架
- 第 2 章模型 2 和 MVC 模式
- 第 3 章Spring MVC 介绍
- 第 4 章基于注解的控制器
- 第 5 章数据绑定和表单标签库
- 第 6 章转换器和格式化
- 第 7 章验证器
- 第 8 章表达式语言
- 第 9 章JSTL
- 第 10 章国际化
- 第 11 章上传文件
- 第 12 章下载文件
- 第 13 章应用测试
- 附录 A Tomcat
- 附录 B Spring Tool Suite 和 Maven
- 附录 C Servlet
- 附录 D JavaServer Pages
- 附录 E 部署描述符
5.3 数据绑定范例
在表单标签库中利用标签进行数据绑定的例子,参见 tags-demo 应用程序。这个范例围绕 domain 类 Book 进行。这个类中有几个属性,包括一个类型为 Category 的 category 属性。Category 有 id 和 name 两个属性。
这个应用程序允许列出书目、添加新书,以及编辑书目。
5.3.1 目录结构
图 5.1 中展示了 tags-demo 的目录结构。
图 5.1 tags-demo 的目录结构
5.3.2 Domain 类
Book 类和 Category 类是这个应用程序中的 domain 类,它们分别如清单 5.1 和清单 5.2 所示。
清单 5.1 Book 类
package domain;
import java.math.BigDecimal;
import java.io.Serializable;
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
private long id;
private String isbn;
private String title;
private Category category;
private String author;
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;
}
// get and set methods not shown
}
清单 5.2 Category 类
package domain;
import java.io.Serializable;
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
public Category() {
}
public Category(int id, String name) {
this.id = id;
this.name = name;
}
// get and set methods not shown
}
5.3.3 Controller 类
下面的范例为 Book 提供了一个控制器:BookController 类。它允许用户创建新书目、更新书的详细信息,并在系统中列出所有书目。清单 5.3 中展示了 BookController 类。
清单 5.3 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 domain.Book;
import domain.Category;
import service.BookService;
@Controller
public class BookController {
@Autowired
private BookService bookService;
private static final Log logger =
LogFactory.getLog(BookController.class);
@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";
}
@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";
}
@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-book";
}
@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-book";
}
@RequestMapping(value = "/list-book")
public String listBooks(Model model) {
logger.info("listBooks");
List<Book> books = bookService.getAllBooks();
model.addAttribute("books", books);
return "BookList";
}
}
BookController 依赖 BookService 进行一些后台处理。@Autowired 注解用于给 Book Controller 注入一个 BookService 实现。
@Autowired
private BookService bookService;
5.3.4 Service 类
清单 5.4 和清单 5.5 分别展示了 BookService 接口和 BookServiceImpl 类。顾名思义,BookServiceImpl 就是实现 BookService。
清单 5.4 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();
}
清单 5.5 BookServiceImpl 类
package service;
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 都是在实例化类时生成的。这个类中还包含了获取所有书目、获取单个书目,以及添加和更新书目的方法。
5.3.5 配置文件
清单 5.6 展示了 tags-demo 中的 Spring MVC 配置文件。
清单 5.6 Spring MVC 配置文件
< ?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"/>
... < !-- other elements are not shown -->
< /beans>
component-scan bean 使得 app05a.controller 包和 app05a.service 包得以被扫描。
5.3.6 视图
tags-demo 中使用的 3 个 JSP 页面如清单 5.7、清单 5.8 和清单 5.9 所示。BookAddForm.jsp 和 BookEditForm.jsp 页面中使用的是来自表单标签库的标签。
清单 5.7 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="/book_input"/>">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="book_edit/${book.id}">Edit</a></td>
</tr>
</c:forEach>
</table>
</div>
</body>
</html>
清单 5.8 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 commandName="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 id="buttons">
<input id="reset" type="reset">
<input id="submit" type="submit"
value="Add Book">
</p>
</fieldset>
</form:form>
</div>
</body>
</html>
清单 5.9 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 commandName="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 id="buttons"> < input id="reset" type="reset"> < input id="submit" type="submit" 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 映射,并且给 action 属性一个静态值“update-book”是有问题的。如果图书 ID 作为请求参数发送到编辑图书页面,则页面 URL 将如下所示:
http://domain/context/edit-book?id=1
该表单将正确提交到 http://domain/context/update-book。
但是,如果图书 ID 作为路径变量发送,页面网址将如下所示:
http://domain/context/edit-book/id
并且表单将被提交到:
http://domain/context/edit-book/update-book
因此,您应该使用<c:url/>来确保表单目标始终正确,无论网页网址如何。遗憾的是,表单的 action 属性不能取<c:url/>。因此,您需要创建一个变量 formAction 并从 action 属性引用它。
5.3.7 测试应用
要想测试这个应用程序范例,请打开以下网页:
http://localhost:8080/tags-demo/list-books
图 5.2 展示了第一次启动这个应用程序时显示的书目列表。
单击“Add Book”链接添加书目,或者单击书籍详情右侧的“Edit”链接来编辑书目。
图 5.3 展示了“Add a book”表单。图 5.4 中展示了“Edit a book”表单。
图 5.2 书目列表
图 5.3 Add a book 表单
图 5.4 Edit a book 表单
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论