理解 Spring MVC
在这一节,您将使用 Spring MVC 为员工信息应用程序创建基于 Web 的用户界面。
用 Spring 实现 MVC 设计模式
模型-视图-控制器(MVC)设计模式是把 Web 层和数据层组件连接在一起,创建 Web 应用程序的最好方式。几乎所有的现代应用程序框架都提供了对构建 MVC 模式应用程序的支持。
MVC 模式提供的一个关键优势就是应用程序中数据和表示之间清晰的隔离。这种清晰的隔离让数据和表示能够相互独立地发展。在生产系统中,这非常有价值,因为数据和表示随着时间的变化和需求的不同会有所变化。
对于 MVC 设计模式的深入介绍,请参阅 参考资料。以下讨论仅为概述,仅适用于我们的示例应用程序。
应用程序的模型部分包含的元素封装了数据和数据上的操作。实际上,已经看到了示例应用程序中的模型 —— 域模型。整个域模型创建和测试时,没有整合任何表示元素(用户界面、报表,等等)。MVC 设计模式允许在独立于视图的情况下创建和测试模型。
应用程序的视图部分完全是用 Java Server Pages(JSP)技术和 JSP Standard Tag Library(JSTL)创建的。您很快就会发现,可以独立于模型创建视图。在生产中这非常重要,因为 Web 页面设计人员(而不是 Java 开发人员)可以处理视图的创建和测试。
控制器是模型和视图之间的关键连接。Spring MVC 是构建基于 Web 的应用程序的框架。在 Spring MVC 中,控制器处理传入的 HTTP Web 请求。
有一组功能丰富的控制器库组件可以用来开发子类。所有 Spring MVC 控制器库类都实现了 org.springframework.web.servlet.mvc.Controller 接口。这个接口只有一个方法:
ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws Exception控制器检查和处理传入的请求,然后返回 ModelAndView 对象。Spring 有一个叫作 org.springframework.web.servlet.mvc.AbstractController 的抽象类,它实现了这个接口并处理大多数缓存和会话管理特性。在实践中,所有专门的 Spring 控制器都派生自这个抽象类。表 4 描述了在示例应用程序中使用的两个 Spring 控制器类:
表 4. 示例应用程序中使用的 Spring MVC 控制器类
| 控制器类 | 说明 |
| AbstractController | 抽象控制器基类。在控制器不需要处理传入的命令/参数或处理表单时有用。在示例应用程序中,它呈现初始页面,列出数据库中的全部员工。 |
| AbstractCommandController | 处理传入命令的抽象控制器类。这个类解析传入的 HTTP 请求并把指定的 Java 对象实例绑定到请求参数,从而实现在控制器逻辑中对参数的轻松处理。 |
关于 Spring 中可以使用的其他控制器基类的更多信息,请参阅 参考资料。
创建 MainController
这个应用程序中的 MainController 处理进入应用程序的初始进入请求。这是在用户通过 URL http://localhost:8080/dwspring/index.cgi 进入应用程序的主页时发生的。
这个控制器显示系统中的所有员工,并允许用户单击员工号上的连接,得到员工细节。MainController 的代码如清单 17 所示:
清单 17. 显示员工列表的 MainController
package com.ibm.dw.spring2.web;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import com.ibm.dw.spring2.Employee;
import com.ibm.dw.spring2.EmployeeService;
public class MainController extends AbstractController{
public ModelAndView handleRequestInternal(HttpServletRequest req,
HttpServletResponse resp) {
List <Employee> emps = employeeService.findAll();
return new ModelAndView("home", "employees", emps);
}
private EmployeeService employeeService;
public void setEmployeeService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
}在清单 17 中,请注意 MainController 没有显示任何用户界面。相反,它访问数据层,得到员工列表。为了得到列表,它调用 EmployeeService 实现的 findAll() 方法。这个实现通过 Spring 的依赖性注入被 “注入” 控制器,是 EmployeeDAO 的实例。马上就会在 dwspring2-servlet.xml bean 描述符中配置它(请参见 连接 Spring MVC bean)。
要呈现用户界面,控制器创建一个 ModelAndView 对象并返回它。ModelAndView 对象创建时有三个参数,如表 5 所述:
表 5. ModelAndView 构造函数参数
| 位置 | 参数 | 说明 |
| 1 | 视图名称 | 这是命名视图的字符串。这个字符串被传递给视图解析器组件,解析为特定视图/表示组件 —— 本例中是 JSP。视图解析器在运行时由 Spring 引擎连接 。在 dwspring2-servlet.xml bean 描述符文件中配置视图解析器。 |
| 2 | 模型名称 | 命名从域模型提取的数据的字符串。数据本身在第三个参数中传递。这个数据通常由视图在执行期间呈现。对于 JSP,这个名称是变量的名称。变量值可以用 JSP 命令或 JSTL 标记呈现。 |
| 3 | 模型对象 | 这是视图呈现的模型数据。在视图中,可按照参数 2 提供的名称引用。 |
要掌握 Spring MVC 的操作,重要的是理解请求流程。图 9 显示了通过 Spring MVC 组件的请求流:
图 9. 通过 Spring MVC 组件的请求流
在图 9 中,虚线矩形中的是 Spring 组件。进入请求先由 Spring 的 DispatcherServlet 处理。这个 servlet 通过连接的 URL 映射器组件对请求的进入 URL 进行映射。这个映射器组件提供了控制请求的实际控制器。一旦控制器处理请求,就把视图名称传递回 Spring MVC。然后 Spring MVC 调用连接的视图解析器组件,传递进要呈现的模型数据。视图解析器组件把视图名称解析为视图对象。这个视图对象被传递给模型数据,并向用户呈现。
Both the URL 映射器和视图解析器都是 Spring 2 提供的组件,可以在 bean 描述符 XML 文件中指导 Spring 引擎连接它们。
连接 Spring MVC bean
在 Spring MVC 中,Web 应用程序是在 Web 应用程序上下文 中执行的。这个上下文由 bean 描述符连接 XML 文件配置,就像数据层代码一样。
默认情况下,用以下规则形成配置文件的名称:采用 Spring DispatcherServlet 的 servlet 名称,后面加上 _servlet.xml。
Spring DispatcherServlet 类通过 Web 容器(在这个示例中是 Tomcat)接受传入的 Web 请求,并把它分配到某个控制器。
在这个示例中,DispatcherServlet 用 dwspring2 名称配置,因此,配置文件应当在 dwspring2_servlet.xml。这个配置文件如清单 18 所示:
清单 18. dwspring2-servlet.xml bean 描述符
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="mainController" class="com.ibm.dw.spring2.web.MainController">
<property name="employeeService">
<ref bean="employeeService"/>
</property>
</bean>
<bean name="empDetailsController" class="com.ibm.dw.spring2.web.EmpDetailsController">
<property name="employeeService">
<ref bean="employeeService"/>
</property>
</bean>
<bean id="controllermap" class=
"org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/home.cgi">mainController</prop>
<prop key="/empdet.cgi">empDetailsController</prop>
</props>
</property>
</bean>
<bean id="viewResolver" class=
"org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/jsp/" />
<property name="suffix" value=".jsp"/>
</bean>
</beans>在清单 18 中,可以看到不同 bean 的连接。首先,mainController bean 是 MainController 类的实例,被注入了一个到 employeeService bean 的引用。当然这个 bean 是在 dwspring2-service.xml 中在数据层定义的 EmployeeDAO 的实例。
另一个控制器,叫作 empDetailsController,也通过引用注入了相同的 employeeService 实例。在这一节后面将看到这个控制器的代码。
被连接的第三个 bean 是 controllermap。这是 org.springframework.web.servlet.handler.SimpleUrlHandlerMapping 的实例。这个 Spring 提供的 bean 能根据映射属性把进入的 URL 请求映射到不同的控制器。在这个示例中,/home.cgi 被映射到 MainController,/empdet.cgi 被映射到 empDetailsController。
连接的最后一个 bean 是视图解析器。这个 Spring 提供的 bean 把视图名称映射到实际视图资源。这个示例中使用的是 org.springframework.web.servlet.view.InternalResourceViewResolver 的实例。这个视图解析器根据进入的视图名称,添加前缀和后缀,创建资源 URL。在清单 18 中,前缀被配置为 /jsp,后缀是 .jsp。例如,名为 home 的视图被映射到 /jsp/home.jsp。这意味着 URL /jsp/home.jsp 呈现名为 home的视图。
配置 Spring DispatcherServlet
WEB-INF/web.xml 部署描述符包含 DispatcherServlet 的配置。清单 19 显示了相关的代码片断:
清单 19. web.xml 中的 Spring DispatcherServlet 配置 <servlet>
<description>
Spring MVC Dispatcher Servlet</description>
<display-name>
DispatcherServlet</display-name>
<servlet-name>dwspring2</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwspring2</servlet-name>
<url-pattern>*.cgi</url-pattern>
</servlet-mapping>指定 <load-on-startup> 确保在第一次启动 Web 应用程序时,装入 servlet。这还会触发 dwspring2-servlet.xml 配置文件的解析。
<servlet-mapping> 标签告诉 Tomcat 对 for *.cgi 资源的全部 Web 请求路由到 DisplatcherServlet。* 号代表通配符匹配。所以,任何 http://host:port/dwspring/*.cgi 形式的请求,都被转发到 DispatcherServlet。
用 Spring MVC 创建基于 JSP/JSTL 的用户界面
MainController 类把 Employee 的 List 传递给 home 视图。 home 视图由 InternalResourceViewResolver 解析成 /jsp/home.jsp。清单 20 是 home.jsp 的代码。这个页面只显示在 ModelAndView 中以 employees 传递过来的员工列表。
清单 20. home.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<link rel="stylesheet" type="text/css" href="css/dwstyles.css"/>
<title>dW Spring 2 Employee Data from DB2 via JPA</title>
</head>
<body>
<h1>Spring 2 JPA Employee List</h1>
<table>
<tr>
<th>Employee number</th>
<th>Name</th>
</tr>
<c:forEach var="emp" items="${employees}">
<tr>
<td><a href="empdet.cgi?empID=${emp.empid}">${emp.empno}</td>
<td>${emp.firstName} ${emp.midInitial} ${emp.lastName}</td>
</tr>
</c:forEach>
</table>
</body>
</html>在清单 20 中,可以看到用 JSTL 的 <c:forEach> 标记迭代 ${employees} 列表中的每个员工。代码在 ${emp.empno} 信息周围创建链接。例如,围绕 Joe Smith 这行生成的 URL 是 http://localhost:8080/dwspring/empdet.cgi?empID=1。
在点击这个 URL 链接时,映射到 empdat.cgi 的资源被激活。根据 web.xml 配置,Tomcat 知道要把全部 *.cgi 资源请求发送给 Spring 的 DispatcherServlet,而且 Spring 的 DispatcherServlet 根据 dwspring2-servlet.xml 中的配置,也知道用 SimpleUrlHandlerMapping。SimpleUrlHandlerMapping 告诉 Spring 引擎把请求引导到 EmpDetailsController。
处理命令的控制器
EmpDetailsController 的代码如清单 21 所示:
清单 21. 显示员工细节的 EmpDetailsController
package com.ibm.dw.spring2.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractCommandController;
import com.ibm.dw.spring2.Employee;
import com.ibm.dw.spring2.EmployeeService;
public class EmpDetailsController extends AbstractCommandController{
public EmpDetailsController() {
setCommandClass(EmployeeDetailsCommand.class);
}
private EmployeeService employeeService;
public void setEmployeeService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
protected ModelAndView handle(HttpServletRequest req,
HttpServletResponse resp, Object cmd, BindException ex) throws Exception {
EmployeeDetailsCommand ecmd = (EmployeeDetailsCommand) cmd;
Employee emp = employeeService.findById(ecmd.getEmpID());
return new ModelAndView("empdetails", "emp", emp);
}
}EmpDetailsController 是 AbstractCommandController 的子类。 AbstractCommandController 是个有用的控制器,在处理根据按钮或 URL 点击需要执行的命令时,可以从它派生子类。
在这个示例中,用户点击员工编号 URL,命令被用来显示员工的详细信息。
AbstractCommandController 主要的增值是:它解析进入的请求,得到请求参数,并把参数绑定到所定义的进入命令类的实例。进入请求参数的名称与命令类的属性名称匹配。命令类叫作 EmployeeDetailsCommand。EmployeeDetailsCommand 的代码如清单 22 所示:
清单 22. EmployeeDetailsCommand 命令类package com.ibm.dw.spring2.web;
public class EmployeeDetailsCommand {
private long empID;
public long getEmpID() {
return empID;
}
public void setEmpID(long empID) {
this.empID = empID;
}
}在清单 22 中,可以看到 EmployeeDetailsCommand 命令类的简单结构。它只有一个属性,叫作 empID。AbstractCommandController 查询叫作 empID 的进入请求参数,并把它绑定到 EmployeeDetailsCommand 类的实例。然后把它传递给 EmployeeDetailsController,在这里调用 handle() 方法(请参阅 清单 21)。
一般来说,可以从 AbstractCommandController 派生子类,处理任意数量的进入请求参数。需要做的全部工作就是用对应的属性定义命令类。
在 清单 21 中,EmployeeDetailsController 把传递过来的 cmd 对象的类型转换回 EmployeeDetailsCommand 实例,并提取 empID。然后用 empID 查询特定 Employee 实例的 EmployeeService。这是通过 DAO 的 employeeService.findById() 方法进行的。
在 清单 21 中,生成的 Employee 记录作为 作为 emp,被传递给视图。指定的视图是 empdetails 视图。同样,Spring 引擎查看 InternalResourceViewResolver 来解析视图。这个视图解析器加上适当的前缀和后缀,并返回 /jsp/empdetails.jsp 作为视图处理器。
empdetails.jsp 的代码如清单 23 所示:
清单 23. empdetails.jsp<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<link rel="stylesheet" type="text/css" href="css/dwstyles.css"/>
<title>dW Spring 2 Employee Detail Information</title>
</head>
<body>
<h1>Spring 2 JPA Employee Details</h1>
<table>
<tr>
<th colspan="2">Employee Information</th>
</tr>
<tr>
<td>Employee No.</td><td>${emp.empno}</td>
</tr>
<tr>
<td>Name</td><td>${emp.firstName} ${emp.midInitial} ${emp.lastName}</td>
</tr>
<tr>
<td>Address</td><td>${emp.addr.number} ${emp.addr.street}</td>
</tr>
<tr>
<td>hone</td><td>${emp.phoneNumber}</td>
</tr>
<tr>
<td>Salary</td><td>${emp.salary}</td>
</tr>
<tr>
<td>Bonus</td><td>${emp.bonus}</td>
</tr>
<tr>
<td>Commission</td><td>${emp.commission}</td>
</tr>
</table>
<a href="home.cgi">Go back to employee list</a>
</body>
</html>在清单 23 中,进入的 ${emp} 变量被用来显示 HTML 表格中的员工详细信息。
请注意,页面底部的链接映射回员工清单页面。URL 是 home.cgi。如果点击这个链接,映射过程就再次开始,并传递到 MainController、然后 /jsp/home.jsp,依次类推。
添加层级样式表
home.jsp 和 empdet.jsp 用 css/dwstyles.css 样式表格式化它们的 HTML。样式表代码如清单 24 所示:
清单 24. dwstyles.css 样式表h1 {
font-family: arial;
font-size: 28;
align: left;
font-weight: bold;
font-style: italic;
color: green;
}
h2 {
font-family: serif, times;
font-size: 18;
align: left;
color: blue;
}
th {
font-family: verdana, arial;
font-size: 13;
font-weight: bold;
align: left;
background-color: black;
color: white;
}
td {
font-family: verdana, arial;
font-size: 12;
font-style: italic;
text-align: left;
}
table {
border-style: solid;
border-width: thin;
}
.label {
font-size: 16;
font-weight: bold;
font-style: normal;
text-align: right;
}
.name {
font-size: 24;
font-weight: bold;
font-style: italic;
font-family: serif, times roman;
}创建 Eclipse WTP 动态 Web 项目
要查看完整应用程序的效果,需要:
1. 编译代码。
2. 构建可部署的 WAR 文件。WAR 文件是用标准格式创建的 JAR 文件,用于在 J2EE 兼容的 Web 层容器(例如 Tomcat)中部署。
3. 把 WAR 文件部署到 Tomcat 服务器。
用 File->New->;Project... 在 Eclipse 中创建新的动态 Web 项目。在 New Project 向导中,在 Web 分类下选择 Dynamic Web Project,把项目命名为 spring2Web. See Figure 10:
图 10. 创建新的 Eclipse 动态 Web 项目
接下来,添加源文件,并如图 11 所示安排它们的位置。如果已经下载了源文件发布(请参阅 下载),可以在文件管理器中拖放文件,把这些文件添加到 src 文件夹。
图 11. src 目录的布局
添加应用程序库
与数据层代码的集成测试环境不同,WAR 文件中的 Web 应用程序必须包含它需要的全部库 JAR。图 12 显示了应当拖放到 WEB-INF/lib 目录的 JAR 文件:
图 12. WebContent 目录的布局
在图 12 中,请注意可以在有依赖项的 Spring 下载的 lib\j2ee 下找到 jstl.jar 和 servlet-api.jar。 standard.jar 标记库来自 lib\jakarta-taglibs 目录。
除了库 JAR 文件,图 12 还显示了其他配置文件的位置。请确保在继续之前找到了需要的文件。
从 Eclipse 项目导出 WAR 文件
要创建能部署到 Tomcat 的 WAR 文件,需要构建项目并把它导出为 WAR 文件。
可以在导航器的 spring2Web 项目上右击,选择 Build Project,为 Tomcat 构建项目。
把项目导出成 WAR 文件,请选择 File->Export... 。在导出向导中,选择 WAR File。Export 向导对话框如图 13 所示。把导出的 WAR 文件命名为 dwspring.war 并单击 Next。
图 13. 导出向导
fig09.gif fig10.jpg fig11.jpg fig12.jpg fig13.jpg 2008-03-05_122952.gif