数据绑定即将用户输入绑定到领域对象,它主要有两个好处:
①数据绑定可以直接绑定到模型对象,不必使用属性全是字符串的Form对象。
②输入验证失败时,会重新生成一个HTML表单回填。
对于第二点(回填)还不是很理解,暂且记下来。
数据绑定于JSP页面上常常会用到Spring MVC的表单标签库,在Struts2里也是有这样的标签库,在Spring MVC中使用的是:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
还原了一下书上的这个例子,这个例子同时将前面学的Spring MVC的知识一起用了起来,做完之后对Spring MVC有了一个完整的把握。
类似Struts2地,Spring MVC的编码也是ISO-8859-1
,可以在web.xml
前部添加一个编码过滤器:
<filter>
<filter-name>encodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>encodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
即可解决这一问题。
这部分和Spring MVC没关系,只是书上的例子用到了方便的JSTL。JSP标准标签库需要添加两个jar包,在这里下载解压就能找到,放在lib目录下并引入即可:
除了添加了前面的编码过滤器之外,这两个文件的配置都和前面学的时候一样,没改动。
领域对象类都应当实现Serializable
接口,必须具有无参构造器,必须为所有属性提供getter和setter方法,后面的Category类也是这样。
private long id;//图书号
private String isbn;//isbn号
private String title;//书名
private Category category;//类别
private String author;//作者
private int id;//书籍分类号
private String name;//类别名称
package org.service;
import org.domain.Book;
import org.domain.Category;
import java.util.List;
//服务层接口,提供用于为Book服务的方法
public interface BookService {
//获取所有的书籍种类
List getAllCategories();
//按书籍种类号获取种类对象
Category getCategory(int id);
//获取所有的书
List getAllBooks();
//添加一本书(一个新的Book对象)
Book save(Book book);
//更新一本书,传入一个修改过信息的Book对象(id是标识它的,不会变)
Book update(Book book);
//根据书的id获取一本书
Book get(long id);
//当要添加一本新书时,需要调用这个方法获取一个允许的为其分配的id
//因为只是模拟,没有操作数据库,所以需要这样的方法;如果数据库里设置了自增,可以不使用这种方式
long getNextId();
}
package org.service.imp;
import org.domain.Book;
import org.domain.Category;
import org.service.BookService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
//使能被用作DI给控制器类
@Service
//书籍服务的一种实现(即服务层接口的一个实现类)
//书上提到这种实现不是线程安全的,先不去管这些..
public class BookServiceImp implements BookService {
//服务类组合这两个List对象,来使得后面的操作能进行
//即不使用数据库,模拟书籍的种类和所有书籍都是保存在内存中(这个实现类的成员)
private List categories;
private List books;
//在构造该类的对象时初始化这两个List,并往里面装一些数据
public BookServiceImp() {
//类别们
this.categories = new ArrayList<>();
Category category1 = new Category(1, "计算机");
Category category2 = new Category(2, "旅游");
Category category3 = new Category(3, "健康");
categories.add(category1);
categories.add(category2);
categories.add(category3);
//具体书籍们
books = new ArrayList<>();
Book book1 = new Book(1L, "isbn号1", "书名1", category1, "作者1");
Book book2 = new Book(2L, "isbn号2", "书名2", category2, "作者2");
Book book3 = new Book(3L, "isbn号3", "书名3", category3, "作者3");
books.add(book1);
books.add(book2);
books.add(book3);
}
//获取所有类别
@Override
public List getAllCategories() {
return this.categories;
}
//获取指定类别号的类别
@Override
public Category getCategory(int id) {
for (Category category : this.categories) {
if (id == category.getId()) {
return category;
}
}
return null;//找不到
}
//获取所有的书
@Override
public List getAllBooks() {
return this.books;
}
//新增一本书
@Override
public Book save(Book book) {
//获取一个可用的书籍id
Long id = this.getNextId();
//其它的信息已经在里面了,只要赋予它这个可用的id就行了
book.setId(id);
//添加到books里面
this.books.add(book);
return book;
}
//传入一个修改过信息的Book对象,以更新这本书的信息
@Override
public Book update(Book book) {
//即只要在books里面找到这本书(通过它的id),然后更新一下就可以了
//books里有多少本书
int bookCount = this.books.size();
//然后就可以按下标遍历,以便按下标设置
for (int i = 0; i < bookCount; i++) {
//如果id相同,说明要更新的就是这本书
if (book.getId() == this.books.get(i).getId()) {
//更新它,因为传入的已经是更新好的book对象,直接更改里面存的对象引用就行了
books.set(i, book);
return book;
}
}
return null;//找不到
}
//按书号获取一本书
@Override
public Book get(long id) {
//遍历找到id一样的书
for (Book book : this.books) {
if (id == book.getId()) {
return book;
}
}
return null;//没找到
}
//获取新添加一本书时可用的id,这个实现是当前最大的id加上1
@Override
public long getNextId() {
//初始化为最小的0
long id = 0L;//书上提示这个资源在多线程环境下需要加锁
//遍历找到当前books里面最大的id
for (Book book : this.books) {
//便利到的书籍的id
long bookId = book.getId();
//如果比当前找到的还大
if (bookId > id) {
id = bookId;//替换
}
}
//循环结束后的id就是最大的,返回它加1
//考虑到books为空时,第一本书id应该是1L,所以这个局部变量前面初始化为0L
return id + 1;
}
}
package org.controller;
import org.domain.Book;
import org.domain.Category;
import org.service.BookService;
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 java.util.List;
@Controller
//书籍相关的控制器类
public class BookController {
//注入一个实现了书籍的服务接口的类的对象
@Autowired
private BookService bookService;
//用户要访问表单页
@RequestMapping(value = "/book_input")
public String inputBook(Model model) {
//获取所有的类别号,表单页上要用
List categories = bookService.getAllCategories();
//放进Model对象
model.addAttribute("categories", categories);
//放一个新创建的Book对象到Model对象里,以和表单关联(作它的领域对象)
model.addAttribute("book", new Book());
return "BookAddForm";
}
//用户要访问显示所有书的页面
@RequestMapping(value = "/book_list")
public String ListBooks(Model model) {
//获取所有书的列表
List books = bookService.getAllBooks();
//放进Model对象
model.addAttribute("books", books);
return "BookList";
}
//用户在显示所有书的页面点击某本书,要编辑那本书,即要进入编辑书的页面
//使用路径变量,以表示要编辑的是哪本书
@RequestMapping(value = "/book_edit/{id}")
public String editBook(Model model, @PathVariable long id) {
//因为书的类别可能也会被编辑,所以类别List要使用,所以要拿出来放进Model对象
List categories = bookService.getAllCategories();
model.addAttribute("categories", categories);
//这本书的原来的信息要拿出来,放进Model对象里并和表单关联
Book book = bookService.get(id);
model.addAttribute("book", book);
return "BookEditForm";
}
//用户要添加一本新图书
@RequestMapping(value = "/book_save")
//这里用@ModelAttribute修饰这个book是Model中key为"book"的对象
public String saveBook(@ModelAttribute Book book) {
//根据选择的类别号,完善Book对象中类别号对象的name属性
//因为提交时候只提交了Book对象的类别号对象的id属性,这是不完整的,所以要用这个id重新查
Category category = bookService.getCategory(book.getCategory().getId());
//把查到的完整的类别对象设置给Book对象
book.setCategory(category);
//调用服务层方法添加这本新图书
bookService.save(book);
//重定向到book_list页面,这样才能调用前面的ListBooks方法查出那些书来
return "redirect:/book_list";
}
//用户要更新一本图书
@RequestMapping(value = "/book_update")
//这里用@ModelAttribute修饰这个book是Model中key为"book"的对象
public String updateBook(@ModelAttribute Book book) {
//和添加图书的情况一样,更新图书只是选择了类别的id,需要查出完整的类别对象来给这个Book对象
Category category = bookService.getCategory(book.getCategory().getId());
book.setCategory(category);
//调用服务层方法更新这本书
bookService.update(book);
//重定向到book_list页面,这样才能调用前面的ListBooks方法查出那些书来
return "redirect:/book_list";
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--使用JSTL(JSP标准标签库)要添加两个jar包到lib目录下--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>所有书籍title>
head>
<body>
<h1>所有书籍h1>
<%--用${pageContext.request.contextPath}完善请求路径--%>
<a href="${pageContext.request.contextPath}/book_input">添加新书a>
<table>
<tr>
<th>类别th>
<th>书名th>
<th>ISBN号th>
<th>作者th>
<th>操作th>
tr>
<%--遍历放入Model对象里的books列表,每次跌倒元素记作book--%>
<c:forEach items="${books}" var="book">
<tr>
<%--对象图导航来显示各种属性,这里显示的是这本书的类别属性的名称属性--%>
<td>${book.category.name}td>
<td>${book.title}td>
<td>${book.isbn}td>
<td>${book.author}td>
<%--编辑时,路径参数是要编辑的这本书的id--%>
<td><a href="${pageContext.request.contextPath}/book_edit/${book.id}">编辑a>td>
tr>
c:forEach>
table>
body>
html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--使用Spring MVC的表单标签库--%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%--JSTL标签库--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>新增图书的表单页title>
head>
<body>
<%--commandName属性指示表单关联的模型对象的名称--%>
<form:form commandName="book" action="book_save" method="post">
<fieldset>
<legend>添加一本新书legend>
<p>
<label for="cate">类别:label>
<%--path属性指示这个表单控件所关联的表单对象的属性(可以嵌套)--%>
<%--itemLabel是选择控件的外显,itemValue是内涵,肯定是name外显而id内涵了--%>
<form:select id="cate" items="${categories}" path="category.id" itemLabel="name" itemValue="id"/>
p>
<p>
<label for="ttl">书名:label>
<%--form:input就相当于type是text的input标签--%>
<form:input id="ttl" path="title"/>
p>
<p>
<label for="atr">作者:label>
<form:input id="atr" path="author"/>
p>
<p>
<label for="isb">ISBN号:label>
<form:input id="isb" path="isbn"/>
p>
<p>
<input type="submit" value="添加">
p>
fieldset>
form:form>
body>
html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--使用Spring MVC的表单标签库--%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%--JSTL标签库--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>编辑图书信息的表单页title>
head>
<body>
<%--commandName属性指示表单关联的模型对象的名称--%>
<%--注意!因为更新(Update)是在编辑(Edit)页面,这里action如果不加'/'的话将变成提交到/book_edit/book_update--%>
<form:form commandName="book" action="/book_update" method="post">
<fieldset>
<legend>编辑一本已经存在的书的信息legend>
<%--因为书已经取出来到Model里,其中id是不应该修改的,所以用hidden--%>
<form:hidden path="id"/>
<p>
<label for="cate">类别:label>
<%--path属性指示这个表单控件所关联的表单对象的属性(可以嵌套)--%>
<%--itemLabel是选择控件的外显,itemValue是内涵,肯定是name外显而id内涵了--%>
<form:select id="cate" path="category.id" items="${categories}" itemLabel="name" itemValue="id"/>
p>
<p>
<label for="ttl">书名:label>
<%--form:input就相当于type是text的input标签--%>
<form:input id="ttl" path="title"/>
p>
<p>
<label for="atr">作者:label>
<form:input id="atr" path="author"/>
p>
<p>
<label for="isb">ISBN号:label>
<form:input id="isb" path="isbn"/>
p>
<p>
<input type="submit" value="修改">
p>
fieldset>
form:form>
body>
html>