2. 第一个示例
我们的大多数示例将仅限于使用 Struts 2 实现的 Web 层:
![]() |
待掌握基础知识后,我们将研究一个采用多层架构的更复杂示例。
2.1. 示例生成
我们开始构建第一个应用程序。
![]() |
- 在 [1] 中,创建一个新项目
- 在 [2] 中,选择 Java Web / Web Application 类型
- 在 [3] 中,为项目命名
- 在 [4] 中,指定项目位置。
- 在 [5] 中,将新项目设为主项目。
![]() |
- 在 [6] 中,选择 Tomcat 服务器。安装 NetBeans 7.01 时,已安装了两个服务器:Apache Tomcat 和 Glassfish 3.1。
- 在 [7] 中,指定将使用 Struts 2 框架。正是安装了 Struts 2 插件才使我们能够进行此操作。如果没有该插件,系统将不会提供 Struts 2 框架选项。
- 在 [8] 中,我们请求创建即将学习的示例项目。
- 在 [9] 中,我们可以查看将要使用的 Struts 2 库。
![]() |
- 在 [10] 中,是生成的项目。我们稍后将回到这里。
- 在 [11] 中,是项目的库文件。它们已由 Struts 2 插件集成。如果未安装该插件,可在下载的 Struts 2 发行版中的 [lib] 文件夹内找到这些库文件。届时请按照步骤 12 和 13 操作。
2.2. 在文件系统中生成的项目
![]() |
- 在 [1] 中,[Projects] 选项卡显示了该项目的“开发者”视图
- 在 [2] 中,[Files] 选项卡显示了文件系统中的项目文件夹
- 在 [2A] 中,分支 [Web Pages] 在 [2] 中由文件夹 [web] [2B] 表示
- 在 [3A] 中,分支 [Source Packages] 在 [2] 中由文件夹 [java] [3B] 表示
2.3. 配置文件 [META-INF/context.xml]
![]() |
该文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/exemple-01"/>
第 2 行表明 Web 应用程序的上下文为 /exemple-01。所有 [http://machine:port/exemple-01/...] 格式的 URL 都将由该应用程序处理。可在项目 [2] 的属性中找到此上下文:右键单击项目 / 属性 / 运行。
2.4. 配置文件 [WEB-INF/web.xml]
![]() |
所有 Web 应用程序均通过应用程序 [WEB-INF] 文件夹中的 [web.xml] 文件进行配置。生成的配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>example/HelloWorld.jsp</welcome-file>
</welcome-file-list>
</web-app>
- 第3-6行定义了一个由Struts 2的[org.apache.struts2.dispatcher.FilterDispatcher]类实现的过滤器。该类将充当MVC模型的C控制器。
- 第7-10行:定义了URL模式与过滤器之间的关联,该过滤器负责处理符合此模式的URL。 此处指定所有 URL(模式 /*) 均应由名为 struts2 的过滤器处理。该过滤器即第 3-6 行所定义的过滤器。因此,此处所有 URL 都将通过 Struts 2 控制器。
- 第11-15行:定义用户会话的时长,此处为30分钟。在处理不同请求时,用户会通过一个会话令牌进行追踪,该令牌在其首次请求时被分配,随后用户会在每次新请求中系统性地发送该令牌。 这使得Web服务器能够识别用户,并为其管理一个称为“会话”的“记忆”。如果两次请求之间间隔超过30分钟,系统将为用户生成一个新的会话令牌,用户因此会丢失其“记忆”并重新开始。
- 第16-18行:定义了当用户访问Web应用程序但未请求具体文档时应显示的文件。因此,当请求的URL为[http://machine:port/exemple-01]时,返回的URL将为[http://machine:port/exemple-01/example/HelloWord.jsp]。
2.5. 配置文件 [struts.xml]
![]() |
文件 [struts.xml] 是 Struts 2 的配置文件。它可以在项目 ClassPath 的任意位置。在上述 NetBeans 项目中,项目的 ClassPath 由两个分支组成:
- 源代码包
- 库
因此,位于这两个分支中的任何文件或库都属于该项目的 ClassPath。通常将 [struts.xml] 放置在 <default package> 中。此处的具体内容如下:
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<include file="example.xml"/>
<!-- 默认包的配置。 -->
<package name="default" extends="struts-default">
</package>
</struts>
- 第 5 行和第 10 行:文档的根标签是 <struts>
- 第 6 行:文件 [example.xml] 将插入此处。因此它带来了自己的配置。我们稍后会再讨论这一点。
- 第 8-9 行:定义了一个名为“default”的包。包用于配置一组具有相同 URL 的 Struts 2 动作。例如 [/chemin/Action1] 和 [/chemin/Action2]。因此,我们可以为这些动作定义一个包:
<package name="employes" namespace="/employes" extends="struts-default">
... configuration
</package>
上述包名为“employes”,用于配置 URL 操作 /employes/Action。该包可通过关键字“extends”继承另一个包。 在上例中,包 employes 继承自包 struts-default。该包位于库 struts2-core.jar 的文件 [struts-default.xml] 中:
![]() |
在文件 [struts-default.xml] 中定义的“struts-default”包配置了多项内容,其中包括在调用操作时执行的拦截器列表。让我们回到 Struts 2 应用程序的 MVC 结构:
![]() |
为了处理 [http://machine:port/.../Action] 格式的 URL,控制器 [FilterDispatcher] 将实例化实现所请求操作的类,并执行该类的一个方法,默认情况下是名为 execute 的方法。 对该方法 execute 的调用将经过一系列拦截器:
![]() |
拦截器和该操作都处理同一个请求。 在 struts-default 包中定义的拦截器列表在大多数情况下已足够。因此,我们的 Struts 包将始终继承 struts-default 包。要了解各个拦截器的作用,请参阅 [ref2] 的第 4 章。
让我们回到配置文件 struts.xml:
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<include file="example.xml"/>
<!-- 默认包的配置。 -->
<package name="default" extends="struts-default">
</package>
</struts>
- 第 8 行:名为 default 的包具有特殊作用。它处理其他包中未配置的操作。在此,第 8-9 行中未对 default 包进行任何配置。因此,我们可以删除该包的定义。
现在来看文件 [struts.xml] 第 6 行中包含的文件 [example.xml]:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="example" namespace="/example" extends="struts-default">
<action name="HelloWorld" class="example.HelloWorld">
<result>/example/HelloWorld.jsp</result>
</action>
</package>
</struts>
- 第 8 行:定义了一个名为 example 的包,该包继承自 struts-default 包。该包管理 /example/Action 类型的 URL(命名空间 /example)。
- 第 9-11 行:定义了一个名为 HelloWorld 的操作(属性 name),因此对应于 URL /example/Helloworld。 该操作由类 example.HelloWorld 的实例处理(属性 class)。
- 控制器 [FilterDispatcher] 将调用该类的 execute 方法。
- 该方法将返回一个称为导航键的字符串。
- 不同的导航键必须通过 <result name="键"/> 标签定义(第 10 行)。若未指定 name 属性,则默认使用 success 键。上文即为这种情况。 因此,类 example.HelloWorld 中的方法 execute 应向控制器 [FilterDispatcher] 返回键 success。
- 随后,控制器 [FilterDispatcher] 会显示页面 [/example/HelloWorld.jsp](第 10 行)。
如果我们将两个文件 [struts.xml] 和 [example.xml] 合并,并删除看似多余的包 default, 结果就好像我们将文件 [struts.xml] 缩减为仅剩 [example.xml] 这一文件。
2.6. 操作 HelloWorld
![]() |
根据所研究的 [struts.xml] 文件,当客户端请求的 URL 为 /example/HelloWorld 时,将触发 HelloWorld 操作。 此时将执行其方法 execute。该方法必须返回一个导航键。 我们看到只有一个导航键:success,因此系统会将页面 /example/HelloWorld.jsp 作为响应发送给用户。
HelloWorld 操作的代码如下:
package example;
import com.opensymphony.xwork2.ActionSupport;
public class HelloWorld extends ActionSupport {
public String execute() throws Exception {
setMessage(getText(MESSAGE));
return SUCCESS;
}
public static final String MESSAGE = "HelloWorld.message";
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
- 第5行:类HelloWorld继承自Struts 2的类ActionSupport。这种情况几乎总是如此。这使得我们可以使用某些方法,例如第8行中的方法getText。
- 第7-10行:execute方法在调用Struts控制器时被执行。 我们知道该方法必须返回一个字符串,因此第7行的签名如此。在此处,它将返回同样在ActionSupport中定义的常量SUCCESS。其他常量也以类似方式定义,用于execute方法的返回结果:
SUCCESS | "success" |
ERROR | "error" |
INPUT | "input" |
LOGIN | "login" |
因此,此处的 execute 方法会生成字符串 success。 如果参考文件 [struts.xml],那么返回给用户的页面将是 /example/HelloWorld.jsp。
- 第 8 行:方法 execute 将第 14 行的字段 message 初始化为 getText(" HelloWorld.message ") 的值。 方法 getText 属于父类 ActionSupport。它允许根据所用语言从文件中提取文本。默认情况下,将使用位于与该操作同一包中的文件 package.properties。该文件有两个版本:
文件 [package.properties] 内容如下:
这是一系列形式为 clé=valeur 的文本行。如果使用该文件,getText("HelloWorld.message") 的值将为 Struts is up and running ...
而文件 [package_es.properties] 内容如下:
如果客户端浏览器使用的语言是西班牙语(package_es.properties中的es属性),则getText("HelloWorld.message")的值将为¡Struts está bien! ... 在所有其他情况下,将使用文件 [package.properties]。
2.7. 视图 HelloWorld.jsp
![]() |
这是 Struts 拼图的最后一块。当请求 URL /example/HelloWorld 时,显示的就是这个视图。我们已经看到初始请求是如何经过一系列处理,最终显示出这个响应的。该页面的代码如下:
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title><s:text name="HelloWorld.message"/></title>
</head>
<body>
<h2><s:property value="message"/></h2>
<h3>Languages</h3>
<ul>
<li>
<s:url id="url" action="HelloWorld">
<s:param name="request_locale">en</s:param>
</s:url>
<s:a href="%{url}">English</s:a>
</li>
<li>
<s:url id="url" action="HelloWorld">
<s:param name="request_locale">es</s:param>
</s:url>
<s:a href="%{url}">Espanol</s:a>
</li>
</ul>
</body>
</html>
- 该页面使用了 HTML 标签(第 5、6 行等)以及第 3 行定义的库中的标签。所有 <s:xx> 标签均属于该库。
- 第7行:<s:text>标签可根据客户端浏览器的语言显示不同的文本。name属性指定了在消息文件中要查找的键。在此处,同样将调用package_xx.properties文件。 需注意,这些文件仅包含一条键值为 HelloWorld.message 的消息。
- 第 11 行:<s:property name="propriété"> 标签用于写入名为 ActionContext 的对象的某个属性值。该对象包含:
- 已执行操作的属性。name="message" 将显示当前操作中 message 字段的值。与该字段关联的 get 和 set 方法用于获取其值或对其进行初始化。因此,这些方法必须存在。
- 当前请求的属性,标记为 <s:property name="#request['clé']">
- 用户会话的属性,标记为 <s:property name="#session['clé']">
- 应用程序本身的属性,标记为 <s:property name="#application['clé']">
- 客户端浏览器发送的参数,标记为 <s:property name="#parameters['clé']">
- <s:property name="#attr['clé']"> 表示从页面、请求、会话、应用程序中按此顺序查找对象的值。
- 第16-18行:<s:url>标签用于定义URL。id属性为即将创建的URL命名,该名称随后在第19行被使用。action属性指定URL应指向哪个操作。
- 第 17 行:<s:param ..> 标签允许以 ?param1=valeur1¶m2=valeur2&... 的形式向 URL 添加参数。此处添加的参数为 ?request_locale=es。
最终生成的 URL 将如下所示:
要理解这个 URL,需注意页面 [HelloWorld.jsp] 在以下两种情况下会被显示:
- 直接请求 URL [/exemple-01/example/HelloWorld.jsp]
- 调用操作 [/exemple-01/example/HelloWorld.action]
在这两种情况下,URL的路径均为 /exemple-01/example.。标签 <s:url action= "... "> 会将由属性 action 定义的操作添加到该路径中。 因此得到 /example-01/example/HelloWorld。随后,它会在前一个 URL 后添加后缀 .action,以及 URL 中的参数(如有)。 生成的 URL /example-01/example/HelloWorld.action?request_locale=en 将调用在文件 [struts.xml] 中定义的动作 HelloWorld,同时传递参数 request_locale=en。 该参数不会由操作 HelloWorld 处理,而是由 Struts 的拦截器之一处理,即负责页面国际化的拦截器。 参数 request_locale 将被识别并处理。页面的语言将变为英语(en)。
- 第 19 行:定义一个 HTML 链接。<a> 标签的 href 属性需要一个字符串。此处,我们希望使用第 16 行定义的、id 为 url 的 URL 值。为此,我们写入 href="%{url}"。 变量 url 被求值,其值被赋给属性 href。在大多数情况下,变量的求值是隐式的。例如,当我们写
<s:property name= "message "/>
时,显示的是属性 message 的值,而不是字符串 "message"。但在其他情况下,需要强制求值变量或属性。 如果写成 href="url",那么字符串 url 就会被赋值给 href 属性。
- 第23-27行:创建一个HTML链接,用于将页面语言切换为西班牙语。
2.8. 应用程序的运行
我们启动项目的运行:
![]() |
- 在 [1] 中,启动项目 [exemple-01] 的执行。 如果 Tomcat Web 服务器尚未启动,此时将自动启动。请求 URL [/exemple-01] 时,将调用 [3]。随后使用文件 [web.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>example/HelloWorld.jsp</welcome-file>
</welcome-file-list>
</web-app>
- 因为在请求的 URL [/exemple-01] 中未指定具体页面,Tomcat 将使用第 16 行和第 18 行中的 <welcome_file-list> 标签。 因此,将返回 URL /example-01/example/HelloWorld.jsp。
- 由于 Struts 2 处理所有 URL(第 8 行和第 9 行),该 URL 将被 Struts 过滤。由于它不对应于一个 Action,而是对应于一个 JSP 页面,因此该页面将被显示。
- 因此,我们在 [2] 中看到的就是我们之前研究过的 HelloWorld.jsp 页面。
- 在 [4] 中,可以看到标签 <title><s:text name="HelloWorld.message"/></title> 并未生效,标签 <h2><s:property value="message"/></h2> 也是如此。原因是未调用任何操作。 因此,在操作之前执行的拦截器列表并未被执行,特别是负责国际化的那个。 国际化标签 <s:text ...> 无法被正确处理。此外,引用类 Action1 的 message 字段的属性 message 不存在。因此未显示内容。
现在,我们点击链接 [English]。我们得到以下页面:
![]() |
- 在 [1] 中,即请求的 URL。我们已解释过该 URL 的生成过程。此次请求的是一个 Struts 操作:在 [example.xml] 中定义的操作 HelloWorld。
<struts>
<package name="example" namespace="/example" extends="struts-default">
<action name="HelloWorld" class="example.HelloWorld">
<result>/example/HelloWorld.jsp</result>
</action>
</package>
</struts>
- 该操作中的方法 execute 已被执行。 我们看到它返回了键 success。从上面的第 4 行可以推断出,页面 /example/HelloWorld.jsp 作为响应返回给客户端。这就是我们在 [3] 中看到的内容。
- 在 [1] 中,我们可以看到请求的 URL 通过参数 request_locale=en 进行了设置。页面语言将从此变为英语。实际上,该语言选择存储在用户的会话中,并会一直保留该选择,直到用户更改为止。
- 在 [2] 和 [3] 中,我们可以看到国际化机制正在发挥作用。 标签 <title><s:text name="HelloWorld.message"/></title> 和 <h2><s:property value="message"/></h2> 此次已发挥作用。
如果现在我们选择链接 [Espanol],就会得到西班牙语页面:
![]() |
2.9. Conclusion
我们研究了一个由 NetBeans 的 Struts 2 插件自动生成的示例。我们发现,追踪请求的处理过程相当困难。涉及以下要素:
- 配置 [web.xml], [struts.xml]
- 执行的操作:拦截器与方法 execute。
起初,Struts 2 的工作机制可能显得复杂,需要一些时间来适应。接下来我们将介绍一系列示例,每个示例都阐释了 Struts 的某个特定要点。















