- 显示电话本
- 显示每个条目的详细信息
- 向电话本中添加一个新条目
- 编辑、修改和删除条目
- 向条目中添加更多详细信息,例如主电子邮件地址
Try Catch
循环。- 面向对象的编程
- Java Platform,Enterprise Edition(Java EE)的术语
- SQL 语句
- XML 语义
- JSP 标记、标记库和标记库描述符
- The Spring Framework v1.2.8 —— 具有所有依赖性的压缩文件。
- Apache Geronimo 1.1 —— Geronimo 是 Apache 的 Java 2 Platform, Enterprise Edition(J2EE)认证应用服务器。
- Apache Derby 数据库 —— 本教程使用 Derby,该数据库是开源的轻量级数据库。Derby 是嵌入到 Geronimo 1.1 里的,因此不需要再单独安装。
- Velocity JAR 文件 —— 您将需要来自 Velocity 模板引擎的 JAR 文件。您既可以从 Spring 框架安装中复制,也可以从 Velocity 站点下载。
- iText —— 这是用于快速生成 PDF 的 PDF 库。Spring 将使用此库来生成 PDF,并且是 Spring 框架的一部分。
- Struts —— 这是来自 Spring 的 Tile 支持,它依赖于 Struts API。您需要使用来自此 API 的主 struts.jar 文件,可以在 Spring 框架中找到。
- Spring Tile 支持所需的常见库 JAR 文件 —— 您需要使用 commons-digester.jar、commons-collection.jar 和 commons-beanutils.jar。这些都来自 Spring 框架安装,因此只需将这些文件复制到开发环境中。
- Standard taglib API —— 由于在 JSP 中使用的是 JSP 标准标记库(JSP Standard Tag Library,JSTL)标记,因此需要使用此压缩文件中的 JAR 文件。
- 标准的 JSTL 库 —— 当前版本为 1.1.2。
- Apache Ant —— 确保正确配置 Ant 并且其 /bin 目录位于 Path 系统变量中。
- Java 1.4.2 —— 确保 Java 安装并运行在系统中。
- 安装 Spring 框架和 Geronimo:要使样例代码运行,需要安装运行 Geronimo 和 Spring 框架。(有关安装指南,请参阅本系列教程的 第 2 部分。)
- 解析 Tile 依赖性所需的 JAR 文件:Spring 中的 Tile 支持需要使用以下 JAR 文件:
- Struts.jar —— <SPRING_HOME>\lib\struts
- Commons-digester.jar —— <SPRING_HOME>\lib\jakarta-commons
- Commons-collection-3.2.jar —— <SPRING_HOME>\lib\jakarta-commons
- Commons-beanutils.jar —— <SPRING_HOME>\lib\jakarta-commons
所有这些 JAR 文件都随 Spring 框架安装包附带而来。您可以在以上列表中指出的目录中找到它们,.jar 文件名旁边。确保将所有这些 JAR 文件复制到 <WORKSPACE>/phonebook/lib 目录中。 - 解析 Velocity 依赖性所需的 JAR 文件:样例电话本应用程序需要包括 velocity-1.4.jar 才能使用 Velocity。Velocity 还需要使用复制的 commons-collections.jar 才能确保 Tile 完整性。Velocity .jar file 可以在 Spring 安装目录的 <SPRING_HOME>\lib\velocity 文件夹中找到。确保将此文件复制到 <WORKSPACE>/phonebook/lib 目录中。
- 解析 PDF 依赖性所需的 JAR 文件:Spring 的 PDF 支持 API 将使用 iText PDF 库,因此需要该库为应用程序创建 PDF。同其他 JAR 文件一样,此文件也是预打包在 Spring 安装中。您可以在 <SPRING_HOME>\lib\itext 中找到 itext-1.3.jar 文件。将此文件也复制到 lib 目录中。
- 安装 Apache 的 Standard Taglib 和 Spring taglib:您将使用在本系列教程的 第 5 部分 中定义的 JSP 并且使用它们扩展应用程序,因此需要安装 JSTL 库。(请参阅本系列教程的第 5 部分中的安装说明。)
- 应用程序的数据模型定义和数据库设置:您将使用在本系列教程的其它部分中创建的同一个 Derby 数据库。数据模型也是相同的。如果在第 3 部分、第 4 部分或第 5 部分中创建了数据库和表,则应当已经设置好了一切。如果未创建,请按照 第 3 部分 中的说明先完成设置。
- 能够通过组合 Tile 创建屏幕/视图:页眉、页脚、菜单、主体等等。
- 可以将定义集中在 XML 文件中或直接集中到 JSP 页面。此示例使用了一个集中的 XML 文件用于视图定义。
- 能够继承定义和扩展另一个定义或覆盖参数。
- 能够使用 Tile 作为布局管理器甚至还可以为各种应用程序重用布局。
- 能够使用 Tile API 轻松地创建国际化的内容。
Try Catch
循环)的复杂应用。- 为应用程序定义一种布局机制。这是一个简单的 Web 应用程序,因此是页眉 > 主体 > 页脚。
- 定义 Tile 模板文件。
- 为 Tile 模板定义视图解析程序。
- 将此模式扩展到电话本应用程序的所有页面。
- 定义一个新控制器来处理 Velocity 模板请求。
- 为主页创建一个 Velocity 模板。
- 为 .vm 文件定义一个视图解析程序。
- 定义一个控制器来处理 PDF 视图请求。
- 定义一个视图类来为电话本主页创建 PDF。
- 将此添加到 Application Context 的视图解析程序链中。
图 2. 解压缩源文件后的应用程序目录结构
![Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术_休闲_02](http://www.ibm.com/developerworks/cn/education/opensource/os-ag-springframe6/fig02.gif)
- 显示电话本条目(默认情况下是主页)。
- 向电话本中添加条目。
- 删除选定条目。
- 修改选定电话本条目。
图 3. 解压缩源文件后的应用程序目录结构
![Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术_职场_03](http://www.ibm.com/developerworks/cn/education/opensource/os-ag-springframe6/fig03.gif)
AbstractPdfView
类来创建 PDF 视图。此类将为您处理所有创建 PDF 文档的工作,并为您提供一个干净的文档用于添加内容。然后将手动创建电话本主页并以 PDF 形式返回。清单 1. 在 web.xml 中添加 Tile 标记库
<jsp-config> <taglib> <taglib-uri>/tiles</taglib-uri> <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location> </taglib> </jsp-config> |
清单 2. phonebook-layout.jsp 显示 Web 页面的布局
<%@ taglib prefix="tiles" uri="/tiles" %> <!-- This page defines the general layout for Phonebook Application Pages --> <tiles:insert name="header"/> <tiles:insert name="body"/> <tiles:insert name="footer"/> |
tiles:insert
标记来插入按名称属性表示的内容。正如此处所示,没有内容,只有页面的布局。TilesConfigurer
类来完成此操作。清单 3 显示了它是如何在 Application Context 文件中定义的。清单 3. 视图定义文件名被传递给 Application Context 中的
TilesConfigurer
<!-View Resolver for Tiles --> <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="order" value="1" /> <property name="basename" value="views-phonebook-tiles"/> </bean> <!-- Tiles configurer reads the view definition from the XML file --> <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles.TilesConfigurer"> <property name="factoryClass" value="org.apache.struts.tiles.xmlDefinition.I18nFactorySet"/> <property name="definitions"> <list> <value>/WEB-INF/defs/phonebook-definitions.xml</value> </list> </property> </bean> |
TilesConfigurer
将在初始化时读取此文件,然后使可用的定义文件中的所有视图都可在应用程序中使用。ViewResolver
的,Spring 必须使用它才能解析视图。ResourceBundleViewResolver
是 Spring 框架中最常用的视图解析程序之一。您需要传递到定义映射到类和 URL 的视图名的属性文件中。清单 4 显示了 views-phonebook-tiles.properties 文件中的内容。清单 4. views-phonebook-tiles.properties 文件中的视图定义
home-mvc.class=org.springframework.web.servlet.view.tiles.TilesView home-mvc.url=home-mvc addentry-mvc.class=org.springframework.web.servlet.view.tiles.TilesView addentry-mvc.url=addentry-mvc modifyentry-mvc.class=org.springframework.web.servlet.view.tiles.TilesView modifyentry-mvc.url=modifyentry-mvc |
TilesView
。第二行显示特定于表示这些视图的每个页面的 URL。addEntry
或 ModifyEntry
JSP 页面内容的任何信息。就是这样设计的,因为它允许您对很多页面重用此布局。不是将内容硬编码到布局定义页面中,而是在运行时将其作为参数传递到布局页面中。清单 5. 用于在运行时将内容传递到布局页面的定义文件
<tiles-definitions> <!-- DEFAULT MAIN TEMPLATE --> <definition name="template" page="/WEB-INF/jsp/phonebook-layout.jsp"> <put name="header" value="/WEB-INF/jsp/header.jsp"/> <put name="footer" value="/WEB-INF/jsp/footer.jsp"/> </definition> <!-- The Home Page --> <definition name="home-mvc" extends="template"> <put name="body" value="/WEB-INF/jsp/home-mvc.jsp" type="page"/> </definition> <!-- The Add Entry Page --> <definition name="addentry-mvc" extends="template"> <put name="body" value="/WEB-INF/jsp/addentry-mvc.jsp" type="page"/> </definition> <!-- The Modify Entry Page --> <definition name="modifyentry-mvc" extends="template"> <put name="body" value="/WEB-INF/jsp/modifyentry-mvc.jsp" type="page"/> </definition> </tiles-definitions> |
put
元素将告诉 Tile 框架获取视图的位置。在本例中,页眉视图将从 header.jsp 中获取其内容,而页脚将从 footer.jsp 中获取其内容。- 由于 home-mvc 扩展了默认模板,因此它将自动从电话本布局中获取页眉和页脚内容。
- 此定义将通过指向 home-mvc.jsp 页面把主体置入主页中。
图 4. 浏览器中启用了 Tile 的主页
![Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术_mvc_04](http://www.ibm.com/developerworks/cn/education/opensource/os-ag-springframe6/fig04.jpg)
ApplicationContext
知道 Velocity 模板的存在。第一种方法是使用 VelocityConfigurer
。您需要做的工作就是在 resourceLoaderPath
中提供 Velocity 模板的位置,VelocityConfigurer
将在运行时使来自该位置的所有模板都可用于应用程序。PhonebookVelocityController
类来控制。您将在以后的部分中定义此类。第三个 bean 定义使用了 VelocityViewResolver
来解析带有 .vm 扩展名的所有页面请求。清单 6. 让上下文知道 Velocity 视图的存在的 Bean 定义
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/*.do">phonebookController</prop> <prop key="/*.htm">phonebookFlowController</prop> <prop key="/*.flow">phonebookFlowController</prop> <prop key="/addentry-mvc.act">addEntryFormController</prop> <prop key="/home-mvc.act">phonebookHomeController</prop> <prop key="/modifyentry-mvc.act">modifyEntryFormController</prop> <prop key="/deleteentry-mvc.act">deleteEntryFormController</prop> <prop key="/home.vel">phonebookVelocityController</prop> <prop key="/home.pdf">phonebookPDFController</prop> </props> </property> </bean> <bean id="phonebookVelocityController" class="phonebook.velocity.PhonebookVelocityController"/> <!-- View Resolver for Velocity --> <bean id="velocityViewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver" > <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".vm"/> </bean> |
PhonebookVelocityController
的代码。清单 7. 从数据库中读取
PhonebookEntries
并传递给视图的类
public class PhonebookVelocityController implements Controller { /** Creates a new instance of PhVelocityController */ public PhonebookVelocityController() { } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, Exception { ModelAndView view = null; if (request.getRequestURI().indexOf("home")!=-1) { view = new ModelAndView("phonebook-template"); view.addObject("viewId", "home"); view.addObject("time", new Date() ); WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(request.getSession(). getServletContext()); IPhonebookDataProvider pb = (IPhonebookDataProvider) ctx.getBean("phonebook"); List phonebookEntries = pb.getPhonebookEntries(); System.out.println("Velocity No. of Entries = "+phonebookEntries.size()); view.addObject("phEntries", phonebookEntries); } return view; } } |
WebApplicationContext
来获取 phonebook
bean 并从数据库中读取电话本条目。然后它将此作为一个对象添加到视图中。您接下来将看到 Velocity 模板怎样读取它。清单 8. 定义 Web 应用程序布局的默认模板
#set ($viewName = ${viewId}) #set ($viewName = "${viewName}.vm") ## View name is passed in dynamically from controller. #parse("header.vm") #parse($viewName) #parse("footer.vm") |
viewName
是由 phonebookVelocityController
在运行时动态传递的。清单 9. 基于 Velocity 的主页的模板代码
#set ($time1 = ${time}) #set ($phonebook = ${phEntries}) <html> <script type="text/javascript"> function goToAddEntryPage() { document.myForm.action="/phonebook/addentry.vel"; document.myForm.method="GET"; document.myForm.submit(); } function setId(entryID, rowID) { document.myForm.entryID.value = entryID; document.myForm.rowID.value = rowID; } function noRowSelected() { var entryID = document.myForm.entryID.value; var rowID = document.myForm.rowID.value; if (entryID == "" || rowID == "") { return "true"; } else { return "false"; } } function goToModifyEntryPage() { if (noRowSelected() == "true") { alert("Please select an Entry to Modify"); return; } document.myForm.action="/phonebook/modifyentry-mvc.act"; document.myForm.method="GET"; document.myForm.submit(); } function deleteEntry() { if (noRowSelected() == "true") { alert("Please select an Entry to Delete"); return; } else { var retVal = confirm("Please click OK to confirm your deletion. Click Cancel otherwise"); if (retVal != true) { return; } } document.myForm.action="/phonebook/deleteentry-mvc.act"; document.myForm.method="POST"; document.myForm.submit(); } </script> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Phone Book - $time1 </title> </head> <body> <!--The Heading of the Phone Book Page--> <h1 align=center>Phone Book</h1> <BR> <BR> <!-- The table containing phone book contents. --> <TABLE border="1" width="100%"> <TH width="5%" align=center>Select</TH> <TH width="25%" align=center>Name</TH> <TH width="15%" align=center>Home Phone</TH> <TH width="15%" align=center>Work Phone</TH> <TH width="15%" align=center>Cell Phone</TH> <TH width="25%" align=center>Email</TH> #foreach( $pbEntry in $phonebook ) #set ($i = $velocityCount - 1) <TR> <TD align=center><input type=radio name="data_"$i alt="Select to Delete" align="middle"></TD> <TD align=center>$pbEntry.getFirstName() $pbEntry.getLastName()</TD> <TD align=center>$pbEntry.getHomeNumber()</TD> <TD align=center>$pbEntry.getWorkNumber()</TD> <TD align=center>$pbEntry.getCellNumber()</TD> <TD align=center>$pbEntry.getEmail()</TD> </TR> #end </TABLE> <BR> <BR> <table align=center> <!-- The row containing command buttons --> <TR align=center> <!-- <TD><input type=submit name="Add" value="Add an Entry" <TD><input type=button name="Modify" value="Modify Selected Entry" <TD><input type=button name="Delete" value="Delete Selected Entry" --> </TR> </table> </body> <BR><BR><BR><BR><BR><BR> </html> |
pbEntries
对象。然后用它迭代用电话本条目填充表的列表。清单 10. *.vm 请求的 URL 映射
<servlet-mapping> <servlet-name>phonebook</servlet-name> <url-pattern>*.vel</url-pattern> </servlet-mapping> |
图 5. 显示运行中的 Velocity 引擎的主页
![Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术_休闲_05](http://www.ibm.com/developerworks/cn/education/opensource/os-ag-springframe6/fig05.jpg)
ApplicationContext
的 bean 定义读取 phonebookEntries
类,然后将其传递给 PhonebookPdfView
类所表示的 PDF 视图。清单 11 显示了此控制器的代码。清单 11.
PhonebookPDFController
类读取电话本条目并将其传递给 PhonebookPdfView
类
public class PhonebookPDFController extends MultiActionController { private static final String PDF_VIEW = "phonebook_pdfView"; private String pdfView = PDF_VIEW; /** Creates a new instance of PbPDFController */ public PhonebookPDFController() { } public void setPdfView(String view) { this.pdfView = view; } /** * Custom handler for phonebook PDF document. * @param request current HTTP request * @param response current HTTP response * @return a ModelAndView to render the response */ public ModelAndView handlePdf(HttpServletRequest request, HttpServletResponse response) throws ServletException, Exception { WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(request.getSession(). getServletContext()); IPhonebookDataProvider pb = (IPhonebookDataProvider) ctx.getBean("phonebook"); List phonebookEntries = pb.getPhonebookEntries(); return new ModelAndView(this.pdfView, "phonebook", phonebookEntries); } } |
View
类,该类将通过模型输出实际生成 PDF。PhonebookPdfView
是 AbstractPdfView
类的子类,用于实现特定于 Web 视图的 PDF 文档的自定义生成。在此类中最值得注意的方法是 buildPdfDocument
。这是所有魔术变幻的关键位置所在。清单 12 显示了为电话本应用程序生成 PDF 的完整代码。清单 12. PDF 视图类将创建 PDF 文档
public class PhonebookPdfView extends AbstractPdfView { private static final Font HEADLINE_FONT = new Font( Font.TIMES_ROMAN , 18, Font.BOLD, Color.black ); private static final Font DATA_HEAD_FONT = new Font( Font.TIMES_ROMAN, 10, Font.BOLD, Color.black ); private static final Font TEXT_FONT = new Font( Font.TIMES_ROMAN, 8, Font.NORMAL, Color.black ); private static final Font FOOTER_FONT = new Font( Font.TIMES_ROMAN, 5, Font.NORMAL, Color.black ); private static final int NO_OF_COLUMNS = 5; /** Creates a new instance of PhonebookPdfView */ public PhonebookPdfView() { } protected void buildPdfMetadata(Map model, Document document, HttpServletRequest request) { document.addTitle("Phone Book"); document.addCreator("Arun Chhatpar"); } protected void buildPdfDocument( Map model, Document doc, PdfWriter writer, HttpServletRequest req, HttpServletResponse resp) throws Exception { List phonebook = (List) model.get("phonebook"); String title = "Phone Book"; Paragraph h1 = new Paragraph(title, HEADLINE_FONT); h1.setAlignment(Paragraph.ALIGN_CENTER); doc.add(h1); doc.add(new Paragraph(" ")); doc.add(new Paragraph(" ")); doc.add(new Paragraph(" ")); // We create a table for used criteria and extracting information PdfPTable table = new PdfPTable(NO_OF_COLUMNS); int headerwidths[] = {20, 20, 20, 20, 20}; table.setWidths(headerwidths); table.setWidthPercentage(100); table.getDefaultCell().setBorderWidth(1); table.getDefaultCell().setBorderColor(Color.black); table.getDefaultCell().setPadding(3); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); table.getDefaultCell().setVerticalAlignment(Element.ALIGN_MIDDLE); table.addCell(new Phrase("Name", DATA_HEAD_FONT)); table.addCell(new Phrase("Home Phone", DATA_HEAD_FONT)); table.addCell(new Phrase("Work Phone", DATA_HEAD_FONT)); table.addCell(new Phrase("Cell Phone", DATA_HEAD_FONT)); table.addCell(new Phrase("Email", DATA_HEAD_FONT)); // We set the above row as title // and adjust properties for normal cells table.setHeaderRows(1); table.getDefaultCell().setBorderWidth(1); // We iterate now on the Phonebook list Iterator it = phonebook.iterator(); while(it.hasNext()) { PhonebookEntry pEntry = (PhonebookEntry) it.next(); table.addCell(new Phrase(pEntry.getFirstName() + " " + pEntry.getLastName(), TEXT_FONT)); table.addCell(new Phrase(pEntry.getHomeNumber(), TEXT_FONT)); table.addCell(new Phrase(pEntry.getWorkNumber(), TEXT_FONT)); table.addCell(new Phrase(pEntry.getCellNumber(), TEXT_FONT)); table.addCell(new Phrase(pEntry.getEmail(), TEXT_FONT)); } doc.add(table); doc.add(new Paragraph(" ")); doc.add(new Paragraph(" ")); doc.add(new Paragraph(" ")); doc.add(new Paragraph(" ")); String footerStr1 = "This example is developed to demonstrate the Spring framework on the Geronimo Application Server."; String footerStr2 = "This example is designed purely for demonstrating the capabilities of the framework. It should under no circumstances be used for production use."; Paragraph footer1 = new Paragraph(footerStr1, FOOTER_FONT); footer1.setAlignment(Paragraph.ALIGN_CENTER); doc.add(footer1); Paragraph footer2 = new Paragraph(footerStr2, FOOTER_FONT); footer2.setAlignment(Paragraph.ALIGN_CENTER); doc.add(footer2); } } |
MultiActionController
。这样做是希望向您展示另一种处理和解析 Spring 中的视图的方法。清单 13 对您来说应当很熟悉。清单 13. 在
ApplicationContext
中添加 PDF 控制器和解析程序
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/*.do">phonebookController</prop> <prop key="/*.htm">phonebookFlowController</prop> <prop key="/*.flow">phonebookFlowController</prop> <prop key="/addentry-mvc.act">addEntryFormController</prop> <prop key="/home-mvc.act">phonebookHomeController</prop> <prop key="/modifyentry-mvc.act">modifyEntryFormController</prop> <prop key="/deleteentry-mvc.act">deleteEntryFormController</prop> <prop key="/home.vel">phonebookVelocityController</prop> <prop key="/home.pdf">phonebookPDFController</prop> </props> </property> </bean> <bean id="phonebookPDFController" class="phonebook.pdf.PhonebookPDFController"> <property name="methodNameResolver"> <bean class="org.springframework.web.servlet.mvc.multiaction. PropertiesMethodNameResolver"> <property name="mappings"> <props> <prop key="/home.pdf">handlePdf</prop> </props> </property> </bean> </property> </bean> <!-- View Resolver for PDF --> <bean id="pdfViewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="order" value="2" /> <property name="basename" value="views-phonebook-pdf"/> </bean> |
ResourceBundleViewResolver
需要用于类的属性文件和用于视图的 URL 定义。在本例中,views-phonebook-pdf.properties 文件只包含一行:phonebook_pdfView.(class)=phonebook.pdf.PhonebookPdfView
。清单 14. web.xml 文件中用于 *.pdf 的 URL 映射
<servlet-mapping> <servlet-name>phonebook</servlet-name> <url-pattern>*.pdf</url-pattern> </servlet-mapping> |
Phonebook application deployed successfully
。接下来,将浏览器指向新页面:http://localhost:8080/phonebook/home.pdf。如果一切运行正常,您应当会在浏览器中看到用 Adobe Acrobat Reader 打开的 PDF。- 明确分隔视图与进入这些视图中的内容
- 能够分隔任务(使用 Spring 的模板使应用程序开发人员可以轻松地将精力完全集中到应用程序代码上,同时使 Web 开发人员可以设计最佳的 HTML 页面而无需担心如何呈现页面)
- 能够动态改变视图技术
- 能够测试单个视图组件并且在开发周期早期就能准确指出错误