Java Web 开发学习手册(三)
四、使用 Struts 2 构建 Web 应用
受伤的鹿跳得最高。
—艾米莉·狄金森
Struts 框架是一棵古老的活树,它的环形模式讲述了遗留 Java web 森林的故事。Struts 于 2001 年 6 月发布,开创了 Model-2 开发模型的基本发展,以应对 web 开发的变迁。您可以看到 Struts 的 DNA 被许多其他架构多样的基于动作的框架所吸收,这些框架是为了解决 Model-2 开发而发展的。Struts 诞生了 Tiles 框架 ,现在可以和无数的 web 框架一起使用。
由于 web 应用越来越复杂,以及来自其他不断发展的 web 框架的竞争,Struts 的流行开始失去势头。构建在经典 Struts 之上的 WebWork 框架后来与它统一起来,创建了 Struts 2 框架。Struts 2 是对经典 Struts 架构的完全重写,旨在解决上述需求。Struts 2 为 web 应用提供了架构基础,提供了自动化重复任务和分离横切关注点的架构机制,并通过配置上的约定使开发人员不必维护过多的配置代码。
一章不足以展示任何框架的全部功能,所以我在这里的目的是展示 Struts 2 web 框架的基础。从本章开始,通过后续章节,您将逐步了解现代 web 框架如何提供以 Java EE web 层模式为中心的架构基础。
Struts 2 框架概述
表 4-1 描述了 Struts 2 框架的关键特性。
表 4-1 。Struts 2 框架的主要特性
|
特征
|
描述
| | --- | --- | | Ajax 支持 | Struts 2 集成了 Ajax 支持。除了现成的 Ajax,Struts 2 还支持无数以 Ajax 为中心的插件。 | | 约定胜于配置 | Struts 2 坚持约定优先于配置的原则,消除了不必要的配置。 | | 使用注释的声明式体系结构 | Struts 2 使用注释的声明式架构减少了 XML 配置,并使配置更接近 action 类。 | | 数据变换 | Struts 2 提供了从基于字符串的表单字段值到对象或原始类型的自动类型转换,消除了在 action 类中提供转换代码的需要。 | | 依赖注入 | Struts 2 对动作使用依赖注入来与它需要的组件协作。 | | 展开性 | Struts 2 是可扩展的,因为框架中的类是基于接口的。 | | 插件架构 | 核心 Struts 行为可以用插件来增强。您可以在这里找到许多适用于 Struts 2 的插件: 【https://cwiki.apache.org/S2PLUGINS/home.html】 | | POJO 表单和操作 | 与使用 ActionForms 的传统 Struts 不同,在 Struts 2 中,您可以使用任何 POJO 来接收表单输入。任何 POJO 都可以作为一个动作。 | | 查看技术 | Struts 支持 JSP、FreeMarker、Velocity、XSLT 等多种视图。 |
在深入研究 Struts 2 之前,有必要了解一下经典 Struts(以下简称 Struts)的架构。Struts 是一个基于 MVC 的框架。Struts 框架的核心组件是 ActionServlet ,它实现了前端控制器 web 层 Java EE 模式。图 4-1 展示了 Struts 的架构。
图 4-1 。Struts 框架的体系结构
Struts 中事件 的顺序如下:
- ActionServlet 将请求处理委托给 RequestProcessor。
- RequestProcessor 处理请求并将表单值存储在 ActionForm 中。
- RequestProcessor 然后调用 Action 类。
- Action 类访问 ActionForm 来检索表单值。
- Action 类调用对服务层的调用。
- Action 类返回 ActionForward,用于封装响应视图。
然而,Struts 2 不同于 Struts。与 Struts 不同,Struts 2 遵循的是 push-MVC 架构,数据应该出现在页面或范围中,而 Struts 2 是 pull-MVC 架构;也就是说,可以从操作中提取数据。表 4-2 显示了 Struts 和 Struts 2 框架元素的一对一映射。
表 4-2 。Struts 和 Struts 2 框架元素的一一映射 ??
|
Struts 框架元素
|
Struts 2 框架元素
| | --- | --- | | 核心控制器 | Servlet 过滤器 | | 请求处理器 | 拦截机 | | 行动 | 行动 | | ActionForm(操作表单) | 行动或 POJOs | | 向前行动 | 结果 |
注意你可以在Struts . Apache . org/release/2.3 . x/docs/comparisng-Struts-1-and-2 . html找到 Struts 和 Struts 2 异同的全面列表。
图 4-2 说明了 Struts 2 的关键元素,它提供了一个更清晰的 MVC 实现。
图 4-2 。Struts 2 的架构
从图 4-2 可以看出,Struts 2 也是一个基于 MVC 的框架,实现了前台控制器模式。Struts 2 框架中的事件顺序如下:
- 该请求被映射到配置元数据。
- 请求通过一堆拦截器,这些拦截器为请求和横切特性提供预处理和后处理。
- 调用提供处理该请求的逻辑的操作和操作中的方法。
- 调用结果来呈现响应。
- 该响应被返回给用户。
接下来讨论图 4-2 中所示支柱 2 的关键元件。
动作
动作是面向动作的 Struts 2 框架的核心,因为它们为请求处理提供了必要的逻辑。实现任何接口或扩展任何类都不需要动作,动作可以是 POJOs。清单 4-1 展示了名为 HelloWorldAction 的动作。
清单 4-1 。作为 POJO 的行动
public class HelloWorldAction{
//...
public String execute() {
return "success";
}
}
这个动作类是在 struts.xml 文件中配置的,如清单 4-2 所示。
清单 4-2 。在 struts.xml 中配置操作类
<package name="helloWorld" " extends="struts-default" namespace="/ >
<action name="hello" class=" HelloWorldAction">
<result name="success"> /hello.jsp</result>
</action>
<package>
清单 4-2 中的动作映射使用 name 属性来定义你可以用来访问这个动作类的动作的名称,并使用 result 标签来定义哪个结果页面应该返回给用户。现在,您可以通过。动作扩展。
http://localhost:8080/helloWorldExample//hello.action
即使您可以使用 POJO 动作,Struts 2 也提供了两个可以使用的动作助手:动作接口和 ActionSupport 类。
动作界面
Struts 2 带有一个可选的动作接口,如清单 4-3 所示。
清单 4-3 。动作界面
package com.opensymphony.xwork2;
public interface Action {
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
public static final String NONE = "none";
public static final String SUCCESS = "success";
public String execute();
}
该接口提供了基于字符串的常量返回值和默认的 execute()方法,这些方法应该由实现类来实现。
实现这个接口的 action 类可以直接使用常量值,如清单 4-4 所示。
清单 4-4 。使用操作界面
import com.opensymphony.xwork2.Action;
public class HelloWorldAction implements Action{
//..
public String execute() {
return SUCCESS;
}
}
ActionSupport 类
ActionSupport 类实现 Action 接口,并提供返回成功值的 execute()方法的实现。ActionSupport 类还实现了一些为验证、本地化和国际化提供支持的接口,如清单 4-5 中的 ActionSupport 类的代码片段所示。
清单 4-5 。ActionSupportClass
public class ActionSupport implements Action, Validateable, ValidationAware,TextProvider, LocaleProvider, Serializable {
...
public String execute(){
return SUCCESS;
}
}
清单 4-6 显示了代码片段,通过它 ActionSupport 类可以用来提供验证。
清单 4-6 。使用动作支持
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport{
private String username;
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
//getters and setters
// ...
public String execute() {
return "SUCCESS";
}
public void validate(){
if("".equals(getUsername())){
addFieldError("username", getText("username.required"));
}
if("".equals(getPassword())){
addFieldError("password", getText("password.required"));
}
}
}
拦截器
拦截器通过将横切关注点的实现从操作中分离出来,促进了关注点的分离。Struts 2 附带了一组预构建的拦截器和拦截器堆栈,您可以开箱即用。清单 4-7 展示了一个动作的声明,该动作属于一个扩展 struts-default 包的包,该包包含默认的拦截器集。
清单 4-7 。宣告一个动作 ??
<package name="default" namespace="/" extends="struts-default">
<action name="helloAction"
class="HelloWorldAction" >
<result name="success">/hello.jsp</result>
</action>
</package>
当您从 struts-default 包扩展您的包时,默认情况下,defaultStack 将用于您的包中的所有操作。defaultStack 是在 struts-default.xml 文件中配置的,它提供了 Struts 2 的所有核心功能。struts-default.xml 文件位于 struts 2-core.jar 文件中。要将其他拦截器映射到一个动作,可以使用 interceptor-ref 元素,如清单 4-8 所示。
清单 4-8 。将拦截器映射到行动 ??
<package name="default" namespace="/" extends="struts-default">
<action name="helloAction"
class="HelloWorldAction" >
<interceptor-ref name="logger"/>
<result name="success">/hellot.jsp</result>
</action>
</package>
在清单 4-8 中,动作映射通过 interceptor-ref 元素将记录器拦截器映射到 HelloWorldAction 动作类。由于 HelloWorldAction 被声明为它自己的拦截器,它失去了默认的拦截器集,为了使用它,你必须显式声明 defaultStack,如清单 4-9 所示。
清单 4-9 。声明一个默认堆栈
<package name="default" namespace="/" extends="struts-default">
<action name="helloAction"
class="HelloWorldAction" >
<interceptor-ref name="logger"/>
<interceptor-ref name="defaultStack"/>
<result name="success">/hello.jsp</result>
</action>
</package>
ValueStack 和 OGNL
对象图导航语言(OGNL 1 )是一种功能强大的表达式语言,用于从 JavaBeans 设置和获取属性,以及从 Java 类调用方法。它还有助于数据传输和类型转换。OGNL 类似于 EL 和 JSTL,它们使用点符号来计算表达式和导航对象图。正如你在图 4-2 中看到的,OGNL 和 ValueStack 虽然不是 MVC 的一部分,但却是 Struts 2 框架的核心。所有 MVC 组件都与 ValueStack 交互以提供上下文数据。这些组件使用 OGNL 语法访问 ValueStack,在 Struts 2 中,OGNL 和 ValueStack 一起处理请求。具体来说,当一个请求被发送到 Struts 2 应用时,会为该请求创建一个 ValueStack 对象,对为该请求而创建的所有对象的引用以及范围属性都保存在 ValueStack 中。所有这些物体都可以通过 OGNL 看到。你不会发现 OGNL 很难使用,因为它类似于埃尔和 JSTL(这将在第三章中讲述)。清单 4-10 展示了 OGNL 的样子。注意,OGNL 使用#,不像 JSP EL 使用$。
清单 4-10 。使用 OGNL
<s:property value="#book.bookTitle" />
结果类型和结果
在 Struts 2 中,响应的呈现由结果类型和结果组成。结果类型提供了返回给用户的视图类型的实现细节。动作的每个方法都返回一个结果,其中包括指定结果类型。如果没有指定结果类型,那么使用默认的结果类型,它会转发到一个 JSP 页面,如清单 4-11 所示。
清单 4-11 。默认结果类型
<result name="success">
/hello.jsp
</result>
Struts 2 附带了许多预定义的结果类型。Struts 允许您使用其他视图技术来呈现结果,包括速度、FreeMarker 和 Tiles,如清单 4-12 所示。
清单 4-12 。将切片声明为结果类型
<action name="login" class="com.apress.bookstore.action.LoginAction">
<result name="success" type="tiles">home</result>
<result name="error" type="tiles">login</result>
</action>
Struts 2 标签
Struts 2 框架提供了一个高级的、可移植的标记 API,您可以将它用于 JSP。在接下来的几节中,您将了解标记如何工作,以及如何使用 OGNL 来引用 ValueStack 上的值。表 4-3 描述了 Struts 2 标签库的不同类别。
表 4-3 。Struts 标签
|Struts 2 标签
|
描述
| | --- | --- | | Ajax 标记 | Struts 通过 Ajax 标签提供 Ajax 支持。 | | 控制标签 | 这些标签提供了操作元素集合的方法。 | | 数据标签 | 这些是呈现来自动作、国际化文本和 URL 的数据的标签。 | | 表单标签 | 这些标签为 HTML 表单标签提供了包装器,还提供了额外的小部件,比如日期选择器。 | | 非表单用户界面标签 | 该组中的标签用于表单中,但不是直接的表单条目元素。它们包括错误消息显示、选项卡式面板和树视图。 |
注意你可以在Struts . Apache . org/release/2.3 . x/docs/tag-reference . html的在线文档中找到 Struts 2 标签的完整列表。
Struts 2 入门
在本节中,您将开发一个 HelloWorld Struts 2 应用。您将使用名为 Maven 的构建和依赖管理工具。 2 Maven 是一个命令行工具,用于构建和打包项目以及管理依赖关系。通过在多个项目之间提供相同的目录结构,这使得开发人员跨多个项目工作变得更加容易。
Maven 配置文件(pom.xml)中描述的显式配置和可传递的依赖项将在构建过程 中从本地存储库访问或下载到本地存储库。该特性允许开发人员创建新项目,而无需创建公共目录结构、创建配置文件以及从头开始编写默认类和测试。使用 Maven 进行运行时依赖 的好处是,你不需要手动记忆和搜索需要的依赖。您将使用 Maven 4 来导入 Struts 2 运行时依赖项。Maven 4 与 Eclipse-kepler 集成在一起,正如第一章中提到的。因此,您不必为 Eclipse 安装 Maven 插件。创建 Maven 项目 ,点击新建【其他】,如图图 4-3 所示。
图 4-3 。选择一个专家项目
在向导中选择 Maven 项目,如图图 4-4 所示。
图 4-4 。选择选项 Maven 项目
点击下一步,配置 选项,如图图 4-5 所示。
图 4-5 。配置一个 Maven 项目
点击下一步,如图 4-6 所示,选择目录、组 Id 和工件 Id。
图 4-6 。选择原型
点击下一步,输入组 ID、工件 ID 和包细节,如图图 4-7 所示。
图 4-7 。指定原型参数
单击完成。图 4-7 中工件 Id 字段中描述的名称的项目被创建。图 4-8 显示了所创建项目的目录结构 。
图 4-8 。已创建项目的目录结构
图 4-8 所示的目录结构遵循正常的 Maven 目录结构。
表 4-4 描述了 HelloWorld 应用 的目录结构。
表 4-4 。基于 Maven 的应用的目录结构
|
目录
|
描述
| | --- | --- | | 科学研究委员会 | 所有来源 | | :-主 | 主源目录 | | ::- java | Java 源代码 | | ::- helloworld | 由 groupID 参数定义的包 | | 动作 | 原型的包装 | | ::-资源 | 资源(配置、属性等) | | :- webapp | Web 应用文件 | | ::- WEB-INF | WEB-INF 文件夹 |
非 Maven HelloWorld 应用的目录结构可能类似于表 4-5 。
表 4-5 。非基于 Maven 的应用的目录结构
|
目录
|
描述
| | --- | --- | | 科学研究委员会 | 所有来源 | | :- helloworld | Helloworld 包 | | -行动 | 行动包 | | - struts.xml | 资源(配置、属性等) | | 网 | Web 应用文件 | | :- WEB-INF | WEB-INF 文件夹 |
您需要将 struts 2-core 依赖项添加到 HelloWorld 应用生成的 pom.xml 文件中。清单 4-13 显示了添加到 pom.xml 的代码片段。
清单 4-13 。struts 双核依赖
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts 2-core</artifactId>
<version>2.3.15.1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
您的 pom.xml 文件 将类似于清单 4-14 。
清单 4-14 。pom.xml
1.<project fontname">http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2\. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd">
3.<modelVersion>4.0.0</modelVersion>
4.<groupId>com.apress</groupId>
5.<artifactId>helloworldStruts2</artifactId>
6.<packaging>war</packaging>
7.<version>0.0.1-SNAPSHOT</version>
8.<name>helloworldStruts2Maven Webapp</name>
9.<url>http://maven.apache.org</url>
10.<dependencies>
11.<dependency>
12.<groupId>junit</groupId>
13.<artifactId>junit</artifactId>
14.<version>3.8.1</version>
15.<scope>test</scope>
16.</dependency>
17.<dependency>
18.<groupId>org.apache.struts</groupId>
19.<artifactId>struts2-core</artifactId>
20.<version>2.3.15.1</version>
21.<type>jar</type>
22.<scope>compile</scope>
23.</dependency>
24.</dependencies>
25.<build>
26.<finalName>helloworldStruts2</finalName>
27.</build>
28. </project>
注意你可以在Struts . Apache . org/development/2 . x/Struts 2-core/dependencies . html找到 Struts 2 的依赖项列表。
清单 4-15 显示了在项目中创建的空部署描述符 。
清单 4-15 。web . XML
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
您需要在部署描述符中配置 servlet 过滤器,如清单 4-16 所示。
清单 4-16 。web.xml
1.<!DOCTYPE web-app PUBLIC
2\. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
3\. "http://java.sun.com/dtd/web-app_2_3.dtd" >
4.
5.<web-app>
6.<display-name>Hello World Struts2 Web App</display-name>
7.<filter>
8.<filter-name>struts2</filter-name>
9.<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
10.</filter>
11.
12.<filter-mapping>
13.<filter-name>struts2</filter-name>
14.<url-pattern>/*</url-pattern>
15.</filter-mapping>
16.</web-app>
- 第 7 行到第 10 行:这几行定义了 Struts prepareendexecutefilter,它被用作 Struts 2 中的 servlet 过滤器,所有的 URL 都被映射到这个过滤器。
注意filter dispatcher(org . Apache . struts2 . dispatcher . filter dispatcher)是早期 Struts 2 开发中使用的,从 Struts 2.1.3 开始就被弃用了。
清单 4-17 展示了 struts.xml 文件。
清单 4-17 。struts . XML
1.<?xml version="1.0" encoding="UTF-8"?>
2.<!DOCTYPE struts PUBLIC
3. "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
4. "http://struts.apache.org/dtds/struts-2.0.dtd">
5.
6.<struts>
7.<constant name="struts.devMode" value="true" />
8.<package name="basicstruts2" extends="struts-default">
9.<action name="index">
10.<result>/index.jsp</result>
11.</action>
12.</package>
13.</struts>
- 第 9 行到第 11 行:这几行定义了 index 动作,该动作呈现结果 index.jspT3。
清单 4-18 展示了 index.jsp。
清单 4-18 。index.jsp
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
将 web 应用部署到 servlet 容器 Tomcat 7,打开 web 浏览器,访问localhost:8080/hello world struts 2/,如图图 4-9 所示。
图 4-9 。运行 HelloWorld 应用
您开发的 HelloWorld 应用不包含任何操作,只包含一个在您创建项目时生成的 JSP 文件。您创建 HelloWorld 应用的目的是测试 Struts 2 的配置。您将在下一节中创建操作。
您将创建一个 HelloWorld 项目 ,向用户显示一条欢迎消息,如图图 4-10 所示。
图 4-10 。HelloWorld 项目的形式
当您在 Struts 2 web 应用中提交 HTML 表单时,输入被发送到名为 Action 的 Java 类。动作执行后,结果选择一个资源来呈现响应,如图图 4-11 所示。
图 4-11 。问候用户
让我们修改你之前创建的 HelloWorld 项目,添加一个动作,接受用户输入的表单,以及问候用户的视图,如图图 4-12 所示的目录结构。
图 4-12 。项目的目录结构
清单 4-19 展示了允许用户输入名字并提交的表单。
清单 4-19 。index.jsp
1.<html>
2.<body>
3.
4.<form action="hello">
5.<label for="name">Enter your name</label><br /><input type="text"
6.name="name" /><input type="submit" value="Submit" />
7.</form>
8.</body>
9.</html>
- 第 4 行:当用户提交表单时,动作名 hello 被发送到容器。
您需要一个映射来将 URL 映射到 HelloWorldAction 控制器。映射告诉 Struts 2 框架哪个类将响应用户的动作,哪个类的方法将被执行,以及哪个视图将被呈现为响应。清单 4-20 展示了这个映射文件。
清单 4-20 。struts.xml
1.<?xml version="1.0" encoding="UTF-8"?>
2.<!DOCTYPE struts PUBLIC
3. "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
4. "http://struts.apache.org/dtds/struts-2.0.dtd">
5.
6.<struts>
7.<constant name="struts.devMode" value="true" />
8.<package name="basicstruts2" extends="struts-default"
9.namespace="/">
10.
11.<action name="index">
12.<result>/index.jsp</result>
13.</action>
14.
15.<action name="hello" class="com.apress.helloworld.action.HelloWorldAction"
16.method="execute">
17.<result name="success">/hello.jsp</result>
18.</action>
19.</package>
20.</struts>
- 第 15 行:这一行声明了 HelloWorldAction 的动作映射。HelloWorldAction 映射到操作名 hello。
- 第 16 行:这一行声明要执行动作的 execute()方法。
- 第 17 行:这一行声明 hello.jsp 被指定为一个成功页面,并将被呈现为响应。
我们需要一个动作类来充当控制器。Action 类响应提交表单并将 hello 操作发送到容器的用户操作。清单 4-21 展示了 HelloWorldAction。
清单 4-21 。HelloWorldAction.java
1.package com.apress.helloworld.action;
2.
3.public class HelloWorldAction {
4.private String name;
5.
6.public String execute() throws Exception {
7.return "success";
8.}
9.
10.public String getName() {
11.return name;
12.}
13.
14.public void setName(String name) {
15.this.name = name;
16.}
17.}
18.
- 第 6 行到第 7 行:Struts 2 框架会创建一个 HelloWorldAction 类的对象,并调用 execute 方法来响应用户的动作。execute 方法返回成功字符串,该字符串被映射到 struts.xml 中的 hello.jsp
清单 4-22 展示了 hello.jsp。
清单 4-22 。hello.jsp
1.<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2.pageEncoding="ISO-8859-1"%>
3.<%@ taglib prefix="s" uri="/struts-tags"%>
4.<html>
5.<head>
6.<title>Hello World</title>
7.</head>
8.<body>
9.Hello
10.<s:property value="name" />
11.</body>
12.</html>
- 第 3 行:taglib 指令告诉 servlet 容器这个页面将使用 Struts 2 标签。
- 第 10 行:s:property 标签显示调用 HelloWorldAction 类的方法 getName 返回的值。getName 方法返回一个字符串。s:property 标记显示的就是 getName 返回的这个字符串。
现在,您将学习 Struts 2 提供的一种不同的声明性配置技术:注释。您可以创建新项目或修改之前创建的项目。图 4-13 说明了目录结构。
图 4-13 。HelloWorld 项目使用 Struts 2 标注
要使用 Struts 2 注释,需要名为 Struts 2-conventi on-plugin 的插件。使用清单 4-23 中所示的片段将依赖项添加到 pom.xml 文件中。
清单 4-23 。struts 2-约定-插件
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>2.3.15.1</version>
</dependency>
清单 4-24 展示了用 Struts 2 注释配置的 HelloWorldAction。
清单 4-24 。hello world action
1.package com.apress.helloworld.action;
2.
3.import org.apache.struts2.convention.annotation.Action;
4.import org.apache.struts2.convention.annotation.Result;
5.
6.@Action(value = "/hello", results = { @Result(name = "success", location = "/hello.jsp") })
7.public class HelloWorldAction {
8.private String name;
9.
10.public String execute() throws Exception {
11.return "success";
12.}
13.
14.public String getName() {
15.return name;
16.}
17.
18.public void setName(String name) {
19.this.name = name;
20.}
21.}
- 第 6 行 : @Action 定义了一个动作的 URL。因为动作注释的值是“/hello”,所以将为请求 URL“/hello”调用动作。
- 第 6 行 : @Result 定义了一个动作的结果。结果注释将结果代码映射到结果页面。这里,结果代码“success”被映射到结果“/hello.jsp”。
清单 4-24 使用动作和结果注释只是为了向你展示如何使用它们。您还可以使用约定插件提供的智能默认值。如果在 web.xml 中将 actionPackages filter init 参数设置为包含 action 类的包的逗号分隔列表,如清单 4-25 所示,包及其子包将被扫描。检查指定包中实现 Action 的所有类或没有实现 Action 接口并以 Action 结束的 POJO 操作。
清单 4-25 。actionPackages Init 参数
<init-param>
<param-name>actionPackages</param-name>
<param-value>com.apress.helloworld.action</param-value>
</init-param>
约定插件使用动作类名来映射动作 URL。约定插件首先删除类名末尾的单词 Action,然后将大小写转换成破折号。因此,默认情况下,将为请求 URL hello-world 调用 HelloWorldAction。但是,如果您希望为不同的 URL 调用操作,那么您可以通过使用操作注释来实现。
提示你可以在Struts . Apache . org/release/2.3 . x/docs/annotations . html找到所有 Struts 2 注释的列表。
在接下来的部分中,您将使用 Struts 2 开发书店 web 应用。
书店网络应用
在本节中,您将逐步开发包括以下功能的书店应用:
- 登录功能
- 模板
- 与数据访问层的集成
- 通过数据库登录
- 从数据库中选择类别
- 按类别列出书籍
该应用的完整代码可从 Apress 网站的可下载档案中获得。
登录功能
图 4-14 显示了初始登录屏幕。
图 4-14 。书店应用的登录页面
当用户提交有效的用户名和密码组合时,用户将登录,并显示用户名,如图图 4-15 所示。
图 4-15 。登录成功
如果用户输入不正确的用户名或密码,将向用户显示一条错误消息,如图图 4-16 所示。
图 4-16 。登录失败
图 4-17 说明了应用的目录结构。您可以从 Apress 网站下载该应用的源代码,然后在阅读每一部分时,您可以参考导入和其他工件的源代码。
图 4-17 。书店应用的目录结构
您可以将 struts 2-core 依赖项添加到 pom.xml 文件中,如清单 4-26 所示。
清单 4-26 。struts 双核依赖
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts 2-core</artifactId>
<version>2.3.15.1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
您可以使用之前在 HelloWorld 项目中使用的相同的 web.xml 文件。修改欢迎文件列表文件,如清单 4-27 所示。
清单 4-27 。欢迎文件
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
清单 4-28 展示了 login.jsp 文件。
清单 4-28 。login.jsp
1.<%@ page contentType="text/html; charset=UTF-8"%>
2.<%@ taglib prefix="s" uri="/struts-tags"%>
3.<html>
4.<head>
5.<title>Bookstore Login</title>
6.</head>
7.<body>
8.<h3>Login Bookstore</h3>
9.<s:actionerror />
10.<s:form action="login.action" method="post">
11.<s:textfield name="username" key="label.username" size="30" />
12.<s:password name="password" key="label.password" size="30" />
13.<s:submit method="execute" align="center" />
14.</s:form>
15.</body>
16.</html>
清单 4-28 展示了几个 Struts 2 标签的用法。这是一个允许用户输入用户名和密码的登录表单。在第 10 行,当用户提交表单时,动作 login 的名称被发送到容器。这个动作名通过 struts.xml 映射到 LoginAction,如清单 4-29 所示。
清单 4-29 。struts.xml
1.<?xml version="1.0" encoding="UTF-8" ?>
2.<!DOCTYPE struts PUBLIC
3."-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
4."http://struts.apache.org/dtds/struts-2.3.dtd">
5.
6.
7.<struts>
8.<constant name="struts.enable.DynamicMethodInvocation" value="false" />
9.<constant name="struts.devMode" value="true" />
10.<constant name="struts.custom.i18n.resources" value="ApplicationResources" />
11.
12.<package name="default" extends="struts-default" namespace="/">
13.<action name="login" class="com.apress.bookstore.action.LoginAction">
14.<result name="success">view/home.jsp</result>
15.<result name="error">login.jsp</result>
16.</action>
17.</package>
18.</struts>
在清单 4-29 中,第 13 行将登录名的 LoginAction 类映射到 URL,当 LoginAction 类返回字符串 success 时,第 14 行呈现 home.jsp。如果 LoginAction 类返回字符串 error,则 login.jsp 将再次显示错误消息。
清单 4-30 展示了登录操作。
清单 4-30 。LoginAction.java
1.package com.apress.bookstore.action;
2.import com.opensymphony.xwork2.ActionSupport;
3.public class LoginAction extends ActionSupport {
4.private String username;
5.private String password;
6.public String execute() {
7.if (this.username.equals("vishal") && this.password.equals("password")) {
8.return "success";
9.} else {
10.addActionError(getText("error.login"));
11.return "error";
12.}
13.}
14.public String getUsername() {
15.return username;
16.}
17.public void setUsername(String username) {
18.this.username = username;
19.}
20.
21.public String getPassword() {
22.return password;
23.}
24.
25.public void setPassword(String password) {
26.this.password = password;
27.}
28.}
在清单 4-30 的第 7 到 9 行,用户名和密码是硬编码的。稍后,您将看到如何针对数据库进行身份验证。如果用户名或密码无效,第 10 行中的 addActonError 方法获取映射到 application resources . properties 的 Error.login 消息,如清单 4-31 所示,并返回字符串 error。
清单 4-31 。应用资源.属性
label.username= Username
label.password= Password
error.login= Invalid Username/Password
清单 4-32 展示了 home.jsp,它是在 LoginAction 类返回字符串 success 时呈现的。
清单 4-32 。home.jsp
1.<%@ page contentType="text/html; charset=UTF-8"%>
2.<%@ taglib prefix="s" uri="/struts-tags"%>
3.<html>
4.<head>
5.<title>Home</title>
6.</head>
7.<body>
8.<s:property value="username" />
9.</body>
10.</html>
清单 4-32 当 LoginAction 返回字符串 success 时,使用 s:property 标签显示用户名。
开发模板
在本节中,您将看到如何将 Tiles 框架与 Struts 2 集成。我们将为上一节中创建的 HelloWorld Struts 应用添加 Tiles 支持。Tiles 是一个模板系统,可以减少代码重复,并在 web 应用的所有页面上保持一致的外观。使用磁贴,您可以在配置文件中定义一个通用布局,该布局将扩展到 web 应用的所有网页。这意味着您可以通过更改模板文件来更改 web 应用所有页面的外观,而不是更改所有页面。对于书店应用,您将添加一个标题和菜单,如图图 4-18 所示。
图 4-18 。书店应用的模板
图 4-19 显示了带有标题的登录屏幕。认证成功后,用户登录,呈现带有菜单栏的主页,如图图 4-20 所示。如果用户名或密码无效,用户将停留在登录屏幕上,并显示一条错误消息,如图 4-21 中的所示。
图 4-19 。带标题的登录屏幕
图 4-20 。成功登录时显示的菜单
图 4-21 。登录失败,标题为
用户登录后,表头显示用户名,菜单出现,如图图 4-20 所示。
图 4-22 说明了与模板文件的目录结构。
图 4-22 。模板文件的目录结构
你需要用 struts 2-tiles3-plugin 和 slf4j-log4j12 修改 pom.xml 文件,如清单 4-33 所示。这些插件是将 Tiles 与 Struts 2 集成所必需的。
清单 4-33 。struts-tiles 和 slf4j-log4j12 插件
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-tiles3-plugin</artifactId>
<version>2.3.15.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.6</version>
</dependency>
清单 4-34 展示了 struts.xml 文件。
清单 4-34 。struts.xml
1.<?xml version="1.0" encoding="UTF-8" ?>
2.<!DOCTYPE struts PUBLIC
3."-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
4."http://struts.apache.org/dtds/struts-2.3.dtd">
5.
6.
7.<struts>
8.<constant name="struts.enable.DynamicMethodInvocation" value="false" />
9.<constant name="struts.devMode" value="true" />
10.<constant name="struts.custom.i18n.resources" value="ApplicationResources" />
11.
12.<package name="default" extends="struts-default" namespace="/">
13.<result-types>
14.<result-type name="tiles"
15.class="org.apache.struts2.views.tiles.TilesResult" />
16.</result-types>
17.<action name="*Link" method="{1}"
18.class="com.apress.bookstore.action.LinkAction">
19.<result name="login" type="tiles">login</result>
20.<result name="allBooks" type="tiles">allBooks</result>
21.</action>
22.<action name="login" class="com.apress.bookstore.action.LoginAction">
23.<result name="success" type="tiles">home</result>
24.<result name="error" type="tiles">login</result>
25.</action>
26.<action name="logout">
27.<result name="success" type="tiles">logout</result>
28.</action>
29.</package>
30.</struts>
- 第 19、20、23、24 和 27 行:这些行将结果类型定义为 tiles 和 tiles.xml 中定义的 tiles 名称,如 home 和 login。根据操作返回的结果字符串,换句话说,成功或错误,tiles.xml 中定义的相应 JSP 文件将使用 tiles.xml 中的定义名称映射到 struts.xml 中的 Tiles 名称。
清单 4-35 展示了 tiles.xml 文件。
清单 4-35 。tiles.xml
1.<?xml version="1.0" encoding="UTF-8" ?>
2.
3.<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
4.
5.<tiles-definitions>
6.
7.<definition name="baseLayout" template="/template/baseLayout.jsp">
8.<put-attribute name="title" value="Template" />
9.<put-attribute name="header" value="/template/header.jsp" />
10.<put-attribute name="menu" value="/template/menu.jsp" />
11.<put-attribute name="body" value="/template/body.jsp" />
12.</definition>
13.
14.<definition name="login" extends="baseLayout">
15.<put-attribute name="title" value="Log in" />
16.<put-attribute name="menu" value="" />
17.<put-attribute name="body" value="/login.jsp" />
18.</definition>
19.<definition name="error" extends="baseLayout">
20.<put-attribute name="title" value="Log in" />
21.<put-attribute name="menu" value="" />
22.<put-attribute name="body" value="/login.jsp" />
23.</definition>
24.<definition name="home" extends="baseLayout">
25.<put-attribute name="title" value="Log in" />
26.<put-attribute name="menu" value="/template/menu.jsp" />
27.<put-attribute name="body" value="/view/home.jsp" />
28.</definition>
29.<definition name="logout" extends="baseLayout">
30.<put-attribute name="title" value="Log in" />
31.<put-attribute name="menu" value="" />
32.<put-attribute name="body" value="/login.jsp" />
33.</definition>
34.
35.<definition name="allBooks" extends="baseLayout">
36.<put-attribute name="title" value="All Books" />
37.<put-attribute name="body" value="/allBooks.jsp" />
38.</definition>
39.
40.
41.</tiles-definitions>
清单 4-36 展示了 baselayout.jsp 文件,它定义了页眉、页脚和正文内容应该插入的位置。
清单 4-36 。基地布局
1.<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
2.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
3. "http://www.w3.org/TR/html4/loose.dtd">
4.
5.<html>
6.<head>
7.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
8.<link rel="stylesheet" href="css/bookstore.css" type="text/css" />
9.<script type="text/javascript" src="js/jquery-1.9.1.js"></script>
10.<script src="js/bookstore.js"></script>
11.<title><tiles:insertAttribute name="title" ignore="true" /></title>
12.</head>
13.<body>
14.<div id="centered">
15.
16.
17.<tiles:insertAttribute name="header" />
18.
19.<tiles:insertAttribute name="menu" />
20.
21.<tiles:insertAttribute name="body" />
22.
23.
24.</div>
25.</body>
26.</html>
清单 4-37 展示了 header.jsp 文件。
清单 4-37 。header.jsp
1.<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2.pageEncoding="ISO-8859-1"%>
3.<%@ taglib prefix="s" uri="/struts-tags"%>
4.
5.<div class="header">
6.<h2>
7.<span style="margin-left: 15px; margin-top: 15px;" class="label">BOOK
8.<span style="color: white;">STORE</span>
9.</span>
10.</h2>
11.<span style="color: black; margin-left: 15px;">
12.
13.
14.<s:if test="%{username!=null && !hasActionErrors() }">Welcome <s:property value="username" /> | <a href='<s:url action="logout.action"/>'>Log out</a></s:if>
15.<s:else>
16.Login
17.</s:else>
18.
19.
20.
21.
22.</span>
23.
24.</div>
25.
第 14 到 17 行使用 Struts 2 s:if、s:else 和 s:property 标记来欢迎用户登录并允许用户注销。由于该代码位于标题中,因此应用中的所有页面都可以使用该功能。
清单 4-38 展示了 web.xml 文件。
清单 4-38 。web.xml
1.<!DOCTYPE web-app PUBLIC
2\. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
3\. "http://java.sun.com/dtd/web-app_2_3.dtd" >
4.
5.<web-app>
6.<display-name>Archetype Created Web Application</display-name>
7.<context-param>
8.<param-name>org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG</param-name>
9.<param-value>/WEB-INF/tiles.xml</param-value>
10.</context-param>
11.
12.<filter>
13.<filter-name>struts2</filter-name>
14.<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
15.</filter>
16.<filter-mapping>
17.<filter-name>struts2</filter-name>
18.<url-pattern>/*</url-pattern>
19.</filter-mapping>
20.<listener>
21.<listener-class>org.apache.tiles.extras.complete.CompleteAutoloadTilesListener</listener-class>
22.</listener>
23.<welcome-file-list>
24.<welcome-file>index.jsp</welcome-file>
25.</welcome-file-list>
26.
27.</web-app>
需要第 7 行到第 10 行和第 21 行到第 22 行来配置带有 Struts 2 的图块。
集成数据访问层
在本节中,您将把您创建的 web 应用与数据访问层集成在一起。您可以使用您在第一章中创建的数据访问层。然后,根据数据库对用户进行身份验证。接下来,您将在菜单栏上显示书店数据库中的类别。然后,您将按类别从数据库中检索图书列表。图 4-23 说明了应用的目录结构 。
图 4-23 。带有数据访问层的目录结构
正如您在目录结构中看到的,应用中只有一个 BookController 。清单 4-39 中说明了该控制器中的方法。
清单 4-39 。记账员
1. public class BookController extends ActionSupport {
2.
3. //properties
4\.
5. public String login() {}
6.
7. public String executelogin() {}
8.
9. public String error() {}
10.
11. public String allBooks() {}
12.
13. public String booksByCategory() {}
14.
15. public String searchByKeyword() {}
16.
17. public String home() {}
18.
19. public String selectedBooks(){}
20.
21. public String logout() {}
22.
23. // getters and setters
24.
25. }
- 第 5 行:负责显示登录表单。
- 第 7 行:根据数据库认证用户。
- 第 9 行:显示错误信息,例如用户无效。
- 第 11 行:列出书店里所有的书。
- 第 13 行:按类别列出书籍。
- 第 15 行:允许用户通过关键字搜索书籍:书名或作者名。
- 第 17 行:点击首页链接,显示首页。
- 第 19 行:显示所选书籍列表。
- 第 21 行:允许用户注销。
使用数据库登录
清单 4-40 说明了 executelogin()方法负责对数据库中的用户进行认证。为此,您需要使用以下 DDL 将用户表添加到您在第一章中开发的数据模型:
CREATE TABLE USER(
ID INT NOT NULL AUTO_INCREMENT,
FIRST_NAME VARCHAR(60) NOT NULL,
LAST_NAME VARCHAR(60) NOT NULL,
USERNAME VARCHAR(60) NOT NULL,
PASSWORD VARCHAR(60) NOT NULL,
PRIMARY KEY (ID)
);
清单 4-40 。BookController 中的 executelogin()方法
1. public String executelogin() {
2. String executelogin = "failed";
3. session = ActionContext.getContext().getSession();
4. dao = new BookDAOImpl();
5. user = new User();
6. user.setUserName(getUsername());
7. user.setPassword(getPassword());
8. setUser(user);
9. if (dao.isUserAllowed(user)) {
10.
11. setCategoryList(dao.findAllCategories());
12. session.put("username", username);
13. session.put("categoryList", getCategoryList());
14. executelogin = "success";
15. }
16. else {
17. addActionError(getText("error.login"));
18. return "error";
19. }
20. // return result;
21. return "executelogin";
22. }
图 4-24 说明了用户表。
图 4-24 。用户表
- 第 9 行:用户通过数据库的认证。正如您在第 9 行中看到的,通过调用 DAO 上的 isUserAllowed(),替换了清单 4-31 中硬编码的用户名和密码。isUserAllowed()方法根据登录表单中输入的用户名和密码,从结果集中的用户表(如图图 4-24 所示)中选择用户名和密码。
- 第 11-14 行:如果用户有效,则从数据库中检索类别,并将类别列表存储在会话中。
- 第 16-19 行:第 16 行将有效布尔变量设置为 true,如果结果集包含用户名和密码,如果用户无效,则返回字符串错误。
显示从数据库中检索到的类别
清单 4-41 展示了显示从数据库返回的类别的菜单。在清单 4-40 中,您检索了类别并将它们存储在有效用户的会话中。这些类别将在 menu.jsp 展示。
清单 4-41 。menu.jsp
1.<li><div>
2.
3.<span class="label" style="margin-left: 15px;">
4.<a href="<s:url action=""/>">Categories</a></span>
5.</div>
6.<ul>
7.<li><s:form action=" booksByCategoryLink">
8.
9.<s:select name="category" list="#session['categoryList']"
10.listValue="categoryDescription" listKey="id" />
11.<s:submit value="Select" />
12.</s:form><a class="label" href=""><span class="label"
13.style="margin-left: 30px;"></span></a></li>
14.
15.</ul></li>
- 第 9 行到第 10 行:这些行使用 Struts 2 的表达式语言显示存储在 LoginAction 会话中的列表中的类别。
- 第 11 行:当用户点击 select 按钮时,第 7 行中选择的类别和动作名称 booksByCategoryLink 被发送到容器中,然后通过 struts.xml 映射到动作,该动作检索图书列表。
按类别列出图书
在本节中,您将从数据库中按类别检索图书列表。选定的类别和动作名称 booksByCategoryLink 被发送到容器,然后通过 struts.xml 映射到 BookController。清单 4-42 展示了 struts.xml 的代码片段
清单 4-42 。用 struts.xml 声明 BookController
1. <action name="*Link" method="{1}"
2. class="com.apress.bookstore.controller.BookController">
3. <result name="login" type="tiles">login</result>
4. <result name="allBooks" type="tiles">booklist</result>
5. <result name="booksByCategory" type="tiles">booklist</result>
6. <result name="searchByKeyword" type="tiles">booklist</result>
7. <result name="home" type="tiles">home</result>
8. <result name="executelogin" type="tiles">executelogin</result>
9. <result name="selectedBooks" type="tiles">selectedBooks</result>
10. <result name="logout" type="tiles">logout</result>
11. <result name="error" type="tiles">error</result>
12. </action>
清单 4-42 中第一行的是通配符。任何以 Link 结尾的操作名称值都将由该操作映射处理。Link 之前的任何值都将是用于方法属性的值(占位符{1}将被替换为该值)。因此,不用在这个简单应用的配置文件中编写九个单独的操作映射节点,只需在名称值中使用通配符并在方法值中使用属性值占位符({1})。这使得 Struts 2 框架能够在运行时动态选择要调用的正确方法。清单 4-43 展示了 booksByCategory()。
清单 4-43 。booksByCategory()
public String booksByCategory() {
dao = new BookDAOImpl();
setBookList(dao.findBooksByCategory(category));
return "booksByCategory";
}
当列表 4-43 中的 booksByCategory()返回时,它被映射到图块名称 booklist,如列表 4-42 的第 5 行所示。这映射到 tiles.xml 中定义的图书列表,如呈现 booklist.jsp 文件的清单 4-44 所示。清单 4-44 展示了 tiles.xml 中的代码片段
清单 4-44 。tiles.xml
1.<definition name="booklist" extends="baseLayout">
2.<put-attribute name="title" value="Log in"/>
3.<put-attribute name="menu" value="/menu.jsp"/>
4.<put-attribute name="body" value="/view/bookList.jsp"/>
5.</definition>
清单 4-45 说明了 bookList.jsp。
清单 4-45 。booklist.jsp
1.<%@ taglib uri="/struts-tags" prefix="s"%>
3.<body>
5.<div id="centered">
8.<s:form action=" selectedbooksLink" theme="simple">
9.<center>
10.<table id="grid">
11.<thead>
12.<tr>
13.<th id="th-title">Book Title</th>
14.<th id="th-author">Author</th>
15.<th id="th-price">Price</th>
16.</tr>
17.</thead>
20.<tbody>
22.<s:iterator value="bookList" id="book">
23.<tr>
25.<td>
26.<s:checkboxname="selectedBooks" fieldValue="%{bookId}" />
27\. <s:propertyvalue="#book.bookTitle" />
29.</td>
30.<td>
31.<s:iterator value="#book.authors" id="author">
32.<s:if test="%{#book.id == #author.bookId}">
33.<s:property value="#author.firstName" />
34.<s:property value="#author.lastName" />
35.</s:if>
36.</s:iterator>
37.</td>
39.<td><s:property value="price" /></td>
40.</tr>
42.</s:iterator>
43.</tbody>
45</table>
47.</center><br>
49.<s:submit value="Add to the shopping cart" />
51.</s:form>
52.
53.</div>
54.</body>
清单 4-45 展示了 Struts 2 标签的用法,以及 Struts 2 OGNL 如何用于导航第 27 行的属性 bookTitle 以及第 33 和 34 行的属性 firstName 和 lastName(作者的)。第 31 行演示了简单的嵌套 s:iterator 的用法。第 26 行允许用户选择要添加到购物车的书籍。用户可以通过点击第 49 行上的按钮来选择和提交图书。当用户单击“添加到购物车”按钮时,第 8 行的操作名称 selectedbooksLink 和第 26 行的 bookId 被发送到容器,然后通过 struts.xml 映射到 Add to Cartaction。然后,AddToCart 操作将图书存储到数据库中的购物车,AddToCart 操作返回字符串 success,该字符串通过 struts.xml 中的操作映射和 tiles.xml 文件中的 tiles 映射到视图 selectedbooksLink。用户可以单击 selectedBooks.jsp 页面上的 Purchase 按钮,以同样的方式调用 PurchaseAction。本章简要概述了 Struts 2。关于 Struts 2 的更详细的报道,我推荐伊恩·罗维利的实用 Apache Struts 2 Web 2.0 项目。
摘要
在本章中,您看到了遵循 MVC 设计模式的 Struts 2 框架的核心组件,并且看到了 Struts 2 如何通过拦截器来分离横切关注点。您看到了 Struts 2 框架如何以两种形式提供声明式架构:XML 和注释。在下一章,你将学习另一个面向动作的框架,叫做 Spring Web MVC。
【http://commons . Apache . org/proper/commons-every/
五、使用 Spring Web MVC 构建 Java Web 应用
你能做的任何事,我都能做到。
-丹尼尔丹尼特
马克·吐温曾经说过:“在春天,我已经数出了 136 种不同的天气。”几乎可以肯定,他指的不是 Spring 框架。或者他是千里眼?Spring 框架已经成长为一个项目生态系统;它包括许多不同的模块,集成了许多框架和库,并在不同的领域提供了各种各样的功能,如 Flash、企业应用、web 服务、数据存储、OSGi、 1 甚至. net。Spring 应用在所有流行的云平台上都得到支持,如 Cloud Foundry、2Google App Engine、 亚马逊 EC2 3 可以利用传统的 RDBMSs 以及新的 NoSQL 4 解决方案和数据存储,如 PostgreSQL、 5 MySQL、MongoDB、 6 和 Redis。 与许多其他框架(如 Struts)不同,它只限于开发 web 应用,Spring 框架可以用来构建独立的、web 的和 JEE 的应用。Spring 支持构建现代 web 应用,包括 REST、HTML5 和 Ajax,以及移动客户端平台,包括 Android 和 iPhone。Spring 框架通过将组件与系统连接起来,使您不必编写管道代码,从而使您能够专注于应用的业务,从而显著地永远改变了企业 Java 的前景。
Spring 框架概述
Spring 框架由以下几类特性组成:
- AOP 和仪器
- 核心容器
- 数据访问/集成
- 网
- 试验
以下各节描述了每个类别中的模块。
AOP 和仪器
AOP 和仪器类别包括 AOP、方面和仪器模块,如表 5-1 中所述。
表 5-1 。AOP 模块
|组件
|
描述
| | --- | --- | | 面向切面编程 | Spring 的 AOP 模块提供了一个符合 AOP 联盟的面向方面的编程实现。 | | 方面 | 方面模块提供了与 AspectJ 的集成。 | | 使用仪器 | 插装模块提供了类插装支持和类加载器实现。 |
核心容器
核心容器类别由 Beans、核心、上下文和表达式语言模块组成,如表 5-2 中所述。
表 5-2 。核心模块
|组件
|
描述
| | --- | --- | | 豆子 | Beans 模块提供了 IoC 和依赖注入特性。 | | 核心 | 核心模块提供了 IoC 和依赖注入特性。 | | 语境 | 上下文模块建立在核心和 Beans 模块的基础上,并增加了对国际化、事件传播、资源加载、EJB 和 JMX 的支持。 | | 表达语言 | 表达式语言模块提供了 Spring 表达式语言。 |
数据访问/集成
数据访问/集成层由 JDBC、ORM、OXM、JMS 和事务模块组成,如表 5-3 所述。
表 5-3 。数据访问/集成模块
|模块
|
描述
| | --- | --- | | 数据库编程 | JDBC 模块提供了一个 JDBC 抽象层,消除了使用纯 JDBC 的需要。 | | 对象关系映射(Object Relation Mapping) | ORM 模块提供了 ORM 框架的集成,如 JPA、JDO、Hibernate 和 iBatis。 | | 泌酸调节素 | OXM 模块支持 JAXB、Castor、XMLBeans、JiBX 和 XStream 的对象/XML 映射实现。 | | (同 JavaMessageService)Java 消息服务 | JMS 模块提供了生成和使用消息的功能。 | | 事务 | 事务模块支持编程式和声明式事务管理。 |
试验
测试类别由测试模块组成,如表 5-4 所述。
表 5-4 。测试模块
|模块
|
描述
| | --- | --- | | 试验 | 测试模块支持用 JUnit 或 TestNG 测试 Spring 组件。 |
网
web 层由 Web、Web-Servlet、Web-Struts 和 Web-Portlet 模块组成,如表 5-5 中所述。
表 5-5 。Web 模块
|模块
|
描述
| | --- | --- | | 网 | Spring 的 web 模块提供了基本的面向 web 的集成特性和面向 Web 的应用上下文。它还提供了几个远程选项,比如远程方法调用(RMI)、Hessian、Burlap、JAX-WS 和 Spring 自己的 HTTP invoker。 | | Web-Servlet | Web-Servlet 模块包含了 Spring 的 Web 应用的模型-视图-控制器(MVC)实现。 | | Web-Struts | Web-Struts 模块支持在 Spring 应用中集成传统的 Struts web 层。请注意,从 Spring 3.0 开始,这种支持已经过时了。 | | 门户网站 | Web-Portlet 模块提供了在 Portlet 环境中使用的 MVC 实现。 |
注意Spring 框架的核心是基于控制反转(IoC)的原理,它提供依赖注入。然而,Spring 并不是唯一提供依赖注入特性的框架;还有其他几个框架,比如 Seam 8 、Google Guice 9 ,以及 JEE6 和更新的版本都提供依赖注入。
在撰写本文时,预计将发布 Spring 3 . 2 . 2;Spring 3.1 发布于 2011 年 12 月。Spring Framework 4.0 预计在 2013 年底发布,计划支持 Java SE 8、Groovy 2 和 Java EE 7 的某些方面。
Spring 框架基础知识
一个应用由 web 组件和业务逻辑组件等组件组成。这些组件需要相互协作来实现应用的共同业务目标,因此这些组件相互依赖。如果不加以控制,这种依赖性通常会导致它们之间的紧密耦合,从而导致应用不可维护。控制这种耦合以使它不会导致一个紧密耦合的应用是一项不简单的任务。相比之下,如果应用的一个组件不依赖于另一个组件,它就不必去寻找它,所有的组件都是完全隔离的,产生的应用将是松散耦合的。但是这样的应用什么都不会做。本质上,组件应该依赖于其他组件,但不应该寻找它们所依赖的组件。相反,这种依赖关系应该提供给依赖组件。这就是控制反转的本质。Spring 框架就是这样一个 IoC 框架,它通过依赖注入的方式为依赖组件提供依赖。
注 IoC 基于好莱坞原则 10 :“不要召唤我们;我们会打电话给你。”
图 5-1 是 Spring 如何工作的高级视图。
图 5-1 。Spring IoC 容器
如图 5-1 所示,Spring IoC 容器通过使用应用 POJO 对象和配置元数据产生完全配置的应用对象。
- 应用 POJO 对象 :在 Spring 中,由 Spring IoC 容器管理的应用对象被称为bean。Spring bean 是由 Spring IoC 容器实例化、组装和管理的对象。
注春天,组件也叫豆。Spring beans 不同于 JavaBeans 约定。Spring beans 可以是任何普通的旧 Java 对象(POJOs)。POJO 是一个普通的 Java 对象,没有任何特定的要求,比如扩展特定的类或实现特定的接口。
- 配置元数据 :配置元数据指定了组成应用的对象以及这些对象之间的相互依赖关系。容器读取配置元数据,并从中判断出要实例化、配置和组装哪些对象。然后,容器在创建 bean 时注入这些依赖项。配置元数据可以用 XML、注释或 Java 代码来表示。
就实现而言,Spring 容器可以在实例和静态方法的参数中注入对象,并可以通过依赖注入来注入构造函数。假设您有一个应用,它的组件 ClassA 依赖于 ClassB。换句话说,ClassB 是依赖关系。你的标准代码看起来会像清单 5-1 中的。
清单 5-1 。紧密耦合依赖
1. public class ClassA {
2. private ClassB classB;
3. public ClassA() {
4. classB = new ClassB();
5. }
6. }
我们在清单 5-1 的第 3-4 行中创建了 ClassA 和 ClassB 之间的依赖关系。这将 ClassA 和 ClassB 紧密地结合在一起。这种紧密耦合可以使用 IoC 来规避,为了做到这一点,首先我们需要将清单 5-1 中的代码改为清单 5-2 中的代码。
清单 5-2 。拆除 a 级和 b 级之间的紧耦合
1. public class ClassA {
2. private ClassB classB;
3. public ClassA(ClassB classB) {
4. this.classB = classB;
5. }
6. }
在清单 5-2 中可以看到,ClassB 是独立实现的,Spring 容器在 ClassA 实例化的时候提供了 ClassB 到 ClassA 的这种实现,依赖关系(换句话说就是 class ClassB)通过构造函数注入到 ClassA 中。因此,控制权已经从 ClassA 中移除并保存在别处(也就是说,在一个 XML 配置文件中,如清单 5-3 所示),并因此被依赖注入(DI)所“反转”,因为依赖被委托给外部系统,换句话说,配置元数据。清单 5-3 展示了一个典型的 Spring 配置元数据文件。
清单 5-3 。配置元数据
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans fontname">http://www.springframework.org/schema/beans"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://www.springframework.org/schema/beans
5. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
6.
7.
8. <bean id=" .." class=".."/>
9.
10. <!-- more bean definitions -- >
11.
12. </beans>
- 第 2 行:要使用、< bean >等标签,需要声明某些名称空间。核心 Spring 框架附带了 10 个配置名称空间。现在让我们专注于 beans 名称空间;随着本章的进展,将使用其他名称空间。然而,在本书中,我们只需要 aop、beans 和上下文模式,所以如果你对使用其他模式感兴趣,你可以在
static . springsource . org/spring/docs/3 . 2 . 2 . release/spring-framework-reference/html/xsd-config . html找到它们。 - 第 2 行:在基于 XML 的配置中描述 bean 时,XML 文件的根元素是 Spring 的 bean 模式中的。整个 Spring 配置,包括< bean >声明,都放在顶层中。
- 线 8 : <豆>是春季最基本的配置单位。它告诉 Spring 为应用创建一个对象。这些< bean >定义对应于组成应用的实际对象。
- 第 8 行:id 属性是一个帮助识别单个 bean 定义的字符串。class 属性定义了 bean 的类型,并使用完全限定的类名。id 属性的值指的是协作对象。
注意虽然 XML 是定义配置元数据的经典方式,但是您可以使用注释(来自 Spring 2.5 和更新版本)或 Java 代码(来自 Spring 3.0 和更新版本)。
清单 5-4 说明了应该包含在清单 5-3 中说明的配置元数据文件中的 bean 定义,以在清单 5-2 中注入 classB 依赖。
清单 5-4 。配置依赖关系
1. <!-- Definition for classA bean -->
2. <bean id="classA" class="ClassA">
3. <constructor-arg ref="classB"/>
4. </bean>
5.
6. <!-- Definition for classB bean -->
7. <bean id="classB" class="ClassB">
8. </bean>
- Line 2 :这指定了由 Spring 容器创建和管理的 classA bean。
- 第 3 行 : < constructor-arg >通过在元素中声明 bean 属性,经由构造函数注入来配置 bean 属性。
- 第 7 行:指定 classB bean 应该由 Spring 容器创建和管理。
清单 5-4 中描述的依赖注入被称为基于构造器的依赖注入。当容器调用带有许多参数的类构造函数时,基于构造函数的 DI 就完成了,每个参数表示对另一个类的依赖。DI 的另一个变种叫做基于设置器的依赖注入。在基于 setter 的 DI 中,容器在调用无参数构造函数或无参数静态工厂方法实例化 bean 后,调用 bean 上的 setter 方法。要使用基于 setter 的 DI,您需要修改清单 5-2 中的,使其看起来像清单 5-5 中的。
清单 5-5 。注入依赖关系的 Setter 方法
1. public class ClassA{
2. private ClassB classB;
3.
4. // a setter method to inject the dependency.
5. public void setClassB(ClassB classB) {
6. this.classB = classB;
7. }
8. // a getter method to return classB
9. public ClassB getClassB() {
10. return classB;
11. }
12.
13. }
在清单 5-5 中,DI 通过创建 ClassB 实例的 ClassA 类的 setter 方法发生,这个实例用于调用 setter 方法来初始化 ClassA 的属性。清单 5-6 展示了 bean 定义,它应该包含在清单 5-3 中展示的配置元数据文件中,以实现清单 5-5 中要求的基于 setter 的依赖注入。
清单 5-6 。通过基于 Setter 的 DI 配置依赖关系
<bean id="classA" class="ClassA">
<property name="classB" ref="classB" />
</bean>
<bean id="classB" class="ClassB" />
标签为依赖注入定义了一个属性。清单 5-6 可以被翻译成清单 5-7 所示的 Java 代码。
清单 5-7 。Java 代码相当于清单 5-6 中的
ClassA classA = new ClassA();
ClassB classB = new ClassB();
classA.setClassB(classB);
提示基于构造函数的 DI 和基于设置器的 DI 可以同时使用,但是建议对强制依赖项使用构造函数参数,对可选依赖项使用设置器。
Spring 容器本质上是一个工厂,它创建封装了对象创建的对象,并使用配置元数据来配置这些对象,配置元数据包含关于应用中必须创建的协作对象的信息。Spring 提供了两种 IoC 容器实现。
- Bean 工厂(由 org . spring framework . beans . factory . bean factory 接口定义)
- 应用上下文(由 org . spring framework . context . application context 接口定义)
Bean 工厂是最简单的容器,为 DI 提供基本支持。ApplicationContext 是 BeanFactory 的子接口,它提供应用框架服务,例如:
- 从属性文件解析文本消息的能力
- 向感兴趣的事件侦听器发布应用事件的能力
- 特定于应用层的上下文,例如要在 web 层中使用的 WebApplicationContext
注意 Web 应用有自己的 WebApplicationContext。当我们在本章后面讨论基于 web 的 Spring 应用时,将解释 WebApplicationContext。
应用上下文
Spring 自带了 ApplicationContext 接口的几个现成实现。最常用的三种方法如下:
- ClassPathXmlApplicationContext:从位于类路径中的 XML 文件加载上下文定义
- FileSystemXmlApplicationContext:从文件系统中的 XML 文件加载上下文定义
- XmlWebApplicationContext:从 web 应用中包含的 XML 文件加载上下文定义
在独立应用中,创建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 的实例是很常见的。
在图 5-1 之后,您必须实例化 Spring IoC 容器(ApplicationContext ),通过读取它们的配置(配置元数据)来创建 bean 实例。然后,您可以从 IoC 容器中获取 bean 实例来使用。
清单 5-8 展示了 ClassPathXmlApplicationContext 的实例化,它是 ApplicationContext 的一个实现。ClassPathXmlApplicationContext 实现通过从类路径加载 XML 配置文件来构建应用上下文。
清单 5-8 。ClassPathXmlApplicationContext 的实例化
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
清单 5-9 说明了文件系统 XmlApplicationContext 的实例化。
清单 5-9 。file systemxmlapplicationcontext 的实例化
ApplicationContextcontext=new FileSystemXmlApplicationContext("c:/beans.xml");
注意filesystemxmlaplicationcontext 在文件系统中的特定位置寻找 beans.xml ,而 ClassPathXmlApplicationContext 在类路径(包括 JAR 文件)的任何地方寻找 beans.xml 。
在接下来的小节中,您将学习如何在创建第一个基于 Spring 的独立应用时使用应用上下文。
Spring 框架的主要目标
依赖注入并不是使用 Spring 框架的唯一好处。Spring 框架的目标是简化开发企业应用的复杂性。这种复杂性在企业应用中以多种方式表现出来,Spring 框架之前的大多数企业应用都无意中遭受了以下几个甚至全部的困难:
- 紧密结合
- 贯穿各领域的问题
- 样板代码
从根本上说,Spring 使您能够从 POJO 构建应用,并将企业服务非介入式地应用到 POJO,这样域模型就不依赖于框架本身。因此,Spring 框架背后的驱动力是通过启用基于 POJO 的编程模型来促进 Java EE 开发中的最佳实践。
使用依赖注入处理紧耦合
现在让我们看看 Spring 如何在一个简单的独立应用的帮助下,通过依赖注入实现松散耦合 。这个应用的代码可以在 Apress 网站的可下载文档中找到。此外,这个应用将成为春天森林的序言。清单 5-10 、 5-11 和 5-12 展示了服务提供者对象的层次结构以及清单 5-13 中展示的 VehicleService 的依赖关系。
清单 5-10 。车辆接口
public interface Vehicle {
public String drive();
}
清单 5-11 。车辆实施:自行车
public class Bike implements Vehicle{
public String drive() {
return " driving a bike";
}
}
清单 5-12 。车辆实施:汽车
public class Car implements Vehicle {
public String drive() {
return " driving a car";
}
}
这些服务提供者对象由 VehicleService 类使用,如清单 5-13 中的所示,然后由客户端对象使用,如清单 5-14 中的所示。
清单 5-13 。车辆服务 ??
1. public class VehicleService {
2.
3. private Vehicle vehicle = new Bike();
4.
5. public void driver() {
6. System.out.println(vehicle.drive());
7.
8. }
9.
10. }
- 第 3 行:在清单 5-13 中,类 Bike 是类 VehicleService 的依赖,在第 3 行被实例化。这是一个紧耦合的例子,因为 VehicleService 类是实现感知的车辆对象,在这个例子中是 Bike。
清单 5-14 展示了独立的 VehicleApp。
清单 5-14 。独立车辆应用
1. public class VehicleApp {
2. public static void main(String[] args) {
3. VehicleService service = new VehicleService();
4. service.driver();
5. }
6. }
如清单 5-14 所示,VehicleService 知道车辆对象的实现,因此与它紧密耦合。现在让我们通过 Spring 框架的 DI 来分离这个应用。第一步是使用 Eclipse IDE 创建一个 Java 项目。选择文件新建
项目,然后从向导列表中选择 Java 项目向导。使用向导将你的项目命名为松散耦合应用,如图图 5-2 所示。
图 5-2 。创建 Java 项目
现在您需要在项目中添加 Spring 框架和公共日志 API 库 。你可以从 projects.spring.io/spring-fram… Spring 框架库。您可以使用 Maven 来配置项目中的 Spring 框架,如前一章所述。使用 Maven 时,需要将以下内容添加到 pom.xml 文件中:](projects.spring.io/spring-fram…)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.5.RELEASE</version>
</dependency>
</dependencies>
使用 Maven 要容易得多,但是要知道您将在本节中创建的三个应用使用了哪些关键库,您可以在 Eclipse 中手动配置这些库。这些库可以在可下载的档案中找到,可以从该书的 press 网页上的源代码/下载标签中找到(www.apress.com/97814302598…
要将 Spring 框架添加到您的项目中,右键单击您的项目并选择 Build PathConfigure Build Path 以显示 Java Build Path 窗口,如图图 5-3 所示。现在添加您从 Apress 网站下载到文件系统中的外部 jar。
图 5-3 。添加外部罐子
现在让我们在 looselyCoupledApplication 项目下创建实际的源文件。首先我们需要创建一个名为 com.apress.decoupled 的包。为此,在 Package Explorer 部分右键单击 src,选择 New Package,并创建包 com.apress.decoupled。然后创建 Vehicle.java、Car.java 和 Bike.java,这是在 com.apress.decoupled 包下的清单 5-10 、 5-11 和 5-12 中所示的代码。
然后创建 VehicleService 类,如清单 5-15 所示。
清单 5-15 。松散耦合车辆服务
1. package com.apress.decoupled;
2.
3. public class VehicleService {
4.
5. private Vehicle vehicle;
6.
7. public void setVehicle(Vehicle vehicle) {
8. this.vehicle = vehicle;
9. }
10.
11. public void driver() {
12. System.out.println(vehicle.drive());
13.
14. }
15.
16. }
17.
- 第 7 行:在清单 5-15 中,我们已经从 VehicleService 类中移除了总控制权,并将其保留在 XML 配置文件中,并且通过第 7 行的 setter 方法将依赖注入到 VehicleService 类中。
现在创建一个客户端类 VehicleApp,如清单 5-16 所示。
清单 5-16 。车辆申请 ??
1. package com.apress.decoupled;
2. import org.springframework.context.ApplicationContext;
3. import org.springframework.context.support.ClassPathXmlApplicationContext;
4.
5. public class VehicleApp {
6.
7. public static void main(String[] args) {
8. ApplicationContext context = new ClassPathXmlApplicationContext(
9. "beans.xml");
10. VehicleService contestService = (VehicleService) context
11. .getBean("vehicleService");
12. contestService.driver();
13. }
14.
15. }
- 第 8 到 9 行:这几行实例化应用上下文并传递配置文件。
- 第 10 行到第 11 行:这几行从配置文件中获取 bean。要从 Bean 工厂或应用上下文中获取声明的 bean,您需要调用 getBean()方法 ,传入唯一的 bean 名称,并在使用之前将返回类型转换为其实际类型。
现在,您需要创建一个 bean 配置文件,这是一个连接 bean 的 XML 文件(如清单 5-17 所示)。
清单 5-17 。配置文件
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans fontname">http://www.springframework.org/schema/beans"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://www.springframework.org/schema/beans
5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
6. <bean id="car" class="com.apress.decoupled.Car" />
7. <bean id="bike" class="com.apress.decoupled.Bike" />
8. <bean id="vehicleService" class="com.apress.decoupled.VehicleService">
9. <property name="vehicle">
10. <ref local="car" />
11. </property>
12. </bean>
13. </beans>
您可以为 beans.xml 选择任何您喜欢的名称。您必须确保该文件在类路径中可用,并且在创建应用上下文时在主应用中使用相同的名称,如 VehicleApp.java 文件所示。图 5-4 显示了应用的目录结构。
图 5-4 。目录结构
使用 AOP 解决横切关注点
Spring 通过面向方面编程(AOP)实现了关注点的分离。AOP 将横切关注点封装在称为方面的独立的、可重用的组件中,并将它们添加到应用中。这个过程叫做编织。这导致内聚的组件专注于业务功能,而完全不知道系统服务,如日志、安全、事务等等。为了理解如何在 Spring 中应用方面,您现在将创建一个简单的 BookService 来检索所有书籍的列表,向 BookService 添加一个基本的日志方面。这个应用的代码可以在 Apress 网站的可下载文档中找到。清单 5-18 展示了图书服务。
清单 5-18 。图书服务界面
package com.apress.aop;
import java.util.List;
public interface BookService {
public List<Book> getAllBooks();
}
清单 5-19 展示了这个图书服务的实现 。
清单 5-19 。图书服务实施
1. package com.apress.aop;
2.
3. import java.util.ArrayList;
4. import java.util.List;
5.
6. public class BookServiceImpl implements BookService{
7. private static List<Book> bookList;
8. static {
9.
10. Book book1 = new Book();
11. book1.setId((long)1);
12. book1.setBookTitle("Modern Java");
13.
14. Book book2 = new Book();
15. book2.setId((long)2);
16. book2.setBookTitle("Beginning Groovy");
17.
18. Book book3 = new Book();
19. book3.setId((long)2);
20. book3.setBookTitle("Beginning Scala");
21.
22. bookList = new ArrayList<Book>();
23. bookList.add(book1);
24. bookList.add(book2);
25. bookList.add(book3);
26. }
27.
28.
29. public List<Book> getAllBooks() {
30. for(Book b: bookList){
31. System.out.println("Books:"+b.getBookTitle());
32. }
33. return bookList;
34. }
35. }
BookServiceImpl 不关心日志记录。对于日志记录,创建一个如清单 5-20 所示的方面,它将被编织到 BookService 对象中,在任何需要的地方提供日志记录。
清单 5-20 。伐木方面
1. package com.apress.aop;
2.
3. public class LoggingAspect {
4. public void logBefore() {
5.
6. System.out.println("Before calling getAllBooks");
7. }
8.
9. public void logAfter() {
10. System.out.println("After calling getAllBooks");
11. }
12. }
13.
- 第 3 行 : LoggingAspect 是一个简单的类,有两个方法。
- 第 4 行:调用 getAllBooks()之前应该调用的 logBefore()方法。
- 第 9 行:调用 getAllBooks()后应该调用的 logAfter()方法。
LoggingAspect 在没有 BookServiceImpl 要求的情况下完成它的工作。此外,因为 BookServiceImpl 不需要知道 LoggingAspect,所以不需要将 LoggingAspect 注入到 BookServiceImpl 中。这消除了 BookServiceImpl 代码中不必要的复杂性,即必须注入 LoggingAspect 并检查 LoggingAspect 是否为 null。您可能已经注意到,LoggingAspect 是一个 POJO。当它在 Spring 上下文中被声明为一个方面时,它就变成了一个方面。LoggingAspect 可以应用于 BookServiceImpl,而不需要 BookServiceImpl 显式调用它。事实上,BookServiceImpl 仍然完全不知道 LoggingAspect 的存在。要使 LoggingAspect 作为一个方面工作,您需要做的就是在 Spring 配置文件中将其声明为一个方面。清单 5-21 展示了将 LoggingAspect 声明为一个方面的应用上下文 XML 文件。
清单 5-21 。配置文件
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans fontname">http://www.springframework.org/schema/beans"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context=
"http://www.springframework.org/schema/context"
4. xmlns:aop="http://www.springframework.org/schema/aop"
5. xsi:schemaLocation="http://www.springframework.org/schema/beans
6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
7. http://www.springframework.org/schema/context
8. http://www.springframework.org/schema/context/spring-context-3.2.xsd
9. http://www.springframework.org/schema/aop
10. http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
11.
12.
13. <bean id="bookService" class="com.apress.aop.BookServiceImpl"/>
14.
15. <bean id="logAspect" class="com.apress.aop.LoggingAspect"/>
16.
17. <aop:config>
18. <aop:aspect ref = "logAspect">
19. <aop:pointcut id = "log"
20. expression="execution(* *.getAllBooks())" />
21. <aop:before pointcut-ref = "log"
22. method="logBefore" />
23. <aop:after pointcut-ref = "log"
24. method="logAfter" />
25. </aop:aspect>
26. </aop:config>
27. </beans>
- 第 9 行到第 10 行:您使用 Spring 的 aop 配置名称空间来声明 LoggingAspect bean 是一个方面。
- 第 15 行:您将 LoggingAspect 声明为一个 bean。即使 Spring 框架通过在上下文中将 POJO 声明为一个方面来将其转换为一个方面,它仍然必须被声明为一个 Spring < bean >。
- 第 18 行:然后你引用了< aop:aspect >元素中的那个 bean。
- 第 19 到 20 行:在前面的<切入点>元素中定义了切入点,并设置了一个表达式属性来选择应该在哪里应用通知。表达式语法是 AspectJ 的切入点表达式语言。
- 第 21 行到第 22 行:你声明(使用< aop:before >)在执行 getAllBooks()方法之前,应该调用 LoggingAspect 的 logBefore 方法。这叫做之前的建议。pointcut-ref 属性引用一个名为 log 的切入点。
- 第 23 到 24 行:你(使用< aop:after >)声明 logAfter 方法应该在 getAllBooks()执行之后被调用。这就是后知后觉的*。pointcut-ref 属性引用一个名为 log 的切入点。*
*清单 5-22 展示了独立的 Java 应用。
清单 5-22 。独立 Java 应用
1. package com.apress.aop;
2. import org.springframework.context.ApplicationContext;
3. import org.springframework.context.support.ClassPathXmlApplicationContext;
4.
5. public class Driver {
6.
7. public static void main(String...args){
8. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
9.
10. BookService bookService = (BookService)context.getBean("bookService");
11. bookService.getAllBooks();
12.
13. }
14. }
图 5-5 说明了该应用的目录结构 。
图 5-5 。目录结构
因此,Spring AOP 可以被用来以声明的方式提供诸如事务和安全之类的服务,而不会打乱您的代码,首先,您的代码应该只关心它的业务功能。
使用模板删除样板代码
在第一章中,我们构建了书店应用的数据访问层,并在 BookDAOImpl 中使用纯 JDBC 来连接数据库。在本节中,您将看到 Spring 框架如何通过 Spring 的 JDBCTemplate 转换 BookDAOImpl,消除使用纯 JDBC 获得数据存储连接所产生的样板代码,并清理资源。这个应用的代码可以在 Apress 网站的可下载文档中找到。清单 5-23 展示了图书服务。
清单 5-23 。图书服务
package com.apress.books.service;
import java.util.List;
import com.apress.books.model.Book;
public interface BookService {
public List<Book> getAllBooks();
}
清单 5-24 展示了 BookService 的实现。
清单 5-24 。图书服务实施
package com.apress.books.service;
import java.util.List;
import com.apress.books.dao.BookDAO;
import com.apress.books.model.Book;
public class BookServiceImpl implements BookService{
private BookDAO bookDao ;
public void setBookDao(BookDAO bookDao) {
this.bookDao = bookDao;
}
public List<Book> getAllBooks() {
List<Book> bookList = bookDao.findAllBooks();
return bookList;
}
}
清单 5-25 说明了 BookDAO。
清单 5-25 。书道
package com.apress.books.dao;
import java.util.List;
import com.apress.books.model.Book;
import com.apress.books.model.Category;
public interface BookDAO {
public List<Book> findAllBooks();
}
清单 5-26 展示了 BookDAO 的实现。
清单 5-26 。BookDAO 实现
1. package com.apress.books.dao;
2.
3. import java.sql.Connection;
4. import java.sql.DriverManager;
5. import java.sql.PreparedStatement;
6. import java.sql.ResultSet;
7. import java.sql.SQLException;
8. import java.sql.Statement;
9. import java.util.ArrayList;
10. import java.util.List;
11.
12. import javax.sql.DataSource;
13.
14. import org.springframework.beans.factory.annotation.Autowired;
15. import org.springframework.jdbc.core.JdbcTemplate;
16.
17. import com.apress.books.model.Author;
18. import com.apress.books.model.Book;
19. import com.apress.books.model.Category;
20.
21. public class BookDAOImpl implements BookDAO {
22.
23.
24. DataSource dataSource;
25.
26.
27. public void setDataSource(DataSource dataSource) {
28. this.dataSource = dataSource;
29. }
30.
31. public List<Book> findAllBooks() {
32. List<Book> bookList = new ArrayList<>();
33.
34. String sql = "select * from book inner join author on book.id = author.book_id";
35.
36. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
37. bookList = jdbcTemplate.query(sql, new BookRowMapper());
38. return bookList;
39. }
40.
41. }
将 BookDAOImpl 的 findAllBooks()方法与第一章的 findAllBooks()方法进行比较,您将会看到 JDBCTemplate 消除了样板代码,这是使用纯 JDBC 来获得到我们的数据存储的连接并清理资源的结果。
- 第 36 行:使用传递给它的数据源创建一个 JDBCTemplate。注意,为了便于解释,JDBCTemplate 是以这种方式实例化的。在一个生产就绪的应用中,你应该像任何其他依赖一样注入 JDBCTemplate 作为一个依赖,如清单 5-15 中所解释的。
- 第 37 行:使用了行映射器实现。接下来解释 BookRowMapper。
清单 5-27 说明了 BookRowMapper 对象查询一行或多行,然后将每一行转换成相应的域对象,而不是检索单个值。
清单 5-27 。书签器对象
1. package com.apress.books.dao;
2.
3. import java.sql.ResultSet;
4. import java.sql.SQLException;
5.
6. import org.springframework.jdbc.core.RowMapper;
7.
8. import com.apress.books.model.Book;
9.
10. public class BookRowMapper implements RowMapper<Book> {
11.
12. @Override
13. public Book mapRow(ResultSet resultSet, int line) throws SQLException {
14. BookExtractor bookExtractor = new BookExtractor();
15. return bookExtractor.extractData(resultSet);
16. }
17.
18. }
- Line 10 : Spring 的 RowMapper < T >接口(在 org.springframework.jdbc.core 包下)提供了一个简单的方法来执行从 jdbc 结果集到 POJOs 的映射。
- 第 14 行:使用 BookExtractor 提取数据。
清单 5-28 展示了 BookExtractor 对象。
清单 5-28 。【BookExtractor 对象
1. package com.apress.books.dao;
2.
3. import java.sql.ResultSet;
4. import java.sql.SQLException;
5. import java.util.ArrayList;
6. import java.util.List;
7.
8. import org.springframework.dao.DataAccessException;
9. import org.springframework.jdbc.core.ResultSetExtractor;
10.
11. import com.apress.books.model.Author;
12. import com.apress.books.model.Book;
13.
14. public class BookExtractor implements ResultSetExtractor<Book> {
15.
16. public Book extractData(ResultSet resultSet) throws SQLException,
17. DataAccessException {
18.
19. Book book = new Book();
20. Author author = new Author();
21. List<Author> authorList = new ArrayList<>();
22.
23. book.setId(resultSet.getLong(1));
24. book.setCategoryId(resultSet.getLong(2));
25. book.setBookTitle(resultSet.getString(3));
26. book.setPublisherName(resultSet.getString(4));
27. book.setAuthorId(resultSet.getLong(5));
28. author.setBookId(resultSet.getLong(6));
29. author.setFirstName(resultSet.getString(7));
30. author.setLastName(resultSet.getString(8));
31. authorList.add(author);
32. book.setAuthors(authorList);
33.
34. return book;
35. }
36.
37. }
- 第 14 行 : BookExtractor 实现 Spring 提供的 ResultSetExtractor(在 org.springframework.jdbc.core 包下)。RowMapper 仅适用于映射到单个域对象。但是由于我们在第 34 行连接了清单 5-26 中的两个表,我们需要使用 ResultSetExtractor 接口 将数据转换成一个嵌套的域对象。
清单 5-29 展示了这个独立应用的配置文件。
清单 5-29 。配置文件
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans fontname">http://www.springframework.org/schema/beans"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context=
"http://www.springframework.org/schema/context"
4. xmlns:aop="http://www.springframework.org/schema/aop"
5. xsi:schemaLocation="http://www.springframework.org/schema/beans
6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
7. http://www.springframework.org/schema/context
8. http://www.springframework.org/schema/context/spring-context-3.2.xsd
9. http://www.springframework.org/schema/aop
10. http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
11.
12. <!-- telling container to take care of annotations stuff -->
13. <context:annotation-config />
14.
15. <!-- declaring base package -->
16. <context:component-scan base-package="com.apress.books" />
17.
18.
19. <bean id="dao" class="com.apress.books.dao.BookDAOImpl" >
20. <property name="dataSource" ref="dataSource">
21. </property>
22. </bean>
23.
24. <bean id="service" class="com.apress.books.service.BookServiceImpl">
25. <property name="bookDao" ref="dao">
26. </property>
27. </bean>
28.
29. <bean id="dataSource"
30. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
31. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
32. <property name="url" value="jdbc:mysql://localhost:3306/books" />
33. <property name="username" value="root" />
34. <property name="password" value="password" />
35. </bean>
36. </beans>
- 第 20 行:用数据源 ?? 配置 dao
这样,Spring 框架消除了样板代码。现在有了独立的 Java 应用,您可以查询我们使用 Spring 框架构建的新数据访问层。清单 5-30 说明了通过服务层组件 BookService 查询数据访问的独立 Java 应用。
清单 5-30 。单机应用
package com.apress.books.client;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.apress.books.model.Book;
import com.apress.books.service.BookService;
public class BookApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
BookService bookService = (BookService)context.getBean("service");
// List all books
System.err.println("Listing all Books:");
List<Book> bookList= bookService.getAllBooks();
for(Book b: bookList){
System.out.println(b.getId()+"--"+b.getBookTitle());
}
}
}
在本节中,您了解了 Spring 框架的关键目标,该框架旨在通过紧密耦合、横切关注点和样板代码来简化任何企业应用的复杂性。下一节,我们将使用 Spring 框架的 web MVC 模块 实现 Web 层。
用 Spring Web MVC 构建 Web 应用
在第一章中,Spring Web MVC 提供了模型-视图-控制器架构和无数的组件,共同帮助你开发基于 Spring IoC 容器的松散耦合的 Web 应用。
Spring Web MVC 架构
Spring 的 web MVC 框架像许多其他 Web MVC 框架一样,是请求驱动的,围绕一个名为 DispatcherServlet 的中央 Servlet 设计,该 servlet 将请求分派给控制器,并提供其他功能来促进 Web 应用的开发。DispatcherServlet 实现了 Java EE web 层模式之一,称为前端控制器。因此,DispatcherServlet 充当 Spring MVC 框架的前端控制器,每个 web 请求都必须经过它,这样它才能控制和管理整个请求处理过程。Spring Web MVC DispatcherServlet 的请求处理工作流程如图图 5-6 所示。
图 5-6 。Spring Web MVC DispatcherServlet 的请求处理工作流
从图 5-6 开始,工作流程的高级概述如下:
- 客户端以 HTTP 请求的形式向 web 容器发送请求。
- DispatcherServlet 截获请求,找出适当的处理程序映射。
- 在如此计算出的处理程序映射的帮助下,DispatcherServlet 将请求分派给适当的控制器。
- 控制器处理请求并将模型和视图对象以 model and view 实例的形式返回给 DispatcherServlet。
- DispatcherServlet 然后通过查询 ViewResolver 对象解析视图(可以是 JSP、FreeMarker、Velocity 等等)。
- 然后,将所选视图呈现回客户端。
DispatcherServlet 是 Spring Web MVC 框架的核心,但是在深入研究 DispatcherServlet 之前,首先必须了解 Web 应用中的 ApplicationContext。如前所述,web 应用有自己专门的 WebApplicationContext,必须在初始化 DispatcherServlet 之前加载。当 Spring Web MVC 应用启动时,在 Web 应用准备好服务请求之前,WebApplicationContext 和 DispatcherServlet 开始工作,如下所述:
- servlet 容器初始化 web 应用,然后触发 contextInitialized 事件,该事件由 ContextLoaderListener 侦听。
- ContextLoaderListener 创建根 WebApplicationContext。
- DispatcherServlet 被初始化,创建自己的 WebApplicationContext 并将其嵌套在根 WebApplicationContext 中。
- DispatcherServlet 搜索组件,如 ViewResolvers 和 HandlerMappings。如果找到一个组件,它将被初始化;否则,组件的默认值将被初始化。
在接下来的小节中,您将更详细地了解这些步骤。
文对象
在 web 应用中,使用的 ApplicationContext 称为 WebApplicationContext ,它是一个专用的 ApplicationContext,能够感知 servlet 环境。它是 web 应用中的根 ApplicationContext,必须在 DispatcherServlet 初始化之前加载,以确保 web 应用所需的所有服务(如数据源)都可用。使用 ContextLoaderListener 在 web.xml 文件中配置 WebApplicationContext,如清单 5-31 所示。
清单 5-31 。在 web.xml 中配置 ContextLoaderListener
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
默认情况下,ContextLoaderListener 加载存储在 WEB-INF 目录中的应用上下文文件。这个位置可以通过在 web.xml 中定义 contextConfigLocation 上下文参数来覆盖,如清单 5-32 所示。
清单 5-32 。使用 contextConfigLocation 参数的文件位置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:service-context.xml
classpath:data-access-context.xml
</param-value>
</context-param>
此时,web.xml 中 WebApplicationContext 的配置看起来像是清单 5-33 。
清单 5-33 。带有 ContextLoaderListener 和 contextConfigLocation 的 Web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:service-context.xml
classpath:data-access-context.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
注意如果没有指定名为 contextConfigLocation 的<上下文参数>,默认情况下,ContextLoaderListener 会查找/we b-INF/application context . XML 文件。
现在,您已经知道如何在 web 应用中配置 WebApplicationContext,我们可以继续讨论第二个对象 DispatcherServlet,它是在 web.xml 文件中配置的。
调度员服务网
与任何 servlet 一样,DispatcherServlet 需要在 web.xml 中进行配置,以便能够处理请求。配置和使用 DispatcherServlet 需要满足以下要求:
- 您必须指示容器加载 DispatcherServlet 并将其映射到 URL 模式 。
- 加载 DispatcherServlet 后,它会创建自己的 org . spring framework . web . context . webapplicationcontext。
- DispatcherServlet 然后从这个应用上下文中检测 SpringMVC 组件,如果没有找到,它将使用缺省值。这些 SpringMVC 组件和它们的缺省值将在后面解释。
- DispatcherServlet 然后根据请求将任务委派给每个 SpringMVC 组件(或它们的缺省值)。
注意 DispatcherServlet 创建自己的 WebApplicationContext,其中包含特定于 web 的组件,如控制器和 ViewResolver。然后,此 WebApplicationContext 嵌套在根 WebApplicationContext 中,该根 WebApplicationContext 在 DispatcherServlet 初始化之前加载,以确保 DispatcherServlet 的 WebApplicationContext 中的 web 组件可以找到它们的依赖项。
与任何其他 Servlet 一样,DispatcherServlet 是在 web 应用的 web.xml 文件中声明的。您需要通过在同一个 web.xml 文件中使用 URL 映射来映射希望 DispatcherServlet 处理的请求。清单 5-34 展示了 DispatcherServlet 声明和映射。
清单 5-34 。声明和映射 DispatcherServlet
<web-app>
<servlet>
<servlet-name>bookstore</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>bookstore</servlet-name>
<url-pattern>/bookstore/*</url-pattern>
</servlet-mapping>
</web-app>
在 Servlet 3.0 和更新的环境中,您还可以使用 WebApplicationInitializer(Spring MVC 框架提供的一个接口)以编程方式配置 Servlet 容器。清单 5-35 展示了前面的 web.xml 示例的等效程序。
清单 5-35 。相当于清单 5-43 的程序
public class ExampleWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.addMapping("/bookstore/*");
}
}
默认情况下,DispatcherServlet 会查找一个名为 WEB-INF/ -servlet.xml 的文件,其中的被替换为在 web.xml 的标记中声明的值。DispatcherServlet 使用这个 -servlet.xml 文件来创建 WebApplicationContext。
Spring MVC 组件
如前所述,DispatcherServlet 从它创建的 WebApplicationContext 中搜索 SpringMVC 组件,如果没有找到,就使用默认的。这些 Spring MVC 组件被表示为接口。表 5-6 给出了请求处理工作流中涉及的所有主要组件类型的概述。
表 5-6 。Spring MVC 组件
|豆类
|
说明
| | --- | --- | | 的配置 | 将传入的请求映射到处理程序和拦截程序 | | 处理器适配器 | 用于扩展 DispatcherServlet 以定制 web 工作流 | | 处理器异常解析器 | 将异常映射到视图 | | 视图解析器 | 将逻辑视图名称解析为实际视图 | | LocaleResolver | 解析客户端用于国际化视图的区域设置 | | 主题解析器 | 解析个性化布局的主题 | | 多重解析器 | 解析文件上传的多部分 | | FlashMapManager | 支持 FlashMap 将属性从一个请求传递到另一个请求 |
Spring DispatcherServlet 使用需要在 WebApplicationContext 中配置的 Spring MVC 组件来处理请求。但是,如果不配置这些组件,Spring Web MVC 会使用默认组件。表 5-7 列出了组件的默认实现。
表 5-7 。DispatcherServlet 的默认组件
|成分
|
默认实现
| | --- | --- | | 多重解析器 | 无违约;需要显式配置 | | LocaleResolver | AcceptHeaderLocaleResolver | | 主题解析器 | FixedThemeResolver | | 的配置 | beannameurlhandlermapping 对映 defaultannotationandhandler 对映 | | 处理器适配器 | HttpRequestHandlerAdapter SimpleControllerHandlerAdapter AnnotationMethodHandlerAdapter | | 处理器异常解析器 | annotation method handler exception resolve response status exception resolve default handler exception resolve | | RequestToViewNameTranslator | DefaultRequestToViewNameTranslator | | 视图解析器 | InternalResourceViewResolver | | FlashMapManager | SessionFlashMapManager |
Spring Web MVC 应用入门
在这一节中,我将带您浏览使用 Spring Tool Suite(一个基于 Eclipse 的 IDE)创建 Hello World Spring MVC 应用的步骤。在构建示例应用时,您将学习 Spring MVC 的基本概念。该应用中使用的工具包括:
- Spring 框架
- Spring 工具套件 IDE 3.2.0(基于 Eclipse Juno 4.2.2)
- v fabric TC Server Developer Edition v 2.8(基于 Apache Tomcat 并针对 Spring 应用进行了优化)
Spring Tool Suite (STS) 是一个基于 Eclipse 的 IDE,由 SpringSource 社区积极开发和维护。STS 提供了 Spring Batch、Spring Integration、Spring Persistence(Hibernate+JPA)、Spring MVC 等项目模板。此外,STS 总是从 Maven 存储库中获得 Spring 工件的最新更新。
您可以选择以下三种方式下载并安装 STS:
- 从安装程序下载并安装 STS。
- 通过 Eclipse 更新安装 STS。
- 下载并解压 zip 存档文件。
在您自己的工作空间中启动 STS。从主菜单中选择文件新建
Spring 模板项目(参见图 5-7 )。
图 5-7 。选择 SpringTemplate 项目
在新建模板项目对话框中,选择 Spring MVC 项目(参见图 5-8 )。
图 5-8 。选择 SpringMVC 项目
点击下一步,需要下载模板的更新,如图图 5-9 所示(第一次使用该模板或有更新时)。
图 5-9 。下载更新
单击 Yes 下载更新,这将打开新的 Spring MVC 项目对话框。
在图 5-10 所示的窗口中输入以下信息:
- 项目名称 : helloworld
- 顶层包:com . a press . hello world
图 5-10 。新建 Spring MVC 项目对话框
单击 Finish,STS 将创建一个基于 Spring MVC 的项目,其中包含一些控制器、视图和配置的默认值。我们还没有写任何代码,但是应用已经准备好部署和运行了。
在 Servers 视图中右键单击并选择 New Server。
在“新建服务器”对话框中,选择 VMware VMware vFabric tc 服务器...,如图图 5-11 所示。
图 5-11 。定义新服务器
单击下一步。在下一个屏幕上,保持选择“创建新实例”选项(见图 5-12 )。
图 5-12 。创建新实例
单击下一步。在下一个屏幕上,键入 tcServer 作为新实例的名称,并选择 base 作为模板(参见图 5-13 )。
图 5-13 。指定实例参数
添加 helloworld 并点击完成以完成服务器设置(参见图 5-14 )。现在部署 helloworld 应用。
图 5-14 。在服务器上配置资源
如果我们在服务器名称下看到应用,那么它就部署在服务器上,如图图 5-15 所示。
图 5-15 。部署的应用
启动服务器,使用 URLlocalhost:8080/hello world运行应用(参见图 5-16 )。
图 5-16 。运行应用
现在让我们探索一下 Spring MVC 项目模板创建了什么。展开项目浏览器视图中的分支,查看项目的结构,如图 5-17 所示。
图 5-17 。Hello World 应用的目录结构
我们将仔细检查图 5-17 中所示的每个组件。图 5-18 说明了生成的 web.xml 文件的内容。
图 5-18 。生成的 web.xml
这是基于 Spring MVC 的应用的典型配置,声明如下:
- Spring's ContextLoaderListener
- 春天的调度员服务网
- Spring 配置文件 root-context.xml
- 弹簧配置文件 servlet-context.xml
- Spring 的 DispatcherServlet 的 URL 映射
我们将看看其中每一个的用法,但在此之前,我们将修改 web.xml。在 Spring MVC 模板项目中,它生成的 web.xml 文件支持 Servlet 2.5。在本章中,我们将使用 Servlet 3.0(STS 附带的 tcServer 构建在 Apache Tomcat 7 之上,后者已经支持 Servlet 3.0),因此我们也需要将 XML 头从 2.5 更改为 3.0。清单 5-36 显示了修改后的<网络应用>标签。
清单 5-36 。Spring MVC 的 Web 部署描述
1. <?xml version="1.0" encoding="UTF-8"?>
2. <web-app fontname">http://java.sun.com/xml/ns/javaee" xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
3. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
4. version="3.0">
5.
6. <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
7. <context-param>
8. <param-name>contextConfigLocation</param-name>
9. <param-value>/WEB-INF/spring/root-context.xml</param-value>
10. </context-param>
11.
12. <!-- Creates the Spring Container shared by all Servlets and Filters -->
13. <listener>
14. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
15. </listener>
16.
17. <!-- Processes application requests -->
18. <servlet>
19. <servlet-name>appServlet</servlet-name>
20. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
21. <init-param>
22. <param-name>contextConfigLocation</param-name>
23. <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
24. </init-param>
25. <load-on-startup>1</load-on-startup>
26. </servlet>
27.
28. <servlet-mapping>
29. <servlet-name>appServlet</servlet-name>
30. <url-pattern>/</url-pattern>
31. </servlet-mapping>
32.
33. </web-app>
- 第 2 行到第 4 行:在< web-app >标签中,版本属性和对应的 URL 被修改为 3.0 版本,向 web 容器表明 web 应用将使用 Servlet 3.0。
- 第 7 行到第 10 行:在< context-param >标签中,提供了 contextConfigLocation 参数,定义了 Spring 的根 WebApplicationContext 配置文件的位置。
- 第 13 行到第 15 行:定义了一个类 org . spring framework . web . context . context loader listener 的监听器。这是为了让 Spring 加载根 WebApplicationContext。
- 第 18 到 26 行:定义了一个调度器 servlet(称为 appServlet) 。我们使用模板项目为应用的表示层生成的那个。dispatcher servlet 的 WebApplicationContext 位于/src/main/WEB app/we b-INF/spring/app servlet/servlet-context . XML。
servlet-context.xml 文件由 Spring 的 DispatcherServlet 加载,它接收所有进入应用的请求。清单 5-37 展示了 servlet-context.xml
清单 5-37 。Hello World 应用的 servlet-context.xml
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans:beans fontname">http://www.springframework.org/schema/mvc"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xmlns:beans="http://www.springframework.org/schema/beans"
5. xmlns:context="http://www.springframework.org/schema/context"
6. xsi:schemaLocation="http://www.springframework.org/schema/mvc
7. http://www.springframework.org/schema/mvc/spring-mvc.xsd
8. http://www.springframework.org/schema/beans
9. http://www.springframework.org/schema/beans/spring-beans.xsd
10. http://www.springframework.org/schema/context
11. http://www.springframework.org/schema/context/spring-context.xsd">
12.
13. <!-- DispatcherServlet Context: defines this servlet's request-processing
14. infrastructure -->
15.
16. <!-- Enables the Spring MVC @Controller programming model -->
17. <annotation-driven />
18.
19. <!-- Handles HTTP GET requests for /resources/** by efficiently serving up
20. static resources in the ${webappRoot}/resources directory -->
21. <resources mapping="/resources/**" location="/resources/" />
22.
23. <!-- Resolves views selected for rendering by @Controllers to .jsp resources
24. in the /WEB-INF/views directory -->
25. <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
26. <beans:property name="prefix" value="/WEB-INF/views/" />
27. <beans:property name="suffix" value=".jsp" />
28. </beans:bean>
29.
30. <context:component-scan base-package="com.apress.helloworld" />
31.
32.
33.
34. </beans:beans>
- 第 17 行 : <注释驱动/ > 告诉框架使用基于注释的方法来扫描包中的文件。因此,我们可以对控制器类使用@Controller 注释,而不是声明 XML 元素。
- 第 21 行 : <资源映射=.../ > 直接用 HTTP GET 请求映射静态资源。例如,图像、JavaScript 和 CSS 资源不必通过控制器。
- 第 25 行到第 28 行:这个 bean 声明告诉框架如何根据控制器返回的逻辑视图名,通过给视图名附加前缀和后缀来找到物理 JSP 文件。例如,如果控制器的方法返回 home 作为逻辑视图名,那么框架将在/WEB-INF/views 目录下找到一个物理文件 home.jsp。
- 第 30 行 : <上下文:组件扫描.../ >告诉框架在使用基于注释的策略时要扫描哪些包。在这里,框架将扫描包 com.apress.helloworld 下的所有类。当应用增长时,您可以为业务 beans、DAOs 、事务等添加更多配置。
现在,我们已经有了检测将处理请求的控制器的基础设施,是时候看看控制器了。
注意在 Spring 2.5 之前,使用了一种基于接口的控制器。从 Spring 3.0 开始,基于接口的控制器被弃用,取而代之的是带注释的类。
清单 5-38 展示了 STS 生成的控制器类 HomeController 的代码。
清单 5-38 。Hello World 应用的 HomeController
1. package com.apress.helloworld;
2.
3. import java.text.DateFormat;
4. import java.util.Date;
5. import java.util.Locale;
6.
7. import org.slf4j.Logger;
8. import org.slf4j.LoggerFactory;
9. import org.springframework.stereotype.Controller;
10. import org.springframework.ui.Model;
11. import org.springframework.web.bind.annotation.RequestMapping;
12. import org.springframework.web.bind.annotation.RequestMethod;
13.
14. /**
15. * Handles requests for the application home page.
16. */
17. @Controller
18. public class HomeController {
19.
20. private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
21.
22. /**
23. * Simply selects the home view to render by returning its name.
24. */
25. @RequestMapping(value = "/", method = RequestMethod.GET)
26. public String home(Locale locale, Model model) {
27. logger.info("Welcome home! The client locale is {}.", locale);
28.
29. Date date = new Date();
30. DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
DateFormat.LONG, locale);
31.
32. String formattedDate = dateFormat.format(date);
33.
34. model.addAttribute("serverTime", formattedDate );
35.
36. return "home";
37. }
38.
39. }
- 第 17 行:@ Controller 注释用于指定这个类是一个 Spring 控制器。DispatcherServlet 通过@RequestMapping 注释扫描这种带注释的类,以查找映射的处理程序方法。
- 第 25 行:@ request mapping 注释指定 home()方法将处理带有 URL /(应用的默认页面)的 GET 请求。
- 第 26 行到第 37 行:home()方法创建一个字符串对象来保存基于当前地区的当前日期,并将这个对象添加到模型中,并命名为 serverTme 。最后,该方法返回一个名为 home 的视图,它将由 servlet-context.xml 文件中指定的视图解析器进行解析,以找到实际的视图文件。在一个控制器类中,我们可以编写许多方法来处理不同的 URL。
@Controller 和@RequestMapping 以及许多其他注释构成了 Spring MVC 实现的基础。要在 Spring 3.0 和更新版本中定义控制器类,必须用@Controller 注释标记该类。当@Controller 注释的类收到请求时,它会寻找合适的处理方法来处理请求。请求要映射到的每个方法都用@RequestMapping 注释修饰,使该方法成为一个处理程序方法,请求通过处理程序映射映射到该方法。
正如您在清单 5-38 中看到的,HomeController 中的 home()方法返回一个名为 home 的视图,该视图由 servlet-context.xml 中指定的视图解析器解析。现在是时候查看视图了,这是在/WEB-INF/views 目录中生成的 home.jsp 文件。清单 5-39 显示了 home.jsp 的。
清单 5-39 。Hello World 应用的 home.jsp
1. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
2. <%@ page session="false" %>
3. <html>
4. <head>
5. <title>Home</title>
6. </head>
7. <body>
8. <h1>
9. Hello world!
10. </h1>
11.
12. <P> The time on the server is ${serverTime}. </P>
13. </body>
14. </html>
清单 5-39 看起来很熟悉。这是一个简单的 JSP 文件,它在第 12 行使用一个 EL 表达式来打印控制器传递的变量 serverTime 的值。
您可能已经注意到,STS 创建了两个 Spring 配置文件 : root-context.xml 和 servlet-context.xml。我们还没有查看 root-context.xml,因为我们的 Hello World 应用不需要这个文件来显示 home.jsp 的内容。该文件默认为空,如图图 5-19 所示。
图 5-19 。生成的 root-context.xml
顾名思义,这个文件指定了 Spring 容器的根配置。root-context.xml 文件由 Spring 的 ContextLoaderListener 在应用启动时加载,正如您在上一节中所了解的。
到目前为止,我们已经浏览了由 Spring MVC 项目模板生成的所有文件,因此您应该有足够的能力来更深入地学习,一路构建书店应用。
在书店应用中实现 Spring Web MVC
在本节中,您将学习如何使用 Spring Web MVC 框架开发书店 web 应用。该应用的代码可以从 Apress 网站下载。如前所述,所有传入的请求都流经 DispatcherServlet。因此,像 Java EE 应用中的任何其他 servlet 一样,Java EE 容器需要被通知在启动时通过 web.xml 加载这个 servlet。清单 5-40 展示了书店应用的 web.xml。
清单 5-40 。web.xml
1. <?xml version="1.0" encoding="UTF-8"?>
2. <web-app fontname">http://java.sun.com/xml/ns/javaee" xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
3. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
4. version="3.0">
5. <!-- Processes application requests -->
6. <servlet>
7. <servlet-name>bookstore</servlet-name>
8. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
9. <init-param>
10. <param-name>contextConfigLocation</param-name>
11. <param-value>/WEB-INF/spring/bookstore/bookstore-servlet.xml</param-value>
12. </init-param>
13. <load-on-startup>1</load-on-startup>
14. </servlet>
15.
16. <servlet-mapping>
17. <servlet-name>bookstore</servlet-name>
18. <url-pattern>*.html</url-pattern>
19. </servlet-mapping>
20.
21. <welcome-file-list>
22. <welcome-file>/list_book.html</welcome-file>
23. </welcome-file-list>
24. </web-app>
- 第 7 行到第 8 行 : DispatcherServlet 注册为一个名为 bookstore 的 Servlet。
- 第 10 行:可以在 contextConfigLocation servlet 参数中明确指定 Spring 配置文件,要求 Spring 加载默认< servletname > -servlet.xml 之外的配置
清单 5-41 展示了 bookstore-servlet.xml。
清单 5-41 。书店-servlet.xml
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans:beans fontname">http://www.springframework.org/schema/mvc"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans=
"http://www.springframework.org/schema/beans"
4. xmlns:context="http://www.springframework.org/schema/context"
5. xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
6. http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/
beans/spring-beans.xsd
7. http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/
context/spring-context.xsd">
8.
9. <!-- DispatcherServlet Context: defines this servlet's request-processing
10. infrastructure -->
11.
12. <beans:bean name="/list_book.html"
13. class="com.apress.bookstore.controller.BookController" />
14.
15. <!-- Resolves views selected for rendering by @Controllers to .jsp resources
16. in the /WEB-INF/views directory -->
17. <beans:bean
18. class="org.springframework.web.servlet.view.InternalResourceViewResolver">
19. <beans:property name="prefix" value="/WEB-INF/views/" />
20. <beans:property name="suffix" value=".jsp" />
21. </beans:bean>
22. </beans:beans>
23.
一旦用户使用localhost:8080/book store请求一个图书列表,该请求就命中 servlet 引擎,该引擎将调用路由到部署在 servlet 容器中的 bookstore web 应用。在清单 5-40 中显示的 web.xml 文件提供了服务于请求的欢迎文件。
21. <welcome-file-list>
22. <welcome-file>/list_book.html</welcome-file>
23. </welcome-file-list>
欢迎文件中的 URL 与已经为 DispatcherServlet 注册的 URL 模式相匹配,请求被路由到它。基于 bookstore-servlet.xml 中可用的配置,请求被路由到特定的控制器,如清单 5-41 的第 12 行所示。这里,list_book.html 文件被声明为一个 bean,并映射到 BookController 类。这意味着如果请求一个带有/list_book.html 的 URL,它将要求 BookController 处理这个请求。清单 5-42 展示了基于界面的 BookController。稍后您将看到如何用带注释的控制器替换这个基于接口的控制器。
清单 5-42 。书店应用的基于界面的控制器
1. package com.apress.bookstore.controller;
2.
3. import javax.servlet.http.HttpServletRequest;
4. import javax.servlet.http.HttpServletResponse;
5.
6. import org.springframework.web.servlet.ModelAndView;
7. import org.springframework.web.servlet.mvc.Controller;
8.
9. import com.apress.bookstore.service.BookService;
10.
11. public class BookController implements Controller{
12.
13. @Override
14. public ModelAndView handleRequest(HttpServletRequest arg0,
15. HttpServletResponse arg1) throws Exception {
16. BookService bookservice = new BookService();
17. ModelAndView modelAndView = new ModelAndView("bookList");
18. modelAndView.addObject("bookList", bookservice.getBookList());
19. return modelAndView;
20. }
21. }
控制器实例化负责返回所需图书数据的 BookService。modeland view(“booklist”)通过将 bookList 传递给 Spring 的视图解析器来调用名为 bookList 的视图,以确定应该将哪个视图返回给用户。在本例中,BookController 返回一个名为 bookList 的 ModelAndView 对象。bookstore-servlet.xml 中的视图解析器片段(来自清单 5-41 )如下所示:
17. <beans:bean
18. class="org.springframework.web.servlet.view.InternalResourceViewResolver">
19. <beans:property name="prefix" value="/WEB-INF/views/" />
20. <beans:property name="suffix" value=".jsp" />
21. </beans:bean>
根据定义,视图解析器使用以下机制查找文件:
Prefix + ModelAndView name + suffix, which translates to : /WEB-INF/jsp/bookList.jsp
modeland view . add object(" bookList ",bookService.getBookList())将 getBookList()返回的图书数据添加到名为 bookList 的模型中,该模型由视图格式化并呈现。
最后,servlet 引擎通过指定的 JSP 呈现响应,如清单 5-43 所示。
清单 5-43 。视角
1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
2. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
3. <!DOCTYPE html>
4. <html>
5. <head>
6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7. <title>Your Book store</title>
8. </head>
9. <body>
10. <h1>Books List</h1>
11. <table border="1">
12. <tr>
13. <th align="left">Author</th>
14. <th align="left">Book Title</th>
15. </tr>
16. <c:forEach items="${bookList}" var="book">
17. <tr>
18. <td>${book.author.authorName}</td>
19.
20. <td>${book.bookTitle}</td>
21. </tr>
22. </c:forEach>
23. </table>
24. </body>
25. </html>
图 5-20 展示了书店应用的目录结构。
图 5-20 。书店应用的目录结构
让我们将清单 5-42 中基于界面的控制器替换为基于注释的控制器。清单 5-44 展示了基于注释的 BookController。
清单 5-44 。基于注释的图书管理员
1. package com.apress.bookstore.controller;
2. import com.apress.bookstore.service.BookService;
3. import org.springframework.stereotype.Controller;
4. import org.springframework.web.bind.annotation.RequestMapping;
5. import org.springframework.web.bind.annotation.RequestMethod;
6. import org.springframework.web.servlet.ModelAndView;
7.
8. @Controller
9. @RequestMapping("/list_book.html")
10. public class BookController {
11. @RequestMapping(method = RequestMethod.GET)
12. public ModelAndView bookListController() {
13. BookService bookManager = new BookService();
14. ModelAndView modelAndView = new ModelAndView("bookList");
15. modelAndView.addObject("bookList", bookManager.getBookList());
16. return modelAndView;
17. }
18. }
- 在基于注释的应用中,表单控制器是用@Controller 创建的。@Controller 表示特定的类充当控制器的角色。@Controller 还允许自动检测,这与 Spring 对检测类路径中的组件类和为它们自动注册 bean 定义的一般支持相一致。在这个例子中,@Controller 注释表明 BookListControler 类是一个控制器类。
- 第 9 行 : @RequestMapping 用于将/list_book.html 这样的 URL 映射到整个类或特定的处理程序方法上。类级别的@RequestMapping 表示该控制器上的所有处理方法都是相对于/list_book.html 路径的。
- 第 13 行 : @RequestMapping 在方法层表示该方法只接受 GET 请求;换句话说,一个/list_book.html 的 HTTP GET 涉及 bookListController()。
清单 5-45 展示了修改后的 bookstore-servlet.xml 来发现基于注释的 BookController。
清单 5-45 。bookstore-servlet.xml 支持基于注释的控制器
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans:beans fontname">http://www.springframework.org/schema/mvc"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans=
"http://www.springframework.org/schema/beans"
4. xmlns:context="http://www.springframework.org/schema/context"
5. xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
6. http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/
beans/spring-beans.xsd
7. http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/
context/spring-context.xsd">
8.
9. <!-- DispatcherServlet Context: defines this servlet's request-processing
10. infrastructure -->
11.
12. <context:component-scan base-package="com.apress.bookStore.controller" />
13. <beans:bean name="/list_book.html"
14. class="com.apress.bookstore.controller.BookController" />
15.
16.
17. <!-- Resolves views selected for rendering by @Controllers to .jsp resources
18. in the /WEB-INF/views directory -->
19. <beans:bean
20. class="org.springframework.web.servlet.view.InternalResourceViewResolver">
21. <beans:property name="prefix" value="/WEB-INF/views/" />
22. <beans:property name="suffix" value=".jsp" />
23. </beans:bean>
24. </beans:beans>
- 第 12 行 : <上下文:dispatcher servlet 的组件扫描>将@Controller 注释的类注册为 beans。BookListController 类被自动发现并注册为 bean。
使用注释处理表单
Spring Web MVC 中的注释驱动配置极大地简化了表单处理。Spring 通过数据绑定机制,从提交的表单中自动填充 Java 对象,并支持验证和错误报告,从而消除了对传统表单处理的需求。清单 5-46 演示了如何使用一个表单,然后处理用户输入的数据。图 5-21 说明了添加到书店目录结构中的新文件。
图 5-21 。用于表单处理的应用中的新文件
添加了一个新的控制器 AddBookController ,它负责所有使用注释的表单处理。清单 5-46 展示了 AddBookController。
清单 5-46 。为表单处理添加 BookController
1. package com.apress.bookstore.controller;
2.
3. import java.util.List;
4.
5. import org.springframework.stereotype.Controller;
6. import org.springframework.ui.ModelMap;
7. import org.springframework.validation.BindingResult;
8. import org.springframework.web.bind.WebDataBinder;
9. import org.springframework.web.bind.annotation.InitBinder;
10. import org.springframework.web.bind.annotation.ModelAttribute;
11. import org.springframework.web.bind.annotation.RequestMapping;
12. import org.springframework.web.bind.annotation.RequestMethod;
13. import org.springframework.web.bind.support.SessionStatus;
14. import org.springframework.web.context.request.WebRequest;
15.
16. import com.apress.bookstore.model.Author;
17. import com.apress.bookstore.model.Book;
18. import com.apress.bookstore.service.AuthorService;
19. import com.apress.bookstore.service.BookService;
20.
21. @Controller
22. @RequestMapping("/addBook.html")
23. public class AddBookController {
24. @RequestMapping(value="/addBook.html", method = RequestMethod.GET)
25. public String initForm(ModelMap model) {
26. Book book = new Book();
27. book.setBookTitle("Add Book :");
28. model.addAttribute("book", book);
29. return "addBook";
30. }
31.
32. @InitBinder
33. public void initBinder(WebDataBinder binder, WebRequest request) {
34. binder.setDisallowedFields(new String[] {"author"});
35. Book book = (Book)binder.getTarget();
36. AuthorService authorService = new AuthorService();
37. Long authorId = null;
38. try {
39. authorId = Long.parseLong(request.getParameter("author"));
40. } catch (Exception e) {}
41. if (authorId != null) {
42. Author author = authorService.getAuthorById(authorId);
43. book.setAuthor(author);
44. }
45. }
46.
47. @ModelAttribute("authorList")
48. public List<Author> populateAuthorList() {
49. AuthorService authorService = new AuthorService();
50. return authorService.getAuthorList();
51. }
52.
53. @RequestMapping(method = RequestMethod.POST)
54. public String processSubmit(@ModelAttribute("book") Book book, BindingResult result,
SessionStatus status) {
55. BookService bookService = new BookService();
56. bookService.createBook(book);
57. return "redirect:/list_book.html";
58. }
59. }
-
第 22 行:addbook controller 类用@RequestMapping("/addBook.html ")进行了注释,这意味着这个类中的所有方法都会处理对 URL"/ addBook.html "的请求。
-
第 24 行:绑定的初始化是通过用@ request mapping(method = request method)注释方法名来完成的。获取)。
-
第 25 行 : initForm()处理 GET 请求类型并显示添加新书表单。
-
第 28 行 : initForm()也向模型映射添加了一个新实例,这样这个新实例就可以与表单相关联了。
-
第 32 行:通过用@InitBinder 标注方法名来定义绑定。
-
用@InitBinder 注释控制器方法允许直接在控制器类中配置 web 数据绑定。@InitBinder 标识初始化 WebDataBinder 的方法,该 WebDataBinder 用于填充命令并形成带注释的处理程序方法的对象参数。这样的 init-binder 方法支持@RequestMapping 支持的所有参数,除了命令/表单对象和相应的验证结果对象。被声明的 Init-binder 方法不能有返回值。因此,它们通常被宣布为无效。
-
第 33 行:典型的参数包括与 WebRequest 或 java.util.Locale 结合的 WebDataBinder,允许代码注册特定于上下文的编辑器。
-
数据绑定是使用 WebDataBinder 类配置的。WebDataBinder 是一个特殊的 DataBinder,用于从 web 请求参数到 JavaBean 对象的数据绑定。
-
Spring 将这个类的一个实例注入到任何用@InitBinder 注释的控制器方法中。然后,该对象用于定义控制器的数据绑定规则。
-
WebRequest 允许通用请求参数访问以及请求/会话属性访问,而无需绑定到本地 Servlet API。
-
第 34 行 : setDisallowedFields()注册不允许绑定的字段。
-
第 47 行:通过用@ModelAttribute 注释方法名,引用数据被放入模型中,以便表单视图可以访问它。
- 当@ModelAttribute 放在方法参数上时,它将模型属性映射到特定的带注释的方法参数。这就是控制器如何获得对保存表单中输入的数据的对象的引用。
- @ModelAttribute 注释通知 Spring MVC 框架 authorList 实例应该被分配为 Author 类的实例,并且应该被传递以填充 AuthorList()。
-
第 53 行:表单提交通过用@ request mapping(method = request method)注释方法名来处理。贴)。
-
第 54 行 : processSubmit()接受 POST 请求;也就是说,针对/new_book.html 的 HTTP POST 调用 processSubmit()。processSubmit()处理表单数据。processSubmit()有三个参数:
- @ model attribute(value = " book ")Book Book:模型属性注释通知 Spring MVC 框架,Book 模型实例应该被赋值为 Book 类的实例,并且应该被传递给 processSubmit()方法。
- BindingResult 结果:Spring 在 Book 类的创建过程中确定错误(如果有的话)。如果发现错误,其描述将作为 BindingResult 实例传递给该方法。
- SessionStatus 状态:SessionStatus 是一个状态句柄,用于将表单处理标记为完成。
-
第 57 行:return 语句中的 redirect:前缀触发 HTTP 重定向回浏览器。当将响应委托给另一个控制器,而不仅仅是呈现视图时,这是必要的。
清单 5-47 展示了修改后的书店应用的服务层,用于表单处理。
清单 5-47 。图书服务
package com.apress.bookstore.service;
import java.util.LinkedList;
import java.util.List;
import com.apress.bookstore.model.Author;
import com.apress.bookstore.model.Book;
public class BookService {
private static List<Book> bookList;
static {
Author author1 = new Author();
author1.setAuthorId((long) 1);
author1.setAuthorName("Vishal Layka");
Book book1 = new Book();
book1.setBookId((long) 1);
book1.setBookTitle("Beginning Groovy, Grails and Griffon");
book1.setAuthor(author1);
Book book2 = new Book();
book2.setBookId((long) 2);
book2.setBookTitle("Modern Java Web Development");
book2.setAuthor(author1);
bookList = new LinkedList<Book>();
bookList.add(book1);
bookList.add(book2);
}
public List<Book> getBookList() {
return bookList;
}
public Book createBook(Book b) {
Book book = new Book();
book.setBookId((long)bookList.size() + 1);
book.setAuthor(b.getAuthor());
book.setBookTitle(b.getBookTitle());
bookList.add(book);
return book;
}
}
清单 5-48 展示了用于表单处理的书店应用的修改后的 bookList.jsp。
清单 5-48 。bookList.jsp
1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
2. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
3. <!DOCTYPE html>
4. <html>
5. <head>
6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7. <title>Your Book store</title>
8. </head>
9. <body>
10. <h1>Books List</h1>
11. <table border="1">
12. <tr>
13. <th align="left">Author</th>
14. <th align="left">Book Title</th>
15. </tr>
16. <c:forEach items="${bookList}" var="book">
17. <tr>
18. <td>${book.author.authorName}</td>
19.
20. <td>${book.bookTitle}</td>
21. </tr>
22. </c:forEach>
23. </table>
24. <br/>
25. <a href="addBook.html">Add books.</a>
26. </body>
27. </html>
- 第 25 行:使用 AddBookController 调用表单控制器,它被映射到清单 5-46 中 AddBookController 的第 22 行和第 24 行。
清单 5-49 展示了用于表单处理的书店应用的新 JSP 页面,当使用Add books 调用 AddBookController 时,将显示该页面。清单 5-48 第 25 行< /a >。
清单 5-49 。addBook.jsp
1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
2. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3. <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
4.
5. <!DOCTYPE html>
6. <html>
7. <head>
8. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
9. <title>Your Book store</title>
10. </head>
11. <body>
12. <h1>Add Book</h1>
13. <form:form method="post" commandName="book">
14. Author<br />
15. <form:select path="author">
16. <form:options items="${authorList}" itemValue="authorId" itemLabel="authorName" />
17. </form:select>
18. <br /><br />
19. Book Name<br />
20. <form:input path="bookTitle"/><br /><br />
21. <br />
22. <input type="submit" value="Submit">
23. </form:form>
24. </body>
25. </html>
清单 5-49 展示了显示的表单。它还展示了 Spring form 标签的用法。
基于注释的验证
下一节演示了如何使用注释来验证用户输入的数据。图 5-22 说明了应用中修改的文件和新增的文件。
图 5-22 。目录结构中的修改文件和新文件,用于基于注释的验证
清单 5-50 展示了 BookValidator。
清单 5-50 。BookValidator 中的验证
1. package com.apress.bookstore.validator;
2.
3. import org.springframework.validation.Errors;
4. import org.springframework.validation.ValidationUtils;
5. import org.springframework.validation.Validator;
6.
7. import com.apress.bookstore.model.Book;
8.
9. public class BookValidator implements Validator {
10. @Override
11. public boolean supports(Class clazz) {
12. return Book.class.equals(clazz);
13. }
14.
15. @Override
16. public void validate(Object obj, Errors errors) {
17. Book book = (Book) obj;
18. ValidationUtils.rejectIfEmptyOrWhitespace(errors, "bookTitle", "field.required",
"Required Field");
19. if ( ! errors.hasFieldErrors("bookTitle")) {
20. if (book.getBookTitle().isEmpty())
21. errors.rejectValue("Title", "", "Cannot be left empty!");
22. }
23. }
24.
25.
26. }
- 第 19 到 23 行:应用中的典型验证
名为 AddBookController 的控制器被更新用于验证,如清单 5-51 中的所示。
清单 5-51 。更新 AddBookController
1. package com.apress.bookstore.controller;
2.
3. import java.util.List;
4.
5. import org.springframework.beans.factory.annotation.Autowired;
6. import org.springframework.stereotype.Controller;
7. import org.springframework.ui.ModelMap;
8. import org.springframework.validation.BindingResult;
9. import org.springframework.web.bind.WebDataBinder;
10. import org.springframework.web.bind.annotation.InitBinder;
11. import org.springframework.web.bind.annotation.ModelAttribute;
12. import org.springframework.web.bind.annotation.RequestMapping;
13. import org.springframework.web.bind.annotation.RequestMethod;
14. import org.springframework.web.bind.support.SessionStatus;
15. import org.springframework.web.context.request.WebRequest;
16.
17. import com.apress.bookstore.model.Author;
18. import com.apress.bookstore.model.Book;
19. import com.apress.bookstore.service.AuthorService;
20. import com.apress.bookstore.service.BookService;
21. import com.apress.bookstore.validator.BookValidator;
22.
23. @Controller
24. @RequestMapping("/addBook.html")
25. public class AddBookController {
26. BookValidator bookValidator;
27.
28. @Autowired
29. public AddBookController(BookValidator bookValidator) {
30. this.bookValidator = bookValidator;
31. }
32.
33. @RequestMapping(value="/addBook.html", method = RequestMethod.GET)
34. public String initForm(ModelMap model) {
35. Book book = new Book();
36. book.setBookTitle("Add Book :");
37. model.addAttribute("book", book);
38. return "addBook";
39. }
40.
41. @InitBinder
42. public void initBinder(WebDataBinder binder, WebRequest request) {
43. binder.setDisallowedFields(new String[] {"author"});
44. Book book = (Book)binder.getTarget();
45. AuthorService authorService = new AuthorService();
46. Long authorId = null;
47. try {
48. authorId = Long.parseLong(request.getParameter("author"));
49. } catch (Exception e) {}
50. if (authorId != null) {
51. Author author = authorService.getAuthorById(authorId);
52. book.setAuthor(author);
53. }
54. }
55.
56. @ModelAttribute("authorList")
57. public List<Author> populateAuthorList() {
58. AuthorService authorService = new AuthorService();
59. return authorService.getAuthorList();
60. }
61.
62. @RequestMapping(method = RequestMethod.POST)
63. public String processSubmit(@ModelAttribute("book") Book book, BindingResult result,
SessionStatus status) {
64. BookService bookService = new BookService();
65. bookService.createBook(book);
66. if(result.hasErrors()) {
67. return "addBook";
68. } else {
69. bookService.createBook(book);
70. return "redirect:/list_book.html";
71. }
72.
73. }
74. }
- 第 29 行:使用 setter 方法注入 BookValidator 类。
- 第 63 行:在 processSubmit()中,调用 BookValidator 的 validate()检查图书明细是否是用户输入的。向 validate()传递图书模型和 BindingResult 对象的值以保存错误(如果有)。
- 第 66 行:检查结果变量是否有错误。如果有错误,应用将显示带有错误消息的相同页面。如果没有错误,也就是说,用户已经输入了所有正确的数据,那么应用将显示图书详细信息列表以及新输入的图书详细信息。
配置验证程序
现在你必须在 bookstore-servlet.xml 中声明 URL addBook.html 的验证器,如清单 5-52 所示。
清单 5-52 。声明 BookValidator
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans:beans fontname">http://www.springframework.org/schema/mvc"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans=
"http://www.springframework.org/schema/beans"
4. xmlns:context="http://www.springframework.org/schema/context"
5. xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/
schema/mvc/spring-mvc.xsd
6. http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/
beans/spring-beans.xsd
7. http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/
context/spring-context.xsd">
8.
9. <!-- DispatcherServlet Context: defines this servlet's request-processing
10. infrastructure -->
11.
12. <context:component-scan base-package="com.apress.bookstore.controller" />
13. <beans:bean class="com.apress.bookstore.validator.BookValidator" />
14.
15. <!-- Resolves views selected for rendering by @Controllers to .jsp resources
16. in the /WEB-INF/views directory -->
17. <beans:bean
18. class="org.springframework.web.servlet.view.InternalResourceViewResolver">
19. <beans:property name="prefix" value="/WEB-INF/views/" />
20. <beans:property name="suffix" value=".jsp" />
21. </beans:bean>
22. </beans:beans>
- 第 13 行:定义了 BookValidator 类。容器通过调用其构造函数来创建 BookValidator 类。
摘要
本章首先简要介绍了 Spring 框架,然后展示了如何处理紧耦合、横切关注点和样板代码。本章转换了在第一章中构建的数据访问层,以消除因使用纯 JDBC 而产生的样板代码。然后讨论了 Spring MVC 的架构,包括它的请求处理生命周期。接下来展示了如何使用 Spring Web MVC 开发 Hello World 应用。然后,它开始使用 Spring Web MVC 实现书店应用,随着应用的发展,它引入了 Spring Web MVC 的注释编程模型,并在 Web 应用中处理表单。
7【redis.io/】??