为Struts 2.0做好准备 - Max On Java - BlogJava

为Struts 2.0做好准备
http://www.blogjava.net/max/category/16130.html

Struts作为MVC 2的Web框架，自推出以来不断受到开发者的追捧，得到用广泛的应用。作为最成功的Web框架，Struts自然拥有众多的优点：

  • MVC 2模型的使用
  • 功能齐全的标志库（Tag Library）
  • 开放源代码

但是，所谓“金无赤金，人无完人”，Struts自身也有不少的缺点：

  • 需要编写的代码过多，容易引起“类爆炸”
  • 单元测试困难

这些缺点随着Web的发展越来越明显。这就促生了Struts 2.0，它的诞生能很好的解决上述问题。 好啦，废话就不多说了，现在就让我们感受一下的Struts 2.0的魅力吧。

  1. 搭建开发和运行环境
    1. 到Apache下载Struts 2.0包

    2. 打开Eclipse 3.2新建Web工程

      点击菜单File\New\Project，出现如图1所示对话框
      图1 新建工程对话框
      图1 新建工程对话框
      选择Web\Dynamic Web Project，点击“Next”，出现图2对话框

      图2 新建动态Web工程对话框
      图2 新建动态Web工程对话框
      在“Project Name”中键入Struts2_HelloWorld，点击“New”，出现以下对话框

      图3 新建服务器运行时对话框
      图3 新建服务器运行时对话框
      选择“Apache\Apache Tomat v5.5”，点击“Next”，出现以下对话框

      图4新建服务器运行时对话框
      图4新建服务器运行时对话框
      点击“Finish”，关闭对话框。

    3. 将Struts 2.0 lib下的jar文件加到工程的构建路径（build path）

      图5 Struts 2.0的lib目录
      图5 Struts 2.0的lib目录
      按ctr+a全选，复制，再转到Eclipse窗口，在“Project Explorer”子窗口中选中Struts2_HelloWorld\WebContent\WEB-INF\lib，然后粘贴。经过Eclipse自动刷新“Project Explorer”子窗口，刚才所粘贴的jar文件应该会出现在Struts2_HelloWorld\Java Resources: src\Libraries\Web App Libraries下，如图6所示：

      图6 Project Explorer子窗口
      图6 Project Explorer子窗口

    4. 打开web.xml文件，将其修改为以下代码：
      <?xml version="1.0" encoding="ISO-8859-1"?>
      <!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>Struts 2.0 Hello World</display-name>
      <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>
      <welcome-file-list>
      <welcome-file>index.html</welcome-file>
      </welcome-file-list>
      </web-app>

    5. 新建struts.xml文件

      右键点击，Struts2_HelloWorld\Java Resources: src，出现如图7所示菜单
      图7 新建Other菜单
      图7 新建Other菜单
      点击“Other”，出现新建对话框，如图8所示

      图8 新建对话框
      图8 新建对话框
      点击“Next”，出现新建文件对话框，如图9所示

      图9 新建文件对话框
      图9 新建文件对话框
      在“File name”中键入sturts.xml，点击“Finish”，然后将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="struts-default.xml"/>
      </struts>

    6. 新建index.html文件

      右键点击Struts2_HelloWorld\WebContent，出现如图10所示的菜单
      图10 新建Other菜单
      图10 新建Other菜单
      点击“Other”，出现新建对话框，如图11所示

      图11 新建对话框
      图11 新建对话框
      选择Web\HTML，点击“Next”出现如图12所示的对话框

      图12 新建HTML页面对话框
      图12 新建HTML页面对话框
      在“File Name”中键入index.html，点击“Next”，出现如图13所示的对话框

      图13 模板选择对话框
      图13 模板选择对话框
      点击“Finish”，将index.html的内容修改为以下内容：

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Hello World</title>
      </head>
      <body>
      <h3>Hello World!</h3>
      </body>
      </html>

    7. 将应用程序打包到tomcat上

      右键点击Struts_HelloWorld，出现如图14所示的菜单
      图14 工程菜单
      图14 工程菜单
      点击“Export\WAR file”，出现如图15所示的对话框

      图15 输出对话框
      图15 输出对话框
      选择“Web\WAR file”，点击“Next”，出现如图16所示的对话框

      图16 输出路径对话框
      图16 输出路径对话框
      输入war文件的路径（如%tomcat%\webapps\Struts2_HelloWorld.war），点击“Finish”关闭对话框。

    8. 启动tomcat，运行应用程序

      打开你的Internet Explorer，键入http://localhost:8080/Struts2_HelloWorld/，窗口输出如图17所示
      图17 Hello World窗口
      图17 Hello World窗口

  2. 第一个Struts 2.0应用程序——Hello World
    1. 新建类包（package）

      右键点击Struts2_HelloWorld\Java Resources: src，出现如图18所示菜单
      图18 新建菜单
      图18 新建菜单"
      点击“New\Package”，出现如图19所示对话框

      图19新建Java类包对话框
      图19新建Java类包对话框
      在“Name”键入tutorial，点击“Finish”关闭对话框。

    2. 新建HelloWorld.java文件

      右键点击Struts2_HelloWorld\Java Resources: src\tutorial，出现如图20所示菜单
      图20 新建菜单
      图20 新建菜单
      点击“New\Class”，出现如图21所示对话框

      图21 新建Java类对话框
      图21 新建Java类对话框
      在“Name”中键入HelloWorld，在“Superclass”中键入com.opensymphony.xwork2.ActionSupport，点击“Finish”关闭对话框。将HelloWorld.java的内容修改为：

      package tutorial;

      import com.opensymphony.xwork2.ActionSupport;

      public class HelloWorld extends ActionSupport [image] {
      private String name;

      public String getName() [image] {
      return name;
      }

      public void setName(String name) [image] {
      this.name = name;
      }

      public String execute() [image] {
      name       = "Hello, " + name + "!";
      return SUCCESS;
      }
      }

    3. 在struts.xml中添加action映射(mapping)
      <!DOCTYPE struts PUBLIC
      "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
      "http://struts.apache.org/dtds/struts-2.0.dtd"      >
      <struts>
      <include file="struts-default.xml"/>
      <package name="tutorial" extends="struts-default">
      <action name="HelloWorld" class="tutorial.HelloWorld">
      <result>HelloWorld.jsp</result>
      </action>
      </package>
      </struts>

    4. 新建SayHello.jsp

      参考“新建index.html文件”步骤，弹出如图22所示对话框
      图22 新建对话框
      图22 新建对话框
      点击“Next”, 进入下一步，如图23所示

      图23 新建JSP对话框
      图23 新建JSP对话框
      在“File name”键入SayHello.jsp，点击“Next”进入下一步，如图24所示

      图24 模板选择对话框
      图24 模板选择对话框
      点击“Finish”关闭对话框，并将SayHello.jsp的内容修改为：

      <% [image] @ page contentType="text/html; charset=UTF-8" %>
      <% [image] @ taglib prefix="s" uri="/struts-tags" %>
      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <title>Say Hello</title>
      </head>
      <body>
      <h3>Say "Hello" to: </h3>
      <s:form action="HelloWorld">
      Name:       <s:textfield name="name" />
      <s:submit />
      </s:form>
      </body>
      </html>

    5. 新建HelloWorld.jsp(请参考上一步)，HelloWorld.jsp的内容为：
      <% [image] @ page contentType="text/html; charset=UTF-8" %>
      <% [image] @ taglib prefix="s" uri="/struts-tags" %>
      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <title>Hello</title>
      </head>
      <body>
      <h3><s:property value="name" /></h3>
      </body>
      </html>

    6. 重新打包发布应用程序

      先停止tomcat, 再将tomcat里webapps下的Struts2_HelloWorld.war和Struts2_HelloWorld文件夹删除，参照“将应用程序打包到tomcat上”重新发布应用程序。

    7. 启动tomcat，运行测试

      打开Internet Explorer，键入http://localhost:8080/Struts2_HelloWorld/SayHello.jsp，窗口输出如图25所示

      图25 SayHello.jsp
      在“Name”键入字符串（如World），点击Submit，转到HelloWorld.jsp页面，如图26所示

      图26 HelloWorld.jsp

  3. 单元测试Hello World

    在文章开始的时候提及，单元测试困难是Struts一大缺点。现在让我们在体验一下，在Struts 2.0中是如何进行测试的。

    1. 新建JUnit单元测试

      右键点击Struts2_HelloWorld\Java Resources: src\tutorial，弹出如图27所示对话框
      图27 新建菜单
      图27 新建菜单
      点击“Next\Other”

      图28 新建对话框
      图28 新建对话框
      选择“Java\JUnit\JUnit Test Case”，点击“Next”

      图29 新建JUnit 测试用例对话框
      图29 新建JUnit 测试用例对话框
      选择“New JUnit 4 test”，在“Name”中键入HelloWorldTest，在“Class under test”键入tutorial.HelloWorld，点击“Next”

      图30 选择方法对话框
      图30 选择方法对话框
      选中HelloWorld\execute方法，点击Finish。如果生成的HelloWorldTest.java文件的图标（Icon）出现红色交叉标志，请进行以下步骤添加JUnit 4的jar包。

      右键点击Struts2_HelloWorld，出现如图所示菜单。 图31 新建菜单
      图31 新建菜单
      点击“Build Path\Add Libararis”，弹出图32对话框

      图32 添加库对话框
      图32 添加库对话框
      选中“JUnit”，点击“Next”

      图33 选择版本对话框
      图33 选择版本对话框
      选择“JUnit 4”，点击“Finish”关闭对话框，并将HelloWorldTest.java的内容修改为：

      package tutorial;

      import static org.junit.Assert.assertTrue;

      import org.junit.Test;

      import com.opensymphony.xwork2.ActionSupport;

      public class HelloWorldTest [image] {

      @Test
      public void testExecute() [image] {
      HelloWorld hello       = new HelloWorld();
      hello.setName(      "World");
      String result       = hello.execute();

      assertTrue(      "Expected a success result!", ActionSupport.SUCCESS.equals(result));

      final String msg = "Hello, World!";
      assertTrue(      "Expected the default message!", msg.equals(hello.getName()));
      }

      }

    2. 运行单元测试

      右键点击Struts2_HelloWorld\Java Resources: src\tutorial\HelloWorldTest.java，弹出如图34所示菜单
      图34 运行为菜单
      图34 运行为菜单
      点击“Run As\JUnit Test”，出现JUnit子窗口如图35所示

      图35 JUnit子窗口
      图35 JUnit子窗口
      图35的绿色矩形表示，所有单元测试通过。

  4. 总结

    上面的例子简单地演示了，Web 应用程序的基本操作，也即是，页面输入->Action处理->再输出到另外页面。Struts 2.0的简单易用、方便测试相信也会给大家留下不错的印象吧。我相信，Struts 2.0作为一个全新的Web架构，将会再次掀起Web开发的热潮。 不过，Struts 2.0还在测试中，正式版的发布还需些时日，所以文档方面可能有所欠缺。请大家继续留意我的博客，我会尽我所能为大家写更多关于Struts 2.0的文章。

posted on 2006-10-10 18:35 Max 阅读(12691) 评论(112) 编辑 收藏 引用 所属分类: Struts 2.0系列
评论共2页: 1 2

评论:
# re: 为Struts 2.0做好准备 2006-10-26 22:57 | esmiles
纠正一个错误：
HelloWorld.jsp中的<s:property name="name" />
name属性是错的。应该改成value属性，既：
<s:property value="name" />

回复 更多评论
# re: 为Struts 2.0做好准备 2006-10-27 09:12 | Max
@esmiles
Thanks，已经改正。 回复 更多评论
# re: 为Struts 2.0做好准备 2006-10-27 14:48 | Max
@风人园
我没有遇过这种情况。
其实不用加也是可以运行的，只不过在tomcat加载应用程序时会有提示。
加一个空白的struts.properties文件就可以解决。
Struts 2.0有两个配置文件，struts.xml和struts.properties都是放在WEB-INF/classes/下。
1、struts.xml用于应用程序相关的配置
2、struts.properties用于Struts 2.0的运行时（Runtime）的配置 回复 更多评论
# re: 为Struts 2.0做好准备 2006-10-30 10:44 | Max
@张先生
struts-default.xml文件不用您自己加，它位于struts2-core-2.0.0.jar包中。 回复 更多评论
# re: 为Struts 2.0做好准备 2006-11-02 23:14 | Tin
谢谢Max的教程。大家也可以到WebWork中文网站：http://webwork.javascud.org/
http://wiki.javascud.org/display/ww2cndoc/WebWork
看看。里面有WebWork 2.2的中文参考手册。
Struts 2.0.1目前还完全等于WebWork 2.2.x，所以可以参照WebWork2手册学习。 回复 更多评论
# re: 为Struts 2.0做好准备 2006-11-10 00:47 | Goingmm[匿名]
简单易懂！测试通过

需要尝试的朋友，有一个细节需要注意：
我开始图方便是把所有包都引入。结果出现struts2-all-2.0.1.jar包和struts2-core-2.0.1.jar包冲突。原因很简单就是这2个包里面都有struts-default.xml文件。造成重复定义
结论：在struts-2.0.1的lib下面有很多jar包。做这个测试只需要引入图5显示的其中6个包（在版本2.0.1里面没有struts2-extras-2.0.0.jar，所以不用引这个包）
回复 更多评论

# re: 为Struts 2.0做好准备 2006-11-22 21:42 | 太行山人
default-config.xml有什么用？一定要include到struts.xml中吗？ 回复 更多评论
# re: 为Struts 2.0做好准备 2006-11-26 18:32 | Max
Default-config.xml contains some interceptor stacks and common configurations. Including it can help you to create action easier 回复 更多评论
# re: 为Struts 2.0做好准备 2006-11-27 12:36 | 太行山人
如果我不想用default-config.xml中的默认配置，而是自己对default-config.xml中的一些参数自定义，应该把这个文件放在哪里呢？还是直接覆盖jar包中的这个文件呢？
或者，在struts.xml中配置相应的元素，会覆盖default-config.xml中相应元素的配置？ 回复 更多评论
# re: 为Struts 2.0做好准备 2006-11-28 23:52 | Max
在struts.xml中配置相应的元素，可以覆盖default-config.xml中相应元素的配置。 回复 更多评论
# re: 为Struts 2.0做好准备 2006-12-02 13:26 | jakki
@Max
警告: Could not find or error in struts.properties
java.lang.IllegalStateException: struts.properties missing

我老是遇到报这个错，我已经加了这个文件了，怎么还报 回复 更多评论
# re: 为Struts 2.0做好准备 2006-12-04 18:50 | 张先生
把struts.properties 放到classes下即可了！！ 回复 更多评论
# re: 为Struts 2.0做好准备 2006-12-05 10:52 | kevin666888
为什么不能正确显示中文：
如果在SayHello.jsp页面的输入框中输入中文，（如“世界”）

提交后显示的是乱码，如"Hello, ?=?!"

请各位指教。 回复 更多评论
# re: 为Struts 2.0做好准备 2006-12-05 18:34 | Max
HelloWorld.jsp中<%@ page contentType="text/html; charset=UTF-8" %>有没有加上这个？
在我的环境中没有这个问题。 回复 更多评论
# re: 为Struts 2.0做好准备 2006-12-07 21:20 | konhon 优华
非常詳細...
我照做了下, 全通過 回复 更多评论
# re: 为Struts 2.0做好准备 2006-12-14 15:04 | xanbio
@风人园
如果你是直接copy的过去就会有这个问题。。建议把配置文件按照上面的手写一遍就好了。。
我也遇到过这样的问题 回复 更多评论
# re: 为Struts 2.0做好准备 2006-12-14 15:07 | xanbio
不完全这样，
1.你需要在你的classpath下放置struts.properties文件。
2.同时你需要将tomcat下的你的项目的启动文件的conttext元素的reload属性设为true。 回复 更多评论
# 问一个关于中文乱码的问题。 2007-04-03 17:44 | lhx
我有一个注册页面register.jsp 如下，这样在页面上中文可以正确显示，但是到action中取出来的“user.username”就是乱吗了,该怎么解决啊？
<%@ page contentType="text/html;charset=GBK"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Sign On</title>
</head>
<body>
<s:form action="RegisterAction">
<table>
<tr>
<td>姓名</td>
<td><s:textfield key="user.username"/></td>
</tr>
<tr>
<td>地址</td>
<td><s:textfield key="user.address" /></td>
</tr>
<tr><td colspan="2"><s:submit/></td></tr>
</table>
</s:form> 回复 更多评论

# re: 为Struts 2.0做好准备 2007-04-07 22:56 | Max
@lhx
<%@ page contentType="text/html;charset=GBK"%>
将页面的输出编码设置为：GBK，但是又使用了：
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
告诉浏览器使用gb2312来显示页面。
建议大家将编码都设为utf-8，在我开发的过程中很少碰到乱码的情况。 回复 更多评论
# re: 为Struts 2.0做好准备 2007-04-09 14:53 | open
@风人园
请你加入这个试试：
把HelloWorld

更改为：HelloWorld.action看看的，应该是这个的问题！
回复 更多评论
# re: 为Struts 2.0做好准备 2007-04-18 13:36 | jiajia
启动tomcat时，strurs2自动得读default-config.xml配置文件，那么怎么读自己建的struts.xml文件 回复 更多评论
# re: 为Struts 2.0做好准备 2007-04-18 23:05 | Max
@jiajia
Struts 2默认会读取classpath下的struts-default.xml,struts-plugin.xml,struts.xml这三个文件。 回复 更多评论
# re: 为Struts 2.0做好准备 2007-04-19 09:49 | jiajia
谢谢max的回答，再问一下struts-plugin.xml这个文件在哪个jar包里面，还是自己定义的 回复 更多评论
# re: 为Struts 2.0做好准备 2007-04-19 23:19 | Max
@jiajia
struts-plugin.xml会在你下载的plugin的jar包中，如struts2-spring-plugin-2.0.6.jar。 回复 更多评论
# re: 为Struts 2.0做好准备 2007-04-19 23:28 | Max
@Tony
你应该没在web.xml有配置struts 2的filter，试下将以下列代码加到web.xml的<web-app></web-app>元素之间：
<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> 回复 更多评论
# re: 为Struts 2.0做好准备 2007-05-10 17:31 | gpiaofei2006
全部按照步骤做完后，在SayHello.jsp，填上任意名字，执行结果都是Hello，null！十分迷惑 回复 更多评论
# re: 为Struts 2.0做好准备 2007-05-13 13:58 | gpiaofei2006
# re: 为Struts 2.0做好准备 2006-10-26 22:57 | esmiles

纠正一个错误：
HelloWorld.jsp中的<s:property name="name" />
name属性是错的。应该改成value属性，既：
<s:property value="name" />

HelloWorld.jsp中的<s:property name="name" /> 应该不改变
否则结果为：Hello,null; 回复 更多评论


常用的Struts 2.0的标志（Tag）介绍 - Max On Java - BlogJava

常用的Struts 2.0的标志（Tag）介绍

在上一篇文章《为Struts 2.0做好准备》中，我过于详细地介绍了Struts 2.0开发环境和运行环境的配置，所以，本文很少涉及的以上两方面的细节。如果，您看完《为Struts 2.0做好准备》后，还有什么不明白，或者没法运行文中例子，请联系我。我的E-MAIL：Max.M.Yuan@gmail.com。

在介绍常用标志前，我想先从总体上，对Struts 1.x与Struts 2.0的标志库（Tag Library）作比较。

 | Struts 1.x | Struts 2.0
分类 | 将标志库按功能分成HTML、Tiles、Logic和Bean等几部分 | 严格上来说，没有分类，所有标志都在URI为“/struts-tags”命名空间下，不过，我们可以从功能上将其分为两大类：非UI标志和UI标志
表达式语言（expression languages） | 不支持嵌入语言（EL） | OGNL、JSTL、Groovy和Velcity
以上表格，纯属个人总结，如有所不足或错误，请不吝指正

好了，我要开始介绍“常用”（这里所谓的“常用”，是指在已往工作中使用Struts里经常用到的）的标志了。

要在JSP中使用Struts 2.0标志，先要指明标志的引入。通过在JSP的代码的顶部加入以下代码可以做到这点。
<%@taglib prefix="s" uri="/struts-tags" %>
  1. 非UI标志
    • if、elseif和else

      描述：
      执行基本的条件流转。

      参数：

      名称 | 必需 | 默认 | 类型 | 描述 | 备注
      test | 是 | Boolean | 决定标志里内容是否显示的表达式 | else标志没有这个参数
      id | 否 | Object/String | 用来标识元素的id。在UI和表单中为HTML的id属性

      例子：

      <%@ page contentType="text/html; charset=UTF-8" %>
      <%@ taglib prefix="s" uri="/struts-tags" %>
      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <title>Condition Flow</title>
      </head>
      <body>
      <h3>Condition Flow</h3>
      <!--
      这里有点小技巧：
      本来可以用#parameters.name[0]来获得，请求中name的值。但是，在我实现include例子时，
      无论我用param标志给name赋任何值，#parameters里面不会含有任何值，所以#parameters.name也为空值。

      其原因为：
      当使用include标志时，被包含的页面（included）里#parameters拿到的是包含页面里的请求参数。

      因此，这里必须手工调用request.getParameter("name")。
      -->
      <s:set name="name" value="<%= "'" + request.getParameter("name") + "'" %>" />
      <s:if test="#name == 'Max'">
      Max's file here
      </s:if>
      <s:elseif test="#name == 'Scott'">
      Scott's file here
      </s:elseif>
      <s:else>
      Other's file here
      </s:else>
      </body>
      </html>
      例1 condition.jsp

    • iterator

      描述：
      用于遍历集合（java.util.Collection）或枚举值（java.util.Iterator）。

      参数：

      名称 | 必需 | 默认 | 类型 | 描述
      status | 否 | String | 如果设置此参数，一个IteratorStatus的实例将会压入每个遍历的堆栈
      value | 否 | Object/String | 要遍历的可枚举的（iteratable）数据源，或者将放入新列表（List）的对象
      id | 否 | Object/String | 用来标识元素的id。在UI和表单中为HTML的id属性

      例子：

      <%@ page contentType="text/html; charset=UTF-8" %>
      <%@ page import="java.util.List" %>
      <%@ page import="java.util.ArrayList" %>
      <%@ taglib prefix="s" uri="/struts-tags" %>

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <%
      List list       = new ArrayList();
      list.add(      "Max");
      list.add(      "Scott");
      list.add(      "Jeffry");
      list.add(      "Joe");
      list.add(      "Kelvin");
      request.setAttribute(      "names", list);
      %>
      <html>
      <head>
      <title>Iterator</title>
      </head>
      <body>
      <h3>Names: </h3>
      <!--
      1、此处的空property元素用于获得当前iterator的值
      2、status被设成stuts，在iterator的里面就可以通过#stuts取得IteratorStatus的对象。IteratorStatus类包含当前序号信息，如是否第一个或最后一个，是否为奇数序号。这些信息在我们做格式化的时候，显得非常有用。
      -->
      <ol>
      <s:iterator value="#request.names" status="stuts">
      <s:if test="#stuts.odd == true">
      <li>White <s:property /></li>
      </s:if>
      <s:else>
      <li style="background-color:gray"><s:property /></li>
      </s:else>
      </s:iterator>
      </ol>
      </body>
      </html>
      例2 iterator.jsp

    • i18n

      描述：
      加载资源包到值堆栈。它可以允许text标志访问任何资源包的信息，而不只当前action相关联的资源包。

      参数：

      名称 | 必需 | 默认 | 类型 | 描述
      value | 是 | Object/String | 资源包的类路径（如com.xxxx.resources.AppMsg）
      id | 否 | Object/String | 用来标识元素的id。在UI和表单中为HTML的id属性

      例子：

      HelloWorld=Hello Wrold!
      例3 classes\ ApplicationMessages.properties

      <%@ page contentType="text/html; charset=UTF-8" %>
      <%@ taglib prefix="s" uri="/struts-tags" %>

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <title>Internationization</title>
      </head>
      <body>
      <h3>
      <s:i18n name="ApplicationMessages">
      <s:text name="HelloWorld" />
      </s:i18n>
      </h3>
      </body>
      </html>
      例3 i18n.jsp

    • include

      描述：
      包含一个servlet的输出（servlet或jsp的页面）。

      参数：

      名称 | 必需 | 默认 | 类型 | 描述
      value | 是 | String | 要包含的jsp或servlet
      id | 否 | Object/String | 用来标识元素的id。在UI和表单中为HTML的id属性

      例子：

      <%@ page contentType="text/html; charset=UTF-8" %>
      <%@ taglib prefix="s" uri="/struts-tags" %>

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <title>Iterator</title>
      </head>
      <body>
      <h3>Interator Page</h3>
      <s:include value="/condition.jsp">
      <s:param name="name">Max</s:param>
      </s:include>
      <h3>i18n</h3>
      <s:include value="/i18n.jsp" />
      </body>
      </html>
      例4 include.jsp

    • param

      描述：
      为其他标签提供参数,比如include标签和bean标签. 参数的name属性是可选的,如果提供，会调用Component的方法addParameter(String, Object), 如果不提供，则外层嵌套标签必须实现UnnamedParametric接口(如TextTag)。

      value的提供有两种方式,通过value属性或者标签中间的text,不同之处我们看一下例子:

      <param name="color">blue</param><!-- (A) -->

      <param name="color" value="blue"/><!-- (B) -->
      (A)参数值会以String的格式放入statck.
      (B)该值会以java.lang.Object的格式放入statck.

      参数：

      名称 | 必需 | 默认 | 类型 | 描述
      name | 否 | String | 参数名
      value | 否 | String | value表达式
      id | 否 | Object/String | 用来标识元素的id。在UI和表单中为HTML的id属性

      例子：
      请参考例4。

    • set

      描述：
      set标签赋予变量一个特定范围内的值。当希望给一个变量赋一个复杂的表达式,每次访问该变量而不是复杂的表达式时用到。其在两种情况下非常有用: 复杂的表达式很耗时 (性能提升) 或者很难理解 (代码可读性提高)。

      参数：

      名称 | 必需 | 默认 | 类型 | 描述
      name | 是 | String | 变量名字
      scope | 否 | String | 变量作用域,可以为application, session, request, page, 或action.
      value | 否 | Object/String | 将会赋给变量的值
      id | 否 | Object/String | 用来标识元素的id。在UI和表单中为HTML的id属性

      例子：
      请参考例1。

    • text

      描述：
      支持国际化信息的标签。国际化信息必须放在一个和当前action同名的resource bundle中,如果没有找到相应message,tag body将被当作默认message,如果没有tag body,message的name会被作为默认message。

      参数：

      名称 | 必需 | 默认 | 类型 | 描述
      name | 是 | String | 资源属性的名字
      id | 否 | Object/String | 用来标识元素的id。在UI和表单中为HTML的id属性

      例子：
      请参考例3。

    • url

      描述：
      该标签用于创建url,可以通过"param"标签提供request参数。

      当includeParams的值时'all'或者'get', param标签中定义的参数将有优先权,也就是说其会覆盖其他同名参数的值。

      参数： 略

      例子：

      <%@ page contentType="text/html; charset=UTF-8" %>
      <%@ taglib prefix="s" uri="/struts-tags" %>
      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <title>URL</title>
      </head>
      <body>
      <h3>URL</h3>
      <a href='<s:url value="/i18n.jsp" />'>i18n</a><br />
      <s:url id="url" value="/condition.jsp">
      <s:param name="name">Max</s:param>
      </s:url>
      <s:a href="%{url}">if\elseif\else</s:a>
      </body>
      </html>
      例5 url.jsp

    • property

      描述：
      得到'value'的属性,如果value没提供,默认为堆栈顶端的元素。

      参数：

      名称 | 必需 | 默认 | 类型 | 描述
      default | 否 | String | 如果属性是null则显示的default值
      escape | 否 | true | Booelean | 是否escape HTML
      value | 否 | 栈顶 | Object | 要显示的值
      id | 否 | Object/String | 用来标识元素的id。在UI和表单中为HTML的id属性

      例子：
      请参考例2。

  2. UI标志

    UI标志又可以分为表单UI和非表单UI两部分。表单UI部分基本与Struts 1.x相同，都是对HTML表单元素的包装。不过，Struts 2.0加了几个我们经常在项目中用到的控件如：datepicker、doubleselect、timepicker、optiontransferselect等。因为这些标志很多都经常用到，而且参数也很多，要在一篇文章详细说明并非易事。

    需要深入了解这些标志的朋友，可以到以下查看以下网址：
    http://wiki.javascud.org/display/ww2cndoc/Tags WebWork2文档中文化计划（中文）
    http://cwiki.apache.org/WW/tag-reference.html Tag Developers Guide（英文）
    本文有相当的内容也来自这两处。

    在此，我虽然不能够详细讲述这些标志，但是可以与大家分享一个来Struts 2.0 Show Case一个例子。

    /**//*
    * $Id: UITagExample.java 420385 2006-07-10 00:57:05Z mrdon $
    *
    * Copyright 2006 The Apache Software Foundation.
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    *     http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
    package org.apache.struts2.showcase;

    import org.apache.struts2.ServletActionContext;
    import com.opensymphony.xwork2.ActionSupport;
    import com.opensymphony.xwork2.Validateable;
    import com.opensymphony.xwork2.util.OgnlValueStack;

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.io.File;

    /** *//**
    */
    public class UITagExample extends ActionSupport implements Validateable [image] {

    private static final long serialVersionUID = -94044809860988047L;
    String name;
    Date birthday;
    String bio;
    String favoriteColor;
    List friends;
    boolean legalAge;
    String state;
    String region;
    File picture;
    String pictureContentType;
    String pictureFileName;
    String favouriteLanguage;
    String favouriteVehicalType     = "MotorcycleKey";
    String favouriteVehicalSpecific     = "YamahaKey";

    List leftSideCartoonCharacters;
    List rightSideCartoonCharacters;

    List favouriteLanguages     = new ArrayList();
    List vehicalTypeList     = new ArrayList();
    Map vehicalSpecificMap     = new HashMap();

    String thoughts;

    public UITagExample() [image] {
    favouriteLanguages.add(    new Language("EnglishKey", "English Language"));
    favouriteLanguages.add(    new Language("FrenchKey", "French Language"));
    favouriteLanguages.add(    new Language("SpanishKey", "Spanish Language"));

    VehicalType car     = new VehicalType("CarKey", "Car");
    VehicalType motorcycle     = new VehicalType("MotorcycleKey", "Motorcycle");
    vehicalTypeList.add(car);
    vehicalTypeList.add(motorcycle);

    List cars     = new ArrayList();
    cars.add(    new VehicalSpecific("MercedesKey", "Mercedes"));
    cars.add(    new VehicalSpecific("HondaKey", "Honda"));
    cars.add(    new VehicalSpecific("FordKey", "Ford"));

    List motorcycles     = new ArrayList();
    motorcycles.add(    new VehicalSpecific("SuzukiKey", "Suzuki"));
    motorcycles.add(    new VehicalSpecific("YamahaKey", "Yamaha"));

    vehicalSpecificMap.put(car, cars);
    vehicalSpecificMap.put(motorcycle, motorcycles);
    }

    public List getLeftSideCartoonCharacters() [image] {
    return leftSideCartoonCharacters;
    }
    public void setLeftSideCartoonCharacters(List leftSideCartoonCharacters) [image] {
    this.leftSideCartoonCharacters = leftSideCartoonCharacters;
    }

    public List getRightSideCartoonCharacters() [image] {
    return rightSideCartoonCharacters;
    }
    public void setRightSideCartoonCharacters(List rightSideCartoonCharacters) [image] {
    this.rightSideCartoonCharacters = rightSideCartoonCharacters;
    }

    public String getFavouriteVehicalType() [image] {
    return favouriteVehicalType;
    }

    public void setFavouriteVehicalType(String favouriteVehicalType) [image] {
    this.favouriteVehicalType = favouriteVehicalType;
    }

    public String getFavouriteVehicalSpecific() [image] {
    return favouriteVehicalSpecific;
    }

    public void setFavouriteVehicalSpecific(String favouriteVehicalSpecific) [image] {
    this.favouriteVehicalSpecific = favouriteVehicalSpecific;
    }

    public List getVehicalTypeList() [image] {
    return vehicalTypeList;
    }

    public List getVehicalSpecificList() [image] {
    OgnlValueStack stack     = ServletActionContext.getValueStack(ServletActionContext.getRequest());
    Object vehicalType     = stack.findValue("top");
    if (vehicalType != null && vehicalType instanceof VehicalType) [image] {
    List l     = (List) vehicalSpecificMap.get(vehicalType);
    return l;
    }
    return Collections.EMPTY_LIST;
    }

    public List getFavouriteLanguages() [image] {
    return favouriteLanguages;
    }

    public String execute() throws Exception [image] {
    return SUCCESS;
    }

    /**//* [image] Getters and Setters [image] */

    public String doSubmit() [image] {
    return SUCCESS;
    }

    // === inner class
    public static class Language [image] {
    String description;
    String key;

    public Language(String key, String description) [image] {
    this.key = key;
    this.description = description;
    }

    public String getKey() [image] {
    return key;
    }
    public String getDescription() [image] {
    return description;
    }

    }

    public static class VehicalType [image] {
    String key;
    String description;
    public VehicalType(String key, String description) [image] {
    this.key = key;
    this.description = description;
    }

    public String getKey() [image] { return this.key; }
    public String getDescription() [image] { return this.description; }

    public boolean equals(Object obj) [image] {
    if (! (obj instanceof VehicalType)) [image] {
    return false;
    }
    else [image] {
    return key.equals(((VehicalType)obj).getKey());
    }
    }

    public int hashCode() [image] {
    return key.hashCode();
    }
    }

    public static class VehicalSpecific [image] {
    String key;
    String description;
    public VehicalSpecific(String key, String description) [image] {
    this.key = key;
    this.description = description;
    }

    public String getKey() [image] { return this.key; }
    public String getDescription() [image] { return this.description; }

    public boolean equals(Object obj) [image] {
    if (! (obj instanceof VehicalSpecific)) [image] {
    return false;
    }
    else [image] {
    return key.equals(((VehicalSpecific)obj).getKey());
    }
    }

    public int hashCode() [image] {
    return key.hashCode();
    }
    }
    }
    例6 org.apache.struts2.showcase.UITagExample.java

    <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
    <%@ taglib prefix="s" uri="/struts-tags" %>
    <html>
    <head>
    <title>UI Tags Example</title>
    <s:head/>
    </head>
    <body>

    <s:actionerror/>
    <s:actionmessage/>
    <s:fielderror />

    <s:form action="exampleSubmit" method="post" enctype="multipart/form-data" tooltipConfig="#{'jsTooltipEnabled':'true'}">
    <s:textfield
    label="Name"
    name    ="name"
    tooltip    ="Enter your Name here" />

    <s:datepicker
    tooltip="Select Your Birthday"
    label    ="Birthday"
    name    ="birthday" />

    <s:textarea
    tooltip="Enter your Biography"
    label    ="Biograph"
    name    ="bio"
    cols    ="20"
    rows    ="3"/>

    <s:select
    tooltip="Choose Your Favourite Color"
    label    ="Favorite Color"
    list    ="{'Red', 'Blue', 'Green'}"
    name    ="favoriteColor"
    emptyOption    ="true"
    headerKey    ="None"
    headerValue    ="None"/>

    <s:select
    tooltip="Choose Your Favourite Language"
    label    ="Favourite Language"
    list    ="favouriteLanguages"
    name    ="favouriteLanguage"
    listKey    ="key"
    listValue    ="description"
    emptyOption    ="true"
    headerKey    ="None"
    headerValue    ="None"/>

    <s:checkboxlist
    tooltip="Choose your Friends"
    label    ="Friends"
    list    ="{'Patrick', 'Jason', 'Jay', 'Toby', 'Rene'}"
    name    ="friends"/>

    <s:checkbox
    tooltip="Confirmed that your are Over 18"
    label    ="Age 18+"
    name    ="legalAge"/>

    <s:doubleselect
    tooltip="Choose Your State"
    label    ="State"
    name    ="region" list="{'North', 'South'}"
    value    ="'South'"
    doubleValue    ="'Florida'"
    doubleList    ="top == 'North' ? {'Oregon', 'Washington'} : {'Texas', 'Florida'}"
    doubleName    ="state"
    headerKey    ="-1"
    headerValue    ="---------- Please Select ----------"
    emptyOption    ="true" />

    <s:doubleselect
    tooltip="Choose your Vehical"
    label    ="Favourite Vehical"
    name    ="favouriteVehicalType"
    list    ="vehicalTypeList"
    listKey    ="key"
    listValue    ="description"
    value    ="'MotorcycleKey'"
    doubleValue    ="'YamahaKey'"
    doubleList    ="vehicalSpecificList"
    doubleListKey    ="key"
    doubleListValue    ="description"
    doubleName    ="favouriteVehicalSpecific" headerKey="-1"
    headerValue    ="---------- Please Select ----------"
    emptyOption    ="true" />

    <s:file
    tooltip="Upload Your Picture"
    label    ="Picture"
    name    ="picture" />

    <s:optiontransferselect
    tooltip="Select Your Favourite Cartoon Characters"
    label    ="Favourite Cartoons Characters"
    name    ="leftSideCartoonCharacters"
    leftTitle    ="Left Title"
    rightTitle    ="Right Title"
    list    ="{'Popeye', 'He-Man', 'Spiderman'}"
    multiple    ="true"
    headerKey    ="headerKey"
    headerValue    ="--- Please Select ---"
    emptyOption    ="true"
    doubleList    ="{'Superman', 'Mickey Mouse', 'Donald Duck'}"
    doubleName    ="rightSideCartoonCharacters"
    doubleHeaderKey    ="doubleHeaderKey"
    doubleHeaderValue    ="--- Please Select ---"
    doubleEmptyOption    ="true"
    doubleMultiple    ="true" />

    <s:textarea
    label="Your Thougths"
    name    ="thoughts"
    tooltip    ="Enter your thoughts here" />

    <s:submit onclick="alert('aaaa');" />
    <s:reset onclick="alert('bbbb');" />
    </s:form>

    </body>
    </html>
    例6 example.jsp

    <action name="example" class="org.apache.struts2.showcase.UITagExample">
    <result>example.jsp</result>
    <result name="input">example.jsp</result>
    </action>
    例6 struts.xml代码片段

posted on 2006-10-18 12:02 Max 阅读(7119) 评论(56) 编辑 收藏 引用 所属分类: Struts 2.0系列

评论:
# re: 常用的Struts 2.0的标志（Tag）介绍 2006-12-01 11:21 | lxf
如果要让两个struts表单元素显示在同一行上面，该如果做呢？ 回复 更多评论
# re: 常用的Struts 2.0的标志（Tag）介绍 2006-12-01 16:36 | Max
你可以在<s:form>加上theme="simple"属性，然后自已布局form的输出。如:
<s:form action="Login" method="POST" theme="simple">
<table>
<tr>
<td>
User name: <s:textfield name="name" label="User name"/>
</td>
<td>
Password: <s:password name="password" label="Password"/>
</td>
</tr>
</table>
</s:form> 回复 更多评论


Struts 2.0的Action讲解 - Max On Java - BlogJava

Struts 2.0的Action讲解

有Struts 1.x经验的朋友都知道Action是Struts的核心内容，当然Struts 2.0也不例外。不过，Struts 1.x与Struts 2.0的Action模型很大的区别。

 | Struts 1.x | Stuts 2.0
接口 | 必须继承org.apache.struts.action.Action或者其子类 | 无须继承任何类型或实现任何接口
表单数据 | 表单数据封装在FormBean中 | 表单数据包含在Action中，通过Getter和Setter获取

虽然，理论上Struts 2.0的Action无须实现任何接口或继承任何类型，但是，我们为了方便实现Action，大多数情况下都会继承com.opensymphony.xwork2.ActionSupport类，并重载（Override）此类里的String execute()方法。具体的实现，如例1所示：

<% @ page contentType = " text/html; charset=UTF-8 " %>
<% @ taglib prefix = " s " uri = " /struts-tags " %>
< html >
< head >
< title > Hello World! </ title >
</ head >
< body >
< h2 >< s:property value ="message" /></ h2 >
</ body >
</ html >
例1 HelloWorld.jsp

package tutorial;

 import java.text.DateFormat;
 import java.util.Date;

 import com.opensymphony.xwork2.ActionSupport;

 public class HelloWorld extends ActionSupport [image] {
 private String message;

 public String getMessage() [image] {
 return message;
}

@Override
public String execute() [image] {
message = " Hello World, Now is " + DateFormat.getInstance().format( new Date());
 return SUCCESS;
}
}
例1 classes/tutorial/HelloWorld.java

< package name ="ActionDemo" extends ="struts-default" >
< action name ="HelloWorld" class ="tutorial.HelloWorld" >
< result > /HelloWorld.jsp </ result >
</ action >
</ package >
例1 classes/struts.xml中HelloWorld Action的配置

在浏览器地址栏中键入http://localhost:8080/Struts2_Action/HelloWorld.action，可以看到如图1所示页面。

图1 HelloWorld输出页面
图1 HelloWorld输出页面

参考JavaDoc，可知ActionSupport类实现了接口：
  • com.opensymphony.xwork2.Action
  • com.opensymphony.xwork2.LoaleProvider
  • com.opensymphony.xwork2.TextProvider
  • com.opensymphony.xwork2.Validateable
  • com.opensymphony.xwork2.ValidationAware
  • com.uwyn.rife.continuations.ContinuableObject
  • java.io.Searializable
  • java.lang.Cloneable

默认情况下，当请求HelloWorld.action发生时，Struts运行时（Runtime）根据struts.xml里的Action映射集(Mapping)，实例化tutoiral.HelloWorld类，并调用其execute方法。当然，我们可以通过以下两种方法改变这种默认调用。这个功能（Feature）有点类似Struts 1.x中的LookupDispathAction。

  1. 在classes/sturts.xml中新建Action，并指明其调用的方法；
  2. 访问Action时，在Action名后加上“!xxx”（xxx为方法名）。

实现方法请参考例2：

在classes/tutorial/HelloWorld.java中加入以下方法：
public String aliasAction() {
message ="自定义Action调用方法";
return SUCCESS;
}
例2 classes/tutorial/HelloWorld.java代码片段

实现方法一，在classes/sturts.xml中加入下面代码：
<action name="AliasHelloWorld" class="tutorial.HelloWorld" method="aliasAction">
<result>/HelloWorld.jsp</result>
</action>
例2 classes/struts.xml中AlaisHelloWorld Action的配置

实现方法二，使用http://localhost:8080/Struts2_Action/HelloWorld!aliasAction.action地址来访问HelloWorld Action。

在浏览器地址栏中键入http://localhost:8080/Struts2_Action/AliasHelloWorld.action或 http://localhost:8080/Struts2_Action/HelloWorld!aliasAction.action，可以看到如图2所示页面。

图2 自定义Action调用方法页面
图2 自定义Action调用方法页面

通过上面的两个例子，细心的朋友应该可能会发现classes/tutorial/HelloWorld.java中Action方法（execute和aliasAction）返回都是SUCCESS。这个属性变量我并没有定义，所以大家应该会猜到它在ActionSupport或其父类中定义。没错，SUCCESS在接口com.opensymphony.xwork2.Action中定义，另外同时定义的还有ERROR , INPUT , LOGIN , NONE。

此外，我在配置Action时都没有为result定义名字（name），所以它们默认都为success。值得一提的是Struts 2.0中的result不仅仅是Struts 1.x中forward的别名，它可以实现除forward外的很激动人心的功能，如将Action输出到FreeMaker模板、Velocity模板、JasperReports和使用XSL转换等。这些都过result里的type（类型）属性（Attribute）定义的。另外，您还可以自定义result类型。

下面让我们来做一个Velocity模板输出的例子，首先在classes/struts.xml中新建一个Action映射（Mapping），将其result类型设为velocity，如以下代码所示：
<action
name="VMHelloWorld"
class="tutorial.HelloWorld">
<result type="velocity">/HelloWorld.vm</result>
</action>
例3 classes/struts.xml中VMHelloWorld Action的配置

新建HelloWorld.vm，内容如下所示：
<html>
<head>
<title>Velocity</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<h2>Message rendered in Velocity: $message</h2>
</body>
</html>
例3 HelloWorld.vm

在浏览器地址栏中键入http://localhost:8080/Struts2_Action/VMHelloWorld.action，页面输出如下图3所示。

图3 HelloWorld.vm的输出页面
要运行例3需要在WEB-INF/lib中添加以下几个包：
  • commons-collections-3.2.jar
  • velocity-1.4.jar
  • velocity-tools-view-1.2.jar
  • avalon-logkit-2.1.jar

前面，我花了不少的时间讨论Action的输出。我老板有句名言——程序无非就是输入、操作和输出。因此，现在我们要讨论一下输入——表单输入。

使用Struts 2.0，表单数据的输入将变得非常方便，和普通的POJO一样在Action编写Getter和Setter，然后在JSP的UI标志的name与其对应，在提交表单到Action时，我们就可以取得其值。

让我们看一个例子，新建Login Action，它通过Login.jsp的表单获得用户名和密码，验查用户名是否为“max”，密码是否则为“secret”。如果，两者都符合，就在HelloWorld中显示“Welcome, max”，否则显示“Invalid user or Password”。

package tutorial;

import com.opensymphony.xwork2.ActionSupport;

publicclass Login extends ActionSupport [image] {
private String name;
private String password;
private String message;

public String getName() [image] {
return name;
}

publicvoid setName(String name) [image] {
this.name = name;
}

public String getPassword() [image] {
return password;
}

publicvoid setPassword(String password) [image] {
this.password = password;
}

public String getMessage() [image] {
return message;
}

@Override
public String execute() [image] {
if("max".equals(name) &&"Secret".equals(password)) [image] {
message ="Welcome, "+ name;
}else [image] {
message ="Invalid user or password";
}
return SUCCESS;
}
}
例4 classes/tutorial/Login.java

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>Login</title>
</head>
<body>
<s:form action="Login" method="POST">
<s:textfield name="name" label="User name"/>
<s:password name="password" label="Password"/>
<s:submit value="Submit"/>
</s:form>
</body>
</html>
例4 Login.jsp

<action name="Login" class="tutorial.Login">
<result>/HelloWorld.jsp</result>
</action>
例4 classes/struts.xml中Login Action的配置

运行Tomcat，在浏览器地址栏中键入http://localhost:8080/Struts2_Action/Login.jsp，出现如图4所示页面。

图4 Login.jsp输出页面

分别在User name中输入“max”和“secret”，点击“Submit”按钮，出现如图5所示页面。

图5 Login成功页面
图5 Login成功页面

在浏览器地址栏中键入http://localhost:8080/Struts2_Action/Login.jsp，分别在User name中输入“Scott”和“password”，点击“Submit”按钮，出现如图6所示页面。

图6 Login失败页面
图6 Login失败页面

Struts 2.0更厉害的是支持更高级的POJO访问，如user.getPassword()。我们可以用另一写法实现例4。首先，将name和password从Login类中分离出来，到新建类User中。这样对我们开发多层系统尤其有用。它可以使系统结构更清晰。

package tutorial;

import com.opensymphony.xwork2.ActionSupport;

publicclass LoginX extends ActionSupport [image] {
private User user;
private String message;

publicvoid setUser(User user) [image] {
this.user = user;
}

public User getUser() [image] {
return user;
}

public String getMessage() [image] {
return message;
}

@Override
public String execute() [image] {
if("max".equals(user.getName()) &&"secret".equals(user.getPassword())) [image] {
message ="Welcome, "+ user.getName();
}else [image] {
message ="Invalid user or password";
}
return SUCCESS;
}
}
例5 classes/tutorial/LoginX.java

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>Login</title>
</head>
<body>
<s:form action="LoginX" method="POST">
<s:textfield name="user.name" label="User name"/>
<s:password name="user.password" label="Password"/>
<s:submit value="Submit"/>
</s:form>
</body>
</html>
例5 LoginX.jsp

<action name="LoginX" class="tutorial.LoginX">
<result>/HelloWorld.jsp</result>
</action>
例5 classes/struts.xml中的LoginX Action配置

很多时候我的同事会问我：“如果我要取得Servlet API中的一些对象，如request、response或session等，应该怎么做？这里的execute不像Struts 1.x的那样在参数中引入。”开发Web应用程序当然免不了跟这些对象打交道。在Strutx 2.0你可以有两种方式获得这些对象：非IoC（控制反转Inversion of Control）方式和IoC方式。

  1. 非IoC方式

    要获得上述对象，关键Struts 2.0中com.opensymphony.xwork2.ActionContext类。我们可以通过它的静态方法getContext()获取当前Action的上下文对象。 另外，org.apache.struts2.ServletActionContext作为辅助类（Helper Class），可以帮助您快捷地获得这几个对象。

    • HttpServletRequest request = ServletActionContext.getRequest();
    • HttpServletResponse response = ServletActionContext.getResponse();
    • HttpSession session = request.getSession();

    如果你只是想访问session的属性（Attribute），你也可以通过ActionContext.getContext().getSession()获取或添加session范围（Scoped）的对象。

  2. IoC方式

    要使用IoC方式，我们首先要告诉IoC容器（Container）想取得某个对象的意愿，通过实现相应的接口做到这点。具体实现，请参考例6 IocServlet.java。
package tutorial;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

publicclass NonIoCServlet extends ActionSupport [image] {
private String message;

public String getMessage() [image] {
return message;
}

@Override
public String execute() [image] {
ActionContext.getContext().getSession().put("msg", "Hello World from Session!");

HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
HttpSession session = request.getSession();

StringBuffer sb =new StringBuffer("Message from request: ");
sb.append(request.getParameter("msg"));
sb.append("<br>Response Buffer Size: ");
sb.append(response.getBufferSize());
sb.append("<br>Session ID: ");
sb.append(session.getId());

message = sb.toString();
return SUCCESS;
}
}
例6 classes/tutorial/NonIoCServlet.java

package tutorial;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.ServletResponseAware;
import org.apache.struts2.interceptor.SessionAware;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

publicclass IoCServlet extends ActionSupport implements SessionAware, ServletRequestAware, ServletResponseAware [image] {
private String message;
private Map att;
private HttpServletRequest request;
private HttpServletResponse response;

public String getMessage() [image] {
return message;
}

publicvoid setSession(Map att) [image] {
this.att = att;
}

publicvoid setServletRequest(HttpServletRequest request) [image] {
this.request = request;
}

publicvoid setServletResponse(HttpServletResponse response) [image] {
this.response = response;
}

@Override
public String execute() [image] {
att.put("msg", "Hello World from Session!");

HttpSession session = request.getSession();

StringBuffer sb =new StringBuffer("Message from request: ");
sb.append(request.getParameter("msg"));
sb.append("<br>Response Buffer Size: ");
sb.append(response.getBufferSize());
sb.append("<br>Session ID: ");
sb.append(session.getId());

message = sb.toString();
return SUCCESS;
}
}
例6 classes/tutorial/IoCServlet.java

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h2>
<s:property value="message" escape="false"/>
<br>Message from session: <s:property value="#session.msg"/>
</h2>
</body>
</html>
例6 Servlet.jsp

<action name="NonIoCServlet" class="tutorial.NonIoCServlet">
<result>/Servlet.jsp</result>
</action>
<action name="IoCServlet" class="tutorial.IoCServlet">
<result>/Servlet.jsp</result>
</action>
例6 classes/struts.xml中NonIocServlet和IoCServlet Action的配置

运行Tomcat，在浏览器地址栏中键入http://localhost:8080/Struts2_Action/NonIoCServlet.action?msg=Hello%20World! 或http://localhost:8080/Struts2_Action/IoCServlet.action?msg=Hello%20World!，出现如图7所示页面。

图7 Servlet.jsp的输出页面

在Servlet.jsp中，我用了两次property标志，第一次将escape设为false为了在JSP中输出<br>转行，第二次的value中的OGNL为“#session.msg”，它的作用与session.getAttribute("msg")等同。
关于property或其它标志，可以参考我的上一篇文章《常用的Struts 2.0的标志（Tag）介绍 》。
posted on 2006-10-25 12:10 Max 阅读(7659) 评论(47) 编辑 收藏 引用 所属分类: Struts 2.0系列


在Struts 2.0中国际化(i18n)您的应用程序 - Max On Java - BlogJava

在Struts 2.0中国际化(i18n)您的应用程序

国际化是商业系统中不可或缺的一部分，所以无论您学习的是什么Web框架，它都是必须掌握的技能。

其实，Struts 1.x在此部分已经做得相当不错了。它极大地简化了我们程序员在做国际化时所需的工作，例如，如果您要输出一条国际化的信息，只需在代码包中加入FILE-NAME_xx_XX.properties（其中FILE-NAME为默认资源文件的文件名），然后在struts-config.xml中指明其路径，再在页面用<bean:message>标志输出即可。

不过，所谓“没有最好，只有更好”。Struts 2.0并没有在这部分止步，而是在原有的简单易用的基础上，将其做得更灵活、更强大。

国际化Hello World

下面让我们看一个例子——HelloWorld。这个例子演示如何根据用户浏览器的设置输出相应的HelloWorld。

  1. 在Eclipse创建工程配置开发和运行环境（如果对这个步骤有问题，可以参考我早前的文章《为Struts 2.0做好准备》）。
  2. 在src文件夹中加入struts.properties文件，内容如下：
    struts.custom.i18n.resources=globalMessages
    Struts 2.0有两个配置文件，struts.xml和struts.properties都是放在WEB-INF/classes/下。
    • struts.xml用于应用程序相关的配置
    • struts.properties用于Struts 2.0的运行时（Runtime）的配置
  3. 在src文件夹中加入globalMessages_en_US.properties文件，内容如下：
    HelloWorld=Hello World!
  4. 在src文件夹中加入globalMessages_zh_CN.properties文件，内容如下：
    HelloWorld=你好，世界！
    在此想和大家分享一个不错的编写properties文件的Eclipse插件（plugin），有了它我们在编辑一些简体中文、繁体中文等Unicode文本时，就不必再使用native2ascii编码了。您可以通过Eclipse中的软件升级（Software Update）安装此插件，步骤如下：
    1、展开Eclipse的Help菜单，将鼠标移到Software Update子项，在出现的子菜单中点击Find and Install；
    2、在Install/Update对话框中选择Search for new features to install，点击Next；
    3、在Install对话框中点击New Remote Site；
    4、在New Update Site对话框的Name填入“PropEdit”或其它任意非空字符串，在URL中填入http://propedit.sourceforge.jp/eclipse/updates/；
    5、在Site to include to search列表中，除上一步加入的site外的其它选项去掉，点击Finsih；
    6、在弹出的Updates对话框中的Select the features to install列表中将所有结尾为“3.1.x”的选项去掉（适用于Eclipse 3.2版本的朋友）；
    7、点击Finish关闭对话框；
    8、在下载后，同意安装，再按提示重启Eclipse，在工具条看到形似vi的按钮表示安装成功，插件可用。此时，Eclpise中所有properties文件的文件名前有绿色的P的图标作为标识。
  5. 在WebContent文件夹下加入HelloWorl.jsp文件，内容如下：
    <%@ page contentType="text/html; charset=UTF-8"%>
    <%@taglib prefix="s" uri="/struts-tags"%>
    <html>
    <head>
    <title>Hello World</title>
    </head>
    <body>
    <h2><s:text name="HelloWorld"/></h2>
    <h2><s:property value="%{getText('HelloWorld')}"/></h2>
    </body>
    </html>
  6. 发布运行应用程序，在浏览器地址栏中输入http://localhost:8080/Struts2_i18n/HelloWorld.jsp ，出现图1所示页面。
    图1 中文输出
    图1 中文输出
  7. 将浏览器的默认语言改为“英语（美国）”，刷新页面，出现图2所示页面。
    图2 英文输出
    图2 英文输出
上面的例子的做法，与Struts 1.x的做法相似，似乎并不能体现Struts 2.0的优势。不过，我在上面的例子用了两种方法来显示国际化字符串，其输出是相同的。其实，这就是Struts 2.0的一个优势，因为它默认支持EL，所示我们可以用getText方法来简洁地取得国际化字符串。另外更普遍的情况——在使用UI表单标志时，getText可以用来设置label属性，例如：
<s:textfield name="name" label="%{getText('UserName')}"/>

资源文件查找顺序

之所以说Struts 2.0的国际化更灵活是因为它可以能根据不同需要配置和获取资源（properties）文件。在Struts 2.0中有下面几种方法：

  1. 使用全局的资源文件，方法如上例所示。这适用于遍布于整个应用程序的国际化字符串，它们在不同的包（package）中被引用，如一些比较共用的出错提示；
  2. 使用包范围内的资源文件。做法是在包的根目录下新建名的package.properties和package_xx_XX.properties文件。这就适用于在包中不同类访问的资源；
  3. 使用Action范围的资源文件。做法为Action的包下新建文件名（除文件扩展名外）与Action类名同样的资源文件。它只能在该Action中访问。如此一来，我们就可以在不同的Action里使用相同的properties名表示不同的值。例如，在ActonOne中title为“动作一”，而同样用title在ActionTwo表示“动作二”，节省一些命名工夫；
  4. 使用<s:i18n>标志访问特定路径的properties文件。使用方法请参考我早前的文章《常用的Struts 2.0的标志（Tag）介绍》。在您使用这一方法时，请注意<s:i18n>标志的范围。在<s:i18n name="xxxxx">到</s:i18n>之间，所有的国际化字符串都会在名为xxxxx资源文件查找，如果找不到，Struts 2.0就会输出默认值（国际化字符串的名字）。

上面我列举了四种配置和访问资源的方法，它们的范围分别是从大到小，而Struts 2.0在查找国际化字符串所遵循的是特定的顺序，如图3所示：

图3 资源文件查找顺序图
图3 资源文件查找顺序图

假设我们在某个ChildAction中调用了getText("user.title")，Struts 2.0的将会执行以下的操作：

  1. 查找ChildAction_xx_XX.properties文件或ChildAction.properties；
  2. 查找ChildAction实现的接口，查找与接口同名的资源文件MyInterface.properties；
  3. 查找ChildAction的父类ParentAction的properties文件，文件名为ParentAction.properties；
  4. 判断当前ChildAction是否实现接口ModelDriven。如果是，调用getModel()获得对象，查找与其同名的资源文件；
  5. 查找当前包下的package.properties文件；
  6. 查找当前包的父包，直到最顶层包；
  7. 在值栈（Value Stack）中，查找名为user的属性，转到user类型同名的资源文件，查找键为title的资源;
  8. 查找在struts.properties配置的默认的资源文件，参考例1;
  9. 输出user.title。

参数化国际化字符串

许多情况下，我们都需要在动行时（runtime）为国际化字符插入一些参数，例如在输入验证提示信息的时候。在Struts 2.0中，我们通过以下两种方法做到这点：

  1. 在资源文件的国际化字符串中使用OGNL，格式为${表达式}，例如：
    validation.require=${getText(fileName)} is required
  2. 使用java.text.MessageFormat中的字符串格式，格式为{ 参数序号（从0开始）, 格式类形（number | date | time | choice）, 格式样式}，例如：
    validation.between=Date must between {0, date, short} and {1, date, short}
在显示这些国际化字符时，同样有两种方法设置参数的值：
  1. 使用标志的value0、value1...valueN的属性，如：
    <s:text name="validation.required" value0="User Name"/>
  2. 使用param子元素，这些param将按先后顺序，代入到国际化字符串的参数中，例如：
    <s:text name="validation.required">
    <s:param value="User Name"/>
    </s:text>

让用户方便地选择语言

开发国际化的应用程序时，有一个功能是必不可少的——让用户快捷地选择或切换语言。在Struts 2.0中，通过ActionContext.getContext().setLocale(Locale arg)可以设置用户的默认语言。不过，由于这是一个比较普遍的应用场景（Scenario），所以Struts 2.0为您提供了一个名i18n的拦截器（Interceptor），并在默认情况下将其注册到拦截器链（Interceptor chain）中。它的原理为在执行Action方法前，i18n拦截器查找请求中的一个名为"request_locale"的参数。如果其存在，拦截器就将其作为参数实例化Locale对象，并将其设为用户默认的区域（Locale），最后，将此Locale对象保存在session的名为“WW_TRANS_I18N_LOCALE”的属性中。

下面，我将提供一完整示例演示它的使用方法。

package tutorial;

import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;

publicclass Locales [image] {
public Map<String, Locale> getLocales() [image] {
Map<String, Locale> locales =new Hashtable<String, Locale>(2);
locales.put("American English", Locale.US);
locales.put("Simplified Chinese", Locale.CHINA);
return locales;
}
}
tutorial/Locales.java

<%@taglib prefix="s" uri="/struts-tags"%>
<script type="text/javascript">
<!--
function langSelecter_onChanged()
{
document.langForm.submit();
}
//-->
</script>
<s:set
name="SESSION_LOCALE"
value="#session['WW_TRANS_I18N_LOCALE']"/>
<s:bean id="locales"
name="tutorial.Locales"/>
<form action="<s:url
includeParams="get" encode="true"/>"
name="langForm"
style="background-color: powderblue;
padding-top: 4px; padding-bottom: 4px;">
Language:
<s:select label="Language"
list="#locales.locales"
listKey="value"    listValue="key"
value="#SESSION_LOCALE
== null ? locale : #SESSION_LOCALE"
name="request_locale"
id="langSelecter"
onchange="langSelecter_onChanged()" theme="simple"/>
</form>
LangSelector.jsp

上述代码的原理为，LangSelector.jsp先实例化一个Locales对象，并把对象的Map类型的属性locales赋予下拉列表（select） 。如此一来，下拉列表就获得可用语言的列表。大家看到LangSelector有<s:form>标志和一段Javascript脚本，它们的作用就是在用户在下拉列表中选择了后，提交包含“reqeust_locale”变量的表单到Action。在打开页面时，为了下拉列表的选中的当前区域，我们需要到session取得当前区域（键为“WW_TRANS_I18N_LOCALE”的属性），而该属性在没有设置语言前是为空的，所以通过值栈中locale属性来取得当前区域（用户浏览器所设置的语言）。

你可以把LangSelector.jsp作为一个控件使用，方法是在JSP页面中把它包含进来，代码如下所示：
<s:include value="/LangSelector.jsp"/>

在例1中的HellloWorld.jsp中<body>后加入上述代码，并在struts.xml中新建Action，代码如下：
<action name="HelloWorld">
<result>/HelloWorld.jsp</result>
</action>

或者，如果你多个JSP需要实现上述功能，你可以使用下面的通用配置，而不是为每一个JSP页面都新建一个Action。
<action name="*">
<result>/{1}.jsp</result>
</action>

分布运行程序，在浏览器的地址栏中输入http://localhost:8080/Struts2_i18n/HelloWorld.action，出现图4所示页面：

图3 HelloWorld.action

在下拉列表中，选择“American English”，出现图5所示页面：

图4 HelloWorld.action
可能大家会问为什么一定要通过Action来访问页面呢？
你可以试一下不用Action而直接用JSP的地址来访问页面，结果会是无论你在下拉列表中选择什么，语言都不会改变。这表示不能正常运行的。其原因为如果直接使用JSP访问页面，Struts 2.0在web.xml的配置的过滤器（Filter）就不会工作，所以拦截器链也不会工作。
posted on 2006-11-01 19:06 Max 阅读(2326) 评论(23) 编辑 收藏 引用 所属分类: Struts 2.0系列


转换器（Converter）——Struts 2.0中的魔术师 - Max On Java - BlogJava

转换器（Converter）——Struts 2.0中的魔术师

在我已往的Struts 1.x项目经验中，有个问题不时的出现——在创建FormBean时，对于某个属性到底应该用String还是其它类型？

开发Web应用程序与开发传统桌面应用程序不同，Web应用程序实际上是分布个不同的主机（当然也可以同一个主机，不过比较少见）上的两个进程之间互交。这种互交建立在HTTP之上，它们互相传递是都是字符串。换句话说， 服务器可以的接收到的来自用户的数据只能是字符串或字符数组，而在服务器上的对象中，这些数据往往有多种不同的类型，如日期（Date），整数（int），浮点数（float）或自定义类型（UDT）等，如图1所示。因此，我们需要服务器端将字符串转换为适合的类型。

图1　UI与服务器对象关系
图1　UI与服务器对象关系

同样的问题也发生在使用UI展示服务器数据的情况。HTML的Form控件不同于桌面应用程序可以表示对象，其值只能为字符串类型，所以我们需要通过某种方式将特定对象转换成字符串。

要实现上述转换，Struts 2.0中有位魔术师可以帮到你——Converter。有了它，你不用一遍又一遍的重复编写诸如此类代码：

Date birthday = DateFormat.getInstance(DateFormat.SHORT).parse(strDate);
<input type="text" value="<%= DateFormat.getInstance(DateFormat.SHORT).format(birthday) %>" />

好了，现在让我们来看一个例子。

转换器——Hello World

在我的上一篇文章《在Struts 2.0中国际化(i18n)您的应用程序》的最后我举了一个可以让用户方便地切换语言的例子，下面例子与其相似，但实现方法不同。

首先，如《在Struts 2.0中国际化(i18n)您的应用程序》的第一个例子一样，创建和配置默认的资源文件；

接着，新建源代码文件夹下的tutorial包创建HelloWorld.java文件，代码如下：

package tutorial;

 import java.util.Locale;

 import com.opensymphony.xwork2.ActionSupport;
 import com.opensymphony.xwork2.util.LocalizedTextUtil;

 public class HelloWorld extends ActionSupport [image] {
 private String msg;
 private Locale loc = Locale.US;

 public String getMsg() [image] {
 return msg;
}

public Locale getLoc() [image] {
 return loc;
}

public void setLoc(Locale loc) [image] {
 this .loc = loc;
}

@Override
 public String execute() [image] {
 // LocalizedTextUtil是Struts 2.0中国际化的工具类，<s:text>标志就是通过调用它实现国际化的
msg = LocalizedTextUtil.findDefaultText( " HelloWorld " , loc);
 return SUCCESS;
}
}

然后，在源代码文件夹下的struts.xml加入如下代码新建Action：

< package name ="ConverterDemo" extends ="struts-default" >
< action name ="HelloWorld" class ="tutorial.HelloWorld" >
< result > /HelloWorld.jsp </ result >
</ action >
</ package >

再在Web文件夹下，新建 HelloWorld.jsp，代码如下：

< %@ page contentType ="text/html; charset=UTF-8" % >
< %@taglib prefix ="s" uri ="/struts-tags" % >
< html >
< head >
< title > Hello World </ title >
</ head >
< body >
< s:form action ="HelloWorld" theme ="simple" >
Locale: < s:textfield name ="loc" /> &nbsp; < s:submit />
</ s:form >
< h2 >< s:property value ="msg" /></ h2 >
</ body >
</ html >

接下来，在源代码文件夹的tutorial包中新建LocaleConverter.java文件，代码如下：

package tutorial;

 import java.util.Locale;
 import java.util.Map;

 public class LocaleConverter extends ognl.DefaultTypeConverter [image] {
@Override
 public Object convertValue(Map context, Object value, Class toType) [image] {
 if (toType == Locale. class ) [image] {
String locale = ((String[]) value)[ 0 ];
 return new Locale(locale.substring( 0 , 2 ), locale.substring( 3 ));
} else if (toType == String. class ) [image] {
Locale locale = (Locale) value;
 return locale.toString();
}
return null ;
}
}

再接下来，在源代码文件夹下新建xwork-conversion.properties，并在其中添加如下代码：

java.util.Locale = tutorial.LocaleConverter

发布运行应用程序，在浏览器中键入http://localhost:8080/Struts2_Converter/HelloWorld.action，输出页面如图2所示：
图2 HelloWorld英文输出
图2 HelloWorld英文输出

在Locale输入框中输入“zh_CN”，按“Submit”提交，出现如图3所示页面：
图3 HelloWorld中文输出
图3 HelloWorld中文输出

上述例子中，Locale文本输入框对应是Action中的类型为java.util.Locale的属性loc，所以需要创建一个自定义转变器实现两者间的转换。所有的Struts 2.0中的转换器都必须实现ognl.TypeConverter接口。 为了简单起见，OGNL包也为你提供了ognl.DefaultTypeConverter类去帮助您实现转换器。在例子中，LocaleConverter继承了ognl.DefaultTypeConverter，重载了其方法原型为“public Object convertValue(Map context, Object value, Class toType)”的方法。下面简单地介绍一下函数的参数：

  1. context——用于获取当前的ActionContext
  2. value——需要转换的值
  3. toType——需要转换成的目标类型
实现转换器，我们需要通过配置告诉Struts 2.0。我们可以通过以下两种方法做到这点：
  1. 配置全局的类型转换器，也即是上例的做法——在源代码文件夹下，新建一个名为“xwork-conversion.properties”的配置文件，并在文件中加入“待转换的类型的全名（包括包路径和类名）=转换器类的全名”对；
  2. 应用于某个特定类的类型转换器，做法为在该类的包中添加一个格式为“类名-conversion.properties”的配置文件，并在文件中加入“待转换的属性的名字=转换器类的全名”对。上面的例子也可以这样配置——在源代码文件夹的tutorial包下新建名为“HelloWorld-conversion.properties”文件，并在其中加入“loc=tutorial.LocaleConverter”。
在继承DefaultTypeConverter时，如果是要将value转换成其它非字符串类型时，要记住value是String[]类型，而不是String类型。它是通过request.getParameterValues(String arg)来获得的，所以不要试图将其强行转换为String类型。

已有的转换器

对于一此经常用到的转换器，如日期、整数或浮点数等类型，Struts 2.0已经为您实现了。下面列出已经实现的转换器。

  1. 预定义类型，例如int、boolean、double等；
  2. 日期类型， 使用当前区域（Locale）的短格式转换，即DateFormat.getInstance(DateFormat.SHORT)；
  3. 集合（Collection）类型， 将request.getParameterValues(String arg)返回的字符串数据与java.util.Collection转换；
  4. 集合（Set）类型， 与List的转换相似，去掉相同的值；
  5. 数组（Array）类型， 将字符串数组的每一个元素转换成特定的类型，并组成一个数组。
对于已有的转换器，大家不必再去重新发明轮子。Struts在遇到这些类型时，会自动去调用相应的转换器。

批量封装对象（Bean）

不知道大家是否遇过这种情况，在一个页面里同时提交几个对象。例如，在发布产品的页面，同时发布几个产品。我在之前一个项目就遇到过这种需求，当时用的是Struts 1.x。那是一个痛苦的经历，我在Google搜了很久都没有理想的结果。幸运的是，在Struts 2.0中这种痛苦将一去不复返。下面我就演示一下如何实现这个需求。

首先，在源代码文件夹下的tutorial包中新建Product.java文件，内容如下：

package tutorial;

import java.util.Date;

publicclass Product [image] {
private String name;
privatedouble price;
private Date dateOfProduction;

public Date getDateOfProduction() [image] {
return dateOfProduction;
}

publicvoid setDateOfProduction(Date dateOfProduction) [image] {
this.dateOfProduction = dateOfProduction;
}

public String getName() [image] {
return name;
}

publicvoid setName(String name) [image] {
this.name = name;
}

publicdouble getPrice() [image] {
return price;
}

publicvoid setPrice(double price) [image] {
this.price = price;
}
}

然后，在同上的包下添加ProductConfirm.java类，代码如下：

package tutorial;

import java.util.List;

import com.opensymphony.xwork2.ActionSupport;

publicclass ProductConfirm extends ActionSupport [image] {
public List<Product> products;

public List<Product> getProducts() [image] {
return products;
}

publicvoid setProducts(List<Product> products) [image] {
this.products = products;
}

@Override
public String execute() [image] {
for(Product p : products) [image] {
System.out.println(p.getName() + " | "+ p.getPrice() +" | " + p.getDateOfProduction());
}
return SUCCESS;
}
}

接看，在同上的包中加入ProductConfirm-conversion.properties，代码如下：

Element_products=tutorial.Product

再在struts.xml文件中配置ProductConfirm Action，代码片段如下：

<action name="ProductConfirm" class="tutorial.ProductConfirm">
<result>/ShowProducts.jsp</result>
</action>

在WEB文件夹下新建AddProducts.jsp，内容如下：

<%@ page contentType="text/html; charset=UTF-8"%>
<%@taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<s:form action="ProductConfirm" theme="simple">
<table>
<tr style="background-color:powderblue; font-weight:bold;">
<td>Product Name</td>
<td>Price</td>
<td>Date of production</td>
</tr>
<s:iterator value="new int[3]" status="stat">
<tr>
<td><s:textfield name="%{'products['+#stat.index+'].name'}"/></td>
<td><s:textfield name="%{'products['+#stat.index+'].price'}"/></td>
<td><s:textfield name="%{'products['+#stat.index+'].dateOfProduction'}"/></td>
</tr>
</s:iterator>
<tr>
<td colspan="3"><s:submit /></td>
</tr>
</table>
</s:form>
</body>
</html>

在同样的文件夹下创建ShowProducts.jsp，内容如下：

<%@ page contentType="text/html; charset=UTF-8"%>
<%@taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<table>
<tr style="background-color:powderblue; font-weight:bold;">
<td>Product Name</td>
<td>Price</td>
<td>Date of production</td>
</tr>
<s:iterator value="products" status="stat">
<tr>
<td><s:property value="name"/></td>
<td>$<s:property value="price"/></td>
<td><s:property value="dateOfProduction"/></td>
</tr>
</s:iterator>
</table>
</body>
</html>

发布运行应用程序，在浏览器中键入http://localhost:8080/Struts2_Converter/AddProducts.jsp，出现如图4所示页面：
图4 添加产品页面
图4 添加产品页面

按图4所示，填写表单，按“Submit”提交，出现图5所示页面：
图5 查看产品页面
图5 查看产品页面

查看服务器的控制台，有如下输出：

Expert One-on-One J2EE Development without EJB | 39.99 | Mon Jun 2100:00:00 CST 2004
Pro Spring | 32.99 | Mon Jan 3100:00:00 CST 2005
Core J2EE Patterns: Best Practices and Design Strategies, Second Edition | 34.64 | Sat May 1000:00:00 CST 2003

上面的代码并不复杂，但有几点需要说明：

  1. ProductConfirm文件中的for(Product p : productes)的写法是J2SE 5.0中的新特性，作用遍历products列表；
  2. List<Product>也是J2SE 5.0的才有的泛型（Generic）；
  3. ProductConfirm-conversion.properties中“Element_products=tutorial.Product”是告诉Struts 2.0列表products的元素的类型为Product，而不是定义转换器；
  4. 在AddProducts.jsp的<s:textfield>的name为“%{'products['+#stat.index+'].name'}”，%{exp}格式表示使用OGNL表达式，上述表达式的相当于<%= "products[" + stat.index + "].name" %>，至于<s:iterator>标志的用法可以参考我之前的文章《常用的Struts 2.0的标志（Tag）介绍》。

转换错误处理

不知道大家在运行上面的例子时，有没有填错日期或数字情况，又或者您有没有思考过这种情况？如果还没有尝试的朋友可以试一下，在第一行的Price和Date of production中输入英文字母，然后按“Submit”提交。你会看到页面为空白，再看一下服务器的控制台输出，有如下语句： 警告: No result defined for action tutorial.ProductConfirm and result input，它提示我们没有为Action定义输入结果，所以，我们应该在源代码文件夹下的struts.xml中的ProductConfirm Action中加入以下代码：

<result name="input">/AddProducts.jsp</result>

重新加载应用程序，刷新浏览器重新提交请求，这时页面返回AddProducts.jsp，格式错误的输入框的值被保留，如下图6所示：
图6 没有提示的错返回页面
图6 没有提示的错返回页面

当然，我们还可以在页面上加上错误提示信息，通过在AddProducts.jsp的“<body>”后，加入下面代码可以实现：

<div style="color:red">
<s:fielderror />
</div>

刷新浏览器，重新提交请求，出现如图7所示页面：
图7 带提示的错返回页面
图7 带提示的错返回页面

以上的功能的都是通过Struts 2.0里的一个名为conversionError的拦截器（interceptor）工作，它被注册到默认拦截器栈（default interceptor stack）中。Struts 2.0在转换出错后，会将错误放到ActionContext中，在conversionError的作用是将这些错误封装为对应的项错误（field error），因此我们可以通过<s:fielderror />来将其在页面上显示出来。另外，大家看第二和第三行的Price都被赋为0.0的值，而第一行则保留其错误值。这同样是conversionError的功劳——没有出错的行调用的products[index].price（默认值为0.0），而出错的行则会被赋为页面所提交的错误值，这样可以提供更好的用户体验。

总结

Struts 2.0的转换器简化的WEB应用程序的模型，为我们的编程带来极大的方便。

posted on 2006-11-07 14:26 Max 阅读(2999) 评论(5) 编辑 收藏 引用 所属分类: Struts 2.0系列


在Struts 2.0中实现表单数据校验（Validation） - Max On Java - BlogJava

在Struts 2.0中实现表单数据校验（Validation）
http://www.blogjava.net/max/category/16130.html
All Input Is Evil!
-Writing secure code

在写前几篇文章的时候，有些朋友建议我的写一篇关于表单数据校验的文章。 正如文章的开头所引用的《Writing Secure Code》的名言：“所有的输入都是罪恶的”，所以我们应该对所有的外部输入进行校验。而表单是应用程序最简单的入口，对其传进来的数据，我们必须进行校验。

转换与校验（Conversion & Validation）

其实上篇文章，我本来是打算写表单数据校验的内容，但是经过再三思考后，还是决定先写Struts 2.0转换器的内容。原因是我认为转换是校验的基础，只有在数据被正确地转换成其对应的类型后，我们才可以对其取值范围进行校验。看个例子相信大家可以更清楚。现在我们就来改造一下《转换器（Converter）——Struts 2.0中的魔术师》的第一个例子。

首先，从Action开始，修改后的代码如下：

package tutorial;

 import java.util.Locale;

 import com.opensymphony.xwork2.ActionSupport;
 import com.opensymphony.xwork2.util.LocalizedTextUtil;

 public class HelloWorld extends ActionSupport [image] {
 private String msg;
 private Locale loc = Locale.US;

 public String getMsg() [image] {
 return msg;
}

public Locale getLoc() [image] {
 return loc;
}

public void setLoc(Locale loc) [image] {
 this .loc = loc;
}

@Override
 public void validate() [image] {
System.out.println( " Calling validate() [image] " );
 if ( ! (loc.equals(Locale.US) || loc.equals(Locale.CHINA))) [image] {
addFieldError( " loc " , getText( " validation.loc " ));
}
}

public void validateExecute() [image] {
System.out.println( " Calling validateExecute() by reflection [image] " );
}

@Override
 public String execute() [image] {
System.out.println( " Calling execute() [image] " );
 // LocalizedTextUtil是Struts 2.0中国际化的工具类，<s:text>标志就是通过调用它实现国际化的
msg = LocalizedTextUtil.findDefaultText( " HelloWorld " , loc);
 return SUCCESS;
}
}

然后，修改Struts.xml中Action的定义指明输入地址：

< action name ="HelloWorld" class ="tutorial.HelloWorld" >
< result > /HelloWorld.jsp </ result >
< result name ="input" > /HelloWorld.jsp </ result >
</ action >

接着，在HelloWorld.jsp中加入错误提示：

<% @ page contentType = " text/html; charset=UTF-8 " %>
<% @taglib prefix = " s " uri = " /struts-tags " %>
< html >
< head >
< title > Hello World </ title >
</ head >
< body >
< div style ="color:red;" >
< s:fielderror />
</ div >
< s:form action ="HelloWorld" theme ="simple" >
Locale: < s:textfield name ="loc" /> &nbsp; < s:submit />
</ s:form >
< h2 >< s:property value ="msg" /></ h2 >
</ body >
</ html >

再修改LocaleConverter.java文件，将内容改为：

package tutorial;

 import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Pattern;

 public class LocaleConverter extends ognl.DefaultTypeConverter [image] {
@Override
 public Object convertValue(Map context, Object value, Class toType) [image] {
 if (toType == Locale. class ) [image] {
System.out.println( " Converting String to Locale [image] " );
String locale = ((String[]) value)[ 0 ];
 return new Locale(locale.substring( 0 , 2 ), locale.substring( 3 ));
} else if (toType == String. class ) [image] {
System.out.println( " Converting Locale to String [image] " );
Locale locale = (Locale) value;
 return locale.toString();
}
return null ;
}
}

之后，修改国际化资源文件，内容为：

HelloWorld = 你好，世界！
invalid.fieldvalue.loc = Locale必须为\ " xx_XX\ " 的格式
validation.loc = 区域必须为中国或美国
globalMessages_zh_CN.properties

HelloWorld = Hello World!
invalid.fieldvalue.loc = Locale must like \ " xx_XX\ "
validation.loc = Locale must be China or USA
globalMessages_en_US.properties

发布运行应用程序，在浏览器中键入http://localhost:8080/Struts2_Validation/HelloWorld.action，在Locale中输入zh_CN，按“Submit”提交，效果如上篇文章所示。而在服务器控制台有如下输出：

Converting String to Locale...
Calling validateExecute() by reflection...
Calling validate()...
Calling execute()...
Converting Locale to String...

上述的输出说明了Struts 2.0的数据校验工作方式，它需要经过下面几个步骤：

  1. 通过转换器将请求参数转换成相应的Bean属性；
  2. 判断转换过程是否出现异常。如果有，则将其保存到ActionContext中，conversionError拦截器再封装为fieldError；如果没有，进行下一步；
  3. 通过反射（Reflection）来调用validateXxx()方法（其中，Xxx表示Action的方法名）；
  4. 调用validate()方法；
  5. 如果经过上述步骤没有出现fieldError，则调用Action方法；如果有，则会跳过Action方法，通过国际化将fieldError输出到页面。

不喜欢看文字的朋友，可以参考下面的图1。

图1 校验顺序图
图1 校验顺序图

看到这里可能大家会疑问：“这么多地方可以校验表单数据，到底我应该在那里做呢？”有选择是好事，但抉择的过程往往是痛苦的，往往让人不知所措。如果大家参照以下几点建议，相信会比较容易地做出正确的抉择。

  1. 如果需要转换的数据，通常做法在转换的时候做格式的校验，在Action中的校验方法中校验取值。假如用户填错了格式，我们可以通过在资源文件配置invalid.fieldvalue.xxx（xxx为属性名）来提示用户正确的格式，不同的阶段出错显示不同的信息。具体做法请参考上面的例子；
  2. 至于用validate()还是validateXxx()，我推荐使用validate()。原因是validateXxx()使用了反射，相对来说性能稍差，而validate()则是通过接口com.opensymphony.xwork2.Validateable调用。当然如果你的表单数据取值是取决于特定Action方法，则应该使用validateXxx()。

在运行上面的例子时，在Locale中输入zh并提交时出现图2所示页面。

图2 错误格式
图2 错误格式

在Locale中输入de_DE时，出现如图3所示页面。

图3 取值错误
图3 取值错误

使用Struts 2.0的校验框架

上一节的内容都是关于如何编程实现校验，这部分工作大都是单调的重复。更多情况下，我们使用Struts 2.0的校验框架，通过配置实现一些常见的校验。

我学习编程有个习惯——喜欢先看输出结果，再看代码实现。这样学的好处是先看结果可以刺激学习的激情，也可以在看代码前自已思考一下如何实现，然后带着问题去看代码，那就清晰多了。因此下面我们先来做演示。

首先，在tutorial包下新建ValidationAction.java，代码如下：

package tutorial;

 import com.opensymphony.xwork2.ActionSupport;

 public class ValidationAction extends ActionSupport [image] {
 private String reqiuredString;

 public String getReqiuredString() [image] {
 return reqiuredString;
}

public void setReqiuredString(String reqiuredString) [image] {
 this .reqiuredString = reqiuredString;
}

@Override
 public String execute() [image] {
 return SUCCESS;
}
}

然后，配置上述所建的Ation，代码片段如下：

< action name ="ValidationAction" class ="tutorial.ValidationAction" >
< result > /Output.jsp </ result >
< result name ="input" > /Input.jsp </ result >
</ action >

接着，创建Input.jsp和Output.jsp，内容分别如下：

<% @ page contentType = " text/html; charset=UTF-8 " %>
<% @taglib prefix = " s " uri = " /struts-tags " %>
< html >
< head >
< title > Hello World </ title >
<!-- 此标志的作用是引入Struts 2.0的常用的Javascript和CSS -->
< s:head />
</ head >
< body >
< s:form action ="ValidationAction" >
< s:textfield name ="reqiuredString" label ="Required String" />
< s:submit />
</ s:form >
</ body >
</ html >
Input.jsp

<% @ page contentType = " text/html; charset=UTF-8 " %>
<% @taglib prefix = " s " uri = " /struts-tags " %>
< html >
< head >
< title > Hello World </ title >
</ head >
< body >
Required String: < s:property value ="reqiuredString" />
</ body >
</ html >
Output.jsp

再接下来，在tutorial包下创建ValidationAction的校验配置文件Xxx-validation.xml（Xxx为Action的类名），在本例中该文件名ValidationAction-validation.xml，内容如下：

<? xml version="1.0" encoding="UTF-8" ?>
<! DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd" >
< validators >
< field name ="reqiuredString" >
< field-validator type ="requiredstring" >
< message > This string is required </ message >
</ field-validator >
</ field >
</ validators >

发布运行应用程序，在地址栏中键入http://localhost:8080/Struts2_Validation/Input.jsp，出现如图4所示页面。

图4 Input.jsp

直接点击“Submit”提交表单，出现图5所示的页面。

图5 错误提示
图5 错误提示

在Required String中随便填点东西，转到Output.jsp页面，如图6所示。

图6 Output.jsp

通过上面的例子，大家可以看到使用该校验框架十分简单方便。不过，上例还有两点不足：

  1. 还没有国际化错误消息；
  2. 没有实现客户端的校验。

当然，要完善以上不足，对于Struts 2.0来说，只是小菜一碟。

  1. 在Xxx-validation.xml文件中的<message>元素中加入key属性；
  2. 在Input.jsp中的<s:form>标志中加入validate="true"属性，就可以在用Javascript在客户端校验数据。

下面是具体的实现，首先在国际化资源文件中加入错误消息，然后按照上面说明实现。因为要使用Javascript在客户端显示出错信息，所以在加载Input.jsp页面时，Struts 2.0需要获得国际化的字符串，故我们需要使用Action来访问Input.jsp页面，具体实现请参考《在Struts 2.0中国际化(i18n)您的应用程序》的最后部分。最后发布运行应用程序，直接在页面中点击“Submit”，表单没有被提交并出现错误提示，如图7所示：

图7 客户端校验
图7 客户端校验

校验框架是通过validation拦截器实现，该拦载被注册到默认的拦截器链中。它在conversionError拦截器之后，在validateXxx()之前被调用。这里又出现了一个选择的问题：到底是应该在action中通过validateXxx()或validate()实现校验，还是使用validation拦截器？绝大多数情况，我建议大家使用校验框架，只有当框架满足不了您的要求才自已编写代码实现。

配置文件查找顺序

在上面的例子中，我们通过创建ValidationAction-validation.xml来配置表单校验。Struts 2.0的校验框架自动会读取该文件，但这样就会引出一个问题——如果我的Action继承其它的Action类，而这两个Action类都需要对表单数据进行校验，那我是否会在子类的配置文件（Xxx-validation.xml）中复制父类的配置吗？

答案是不，因为Struts 2.0的校验框架跟《在Struts 2.0中国际化(i18n)您的应用程序》提到的“资源文件查找顺序”相似，有特定的配置文件查找顺序。不同的是校验框架按照自上而下的顺序在类层次查找配置文件。假设以下条件成立：

  1. 接口 Animal;
  2. 接口 Quadraped 扩展了 Animal;
  3. 类 AnimalImpl 实现了 Animal;
  4. 类 QuadrapedImpl 扩展了 AnimalImpl 实现了 Quadraped;
  5. 类 Dog 扩展了 QuadrapedImpl;

如果Dog要被校验,框架方法会查找下面的配置文件（其中别名是Action在struts.xml中定义的别名）:

  1. Animal-validation.xml
  2. Animal-别名-validation.xml
  3. AnimalImpl-validation.xml
  4. AnimalImpl-别名-validation.xml
  5. Quadraped-validation.xml
  6. Quadraped-别名-validation.xml
  7. QuadrapedImpl-validation.xml
  8. QuadrapedImpl-别名-validation.xml
  9. Dog-validation.xml
  10. Dog-别名-validation.xml

已有的校验器

Struts 2.0已经为您实现很多常用的校验了，以下在jar的default.xml中的注册的校验器。

< validators >
< validator name ="required" class ="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator" />
< validator name ="requiredstring" class ="com.opensymphony.xwork2.validator.validators.RequiredStringValidator" />
< validator name ="int" class ="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator" />
< validator name ="double" class ="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator" />
< validator name ="date" class ="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator" />
< validator name ="expression" class ="com.opensymphony.xwork2.validator.validators.ExpressionValidator" />
< validator name ="fieldexpression" class ="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator" />
< validator name ="email" class ="com.opensymphony.xwork2.validator.validators.EmailValidator" />
< validator name ="url" class ="com.opensymphony.xwork2.validator.validators.URLValidator" />
< validator name ="visitor" class ="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator" />
< validator name ="conversion" class ="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator" />
< validator name ="stringlength" class ="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator" />
< validator name ="regex" class ="com.opensymphony.xwork2.validator.validators.RegexFieldValidator" />
</ validators >
关于每个校验器的具体用法，请参考以下链接：
http://wiki.javascud.org/display/ww2cndoc/Validation
该链接中还有一些很有的信息，请大家仔细阅读。

总结

使用校验框架既可以方便地实现表单数据校验，又能够将校验与Action分离，故我们应该尽可能使用校验框架。在使用校验框架时，请不要忘记通过在资源文件加入invalid.fieldvalue.xxx字符串，显示适合的类型转换出错信息；或者使用conversion校验器。

posted on 2006-11-14 13:38 Max 阅读(4654) 评论(31) 编辑 收藏 引用 所属分类: Struts 2.0系列


Struts 2的基石——拦截器（Interceptor） - Max On Java - BlogJava

Struts 2的基石——拦截器（Interceptor）
http://www.blogjava.net/max/category/16130.html

首先，要跟大家道个歉，前一阵子为给客户个一个DEMO，忙得不可开交，所以很久没有更新Blog。提到这个DEMO我想顺便跟大家分享一下心得——如果大家希望快速开发，一个类似Struts 2这样的简单方便的WEB框架必不可少。我们在开发DEMO使用的还是Struts 1.2.8，而且没有不使用任何EL（表达式语言），导致页面出现无数类似“<%= ((Integer) request.getAttribute("xx")).intValue()%6 %>”的代码。Struts 1.x的Form Bean的麻烦使得有部分同事直接使用request.getParameter(String arg)，继而引入另一种麻烦。诸如此类的问题，在DEMO这样时间紧迫的项目凸显了 Struts 1.x对快速开发的无能为力。不过没办法，由于我们项目中的几个资深员工除了Struts 1.x外，对其它的WEB框架似乎不大感兴趣。

言归正传，Interceptor（以下译为拦截器）是Struts 2的一个强有力的工具，有许多功能（feature）都是构建于它之上，如国际化、转换器，校验等。

什么是拦截器

拦截器，在AOP（Aspect-Oriented Programming）中用于在某个方法或字段被访问之前，进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。

在 Webwork的中文文档的解释为——拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码，也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。

谈到拦截器，还有一个词大家应该知道——拦截器链（Interceptor Chain，在Struts 2中称为拦截器栈Interceptor Stack）。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时，拦截器链中的拦截器就会按其之前定义的顺序被调用。

实现原理

Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时，Struts 2会查找配置文件，并根据其配置实例化相对的拦截器对象，然后串成一个列表（list），最后一个一个地调用列表中的拦截器，如图1所示。

图１　拦截器调用序列图
图１　拦截器调用序列图

已有的拦截器

Struts 2已经为您提供丰富多样的，功能齐全的拦截器实现。大家可以到struts2-all-2.0.1.jar或struts2-core-2.0.1.jar包的struts-default.xml查看关于默认的拦截器与拦截器链的配置。

在本文使用是Struts 2的最新发布版本2.0.1。需要下载的朋友请点击以下链接：
http://apache.justdn.org/struts/binaries/struts-2.0.1-all.zip

以下部分就是从struts-default.xml文件摘取的内容：

< interceptor name ="alias" class ="com.opensymphony.xwork2.interceptor.AliasInterceptor" />
< interceptor name ="autowiring" class ="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor" />
< interceptor name ="chain" class ="com.opensymphony.xwork2.interceptor.ChainingInterceptor" />
< interceptor name ="conversionError" class ="org.apache.struts2.interceptor.StrutsConversionErrorInterceptor" />
< interceptor name ="createSession" class ="org.apache.struts2.interceptor.CreateSessionInterceptor" />
< interceptor name ="debugging" class ="org.apache.struts2.interceptor.debugging.DebuggingInterceptor" />
< interceptor name ="external-ref" class ="com.opensymphony.xwork2.interceptor.ExternalReferencesInterceptor" />
< interceptor name ="execAndWait" class ="org.apache.struts2.interceptor.ExecuteAndWaitInterceptor" />
< interceptor name ="exception" class ="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor" />
< interceptor name ="fileUpload" class ="org.apache.struts2.interceptor.FileUploadInterceptor" />
< interceptor name ="i18n" class ="com.opensymphony.xwork2.interceptor.I18nInterceptor" />
< interceptor name ="logger" class ="com.opensymphony.xwork2.interceptor.LoggingInterceptor" />
< interceptor name ="model-driven" class ="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor" />
< interceptor name ="scoped-model-driven" class ="com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor" />
< interceptor name ="params" class ="com.opensymphony.xwork2.interceptor.ParametersInterceptor" />
< interceptor name ="prepare" class ="com.opensymphony.xwork2.interceptor.PrepareInterceptor" />
< interceptor name ="static-params" class ="com.opensymphony.xwork2.interceptor.StaticParametersInterceptor" />
< interceptor name ="scope" class ="org.apache.struts2.interceptor.ScopeInterceptor" />
< interceptor name ="servlet-config" class ="org.apache.struts2.interceptor.ServletConfigInterceptor" />
< interceptor name ="sessionAutowiring" class ="org.apache.struts2.spring.interceptor.SessionContextAutowiringInterceptor" />
< interceptor name ="timer" class ="com.opensymphony.xwork2.interceptor.TimerInterceptor" />
< interceptor name ="token" class ="org.apache.struts2.interceptor.TokenInterceptor" />
< interceptor name ="token-session" class ="org.apache.struts2.interceptor.TokenSessionStoreInterceptor" />
< interceptor name ="validation" class ="com.opensymphony.xwork2.validator.ValidationInterceptor" />
< interceptor name ="workflow" class ="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor" />
< interceptor name ="store" class ="org.apache.struts2.interceptor.MessageStoreInterceptor" />
< interceptor name ="checkbox" class ="org.apache.struts2.interceptor.CheckboxInterceptor" />
< interceptor name ="profiling" class ="org.apache.struts2.interceptor.ProfilingActivationInterceptor" />

配置和使用拦截器

在struts-default.xml中已经配置了以上的拦截器。如果您想要使用上述拦截器，只需要在应用程序struts.xml文件中通过“<include file="struts-default.xml" />”将struts-default.xml文件包含进来，并继承其中的struts-default包（package），最后在定义Action时，使用“<interceptor-ref name="xx" />”引用拦截器或拦截器栈（interceptor stack）。一旦您继承了struts-default包（package），所有Action都会调用拦截器栈 ——defaultStack。当然，在Action配置中加入“<interceptor-ref name="xx" />”可以覆盖defaultStack。

下面是关于拦截器timer使用的例子。首先，新建Action类tuotrial/TimerInterceptorAction.java，内容如下：

package tutorial;

 import com.opensymphony.xwork2.ActionSupport;

 public class TimerInterceptorAction extends ActionSupport [image] {
@Override
 public String execute() [image] {
 try [image] {
 // 模拟耗时的操作
Thread.sleep( 500 );
} catch (Exception e) [image] {
e.printStackTrace();
}
return SUCCESS;
}
}

配置Action，名为Timer，配置文件如下：

<! DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd" >
< struts >
< include file ="struts-default.xml" />
< package name ="InterceptorDemo" extends ="struts-default" >
< action name ="Timer" class ="tutorial.TimerInterceptorAction" >
< interceptor-ref name ="timer" />
< result > /Timer.jsp </ result >
</ action >
</ package >
</ struts >

至于Timer.jsp可以随意写些什么到里面。发布运行应用程序，在浏览器的地址栏键入http://localhost:8080/Struts2_Interceptor/Timer.action，在出现Timer.jsp页面后，查看服务器的后台输出。

2006 - 12 - 6 14 : 27 : 32 com.opensymphony.xwork2.interceptor.TimerInterceptor doLog
信息: Executed action [ //Timer!execute ] took 2859 ms.

在您的环境中执行Timer!execute的耗时，可能上述的时间有些不同，这取决于您PC的性能。但是无论如何，2859 ms与500 ms还是相差太远了。这是什么原因呢？其实原因是第一次加载Timer时，需要进行一定的初始工作。当你重新请求Timer.action时，以上输出会变为：

2006 - 12 - 6 14 : 29 : 18 com.opensymphony.xwork2.interceptor.TimerInterceptor doLog
信息: Executed action [ //Timer!execute ] took 500 ms.

OK，这正是我们期待的结果。上述例子演示了拦截器timer的用途——用于显示执行某个action方法的耗时，在我们做一个粗略的性能调试时，这相当有用。

自定义拦截器

作为“框架（framework）”，可扩展性是不可或缺的，因为世上没有放之四海而皆准的东西。虽然，Struts 2为我们提供如此丰富的拦截器实现，但是这并不意味我们失去创建自定义拦截器的能力，恰恰相反，在Struts 2自定义拦截器是相当容易的一件事。

大家在开始着手创建自定义拦截器前，切记以下原则：
拦截器必须是无状态的，不要使用在API提供的ActionInvocation之外的任何东西。

要求拦截器是无状态的原因是Struts 2不能保证为每一个请求或者action创建一个实例，所以如果拦截器带有状态，会引发并发问题。

所有的Struts 2的拦截器都直接或间接实现接口com.opensymphony.xwork2.interceptor.Interceptor。除此之外，大家可能更喜欢继承类com.opensymphony.xwork2.interceptor.AbstractInterceptor。

以下例子演示通过继承AbstractInterceptor，实现授权拦截器。

首先，创建授权拦截器类tutorial.AuthorizationInterceptor，代码如下：

package tutorial;

 import java.util.Map;

 import com.opensymphony.xwork2.Action;
 import com.opensymphony.xwork2.ActionInvocation;
 import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

 public class AuthorizationInterceptor extends AbstractInterceptor [image] {

@Override
 public String intercept(ActionInvocation ai) throws Exception [image] {
Map session = ai.getInvocationContext().getSession();
String role = (String) session.get( " ROLE " );
 if ( null != role) [image] {
Object o = ai.getAction();
 if (o instanceof RoleAware) [image] {
RoleAware action = (RoleAware) o;
action.setRole(role);
}
return ai.invoke();
} else [image] {
 return Action.LOGIN;
}
}

}

以上代码相当简单，我们通过检查session是否存在键为“ROLE”的字符串，判断用户是否登陆。如果用户已经登陆，将角色放到Action中，调用Action；否则，拦截直接返回Action.LOGIN字段。为了方便将角色放入Action，我定义了接口tutorial.RoleAware，代码如下：

package tutorial;

 public interface RoleAware [image] {
 void setRole(String role);
}

接着，创建Action类tutorial.AuthorizatedAccess模拟访问受限资源，它作用就是通过实现RoleAware获取角色，并将其显示到ShowUser.jsp中，代码如下：

package tutorial;

 import com.opensymphony.xwork2.ActionSupport;

 public class AuthorizatedAccess extends ActionSupport implements RoleAware [image] {
 private String role;

 public void setRole(String role) [image] {
 this .role = role;
}

public String getRole() [image] {
 return role;
}

@Override
 public String execute() [image] {
 return SUCCESS;
}
}

以下是ShowUser.jsp的代码：

<% @ page contentType = " text/html; charset=UTF-8 " %>
<% @taglib prefix = " s " uri = " /struts-tags " %>
< html >
< head >
< title > Authorizated User </ title >
</ head >
< body >
< h1 > Your role is: < s:property value ="role" /></ h1 >
</ body >
</ html >

然后，创建tutorial.Roles初始化角色列表，代码如下：

package tutorial;

 import java.util.Hashtable;
 import java.util.Map;

 public class Roles [image] {
 public Map < String, String > getRoles() [image] {
Map < String, String > roles = new Hashtable < String, String > ( 2 );
roles.put( " EMPLOYEE " , " Employee " );
roles.put( " MANAGER " , " Manager " );
 return roles;
}
}

接下来，新建Login.jsp实例化tutorial.Roles，并将其roles属性赋予<s:radio>标志，代码如下：

<% @ page contentType = " text/html; charset=UTF-8 " %>
<% @taglib prefix = " s " uri = " /struts-tags " %>
< html >
< head >
< title > Login </ title >
</ head >
< body >
< h1 > Login </ h1 >
Please select a role below:
< s:bean id ="roles" name ="tutorial.Roles" />
< s:form action ="Login" >
< s:radio list ="#roles.roles" value ="'EMPLOYEE'" name ="role" label ="Role" />
< s:submit />
</ s:form >
</ body >
</ html >

创建Action类tutorial.Login将role放到session中，并转到Action类tutorial.AuthorizatedAccess，代码如下：

package tutorial;

 import java.util.Map;

 import org.apache.struts2.interceptor.SessionAware;

 import com.opensymphony.xwork2.ActionSupport;

 public class Login extends ActionSupport implements SessionAware [image] {
 private String role;
private Map session;

 public String getRole() [image] {
 return role;
}

public void setRole(String role) [image] {
 this .role = role;
}

public void setSession(Map session) [image] {
 this .session = session;
}

@Override
 public String execute() [image] {
session.put( " ROLE " , role);
 return SUCCESS;
}
}

最后，配置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 ="struts-default.xml" />
< package name ="InterceptorDemo" extends ="struts-default" >
< interceptors >
< interceptor name ="auth" class ="tutorial.AuthorizationInterceptor" />
</ interceptors >
< action name ="Timer" class ="tutorial.TimerInterceptorAction" >
< interceptor-ref name ="timer" />
< result > /Timer.jsp </ result >
</ action >
< action name ="Login" class ="tutorial.Login" >
< result type ="chain" > AuthorizatedAccess </ result >
</ action >
< action name ="AuthorizatedAccess" class ="tutorial.AuthorizatedAccess" >
< interceptor-ref name ="auth" />
< result name ="login" > /Login.jsp </ result >
< result name ="success" > /ShowRole.jsp </ result >
</ action >
</ package >
</ struts >

发布运行应用程序，在浏览器地址栏中输入：http://localhost:8080/Struts2_Interceptor/AuthorizatedAccess.action。由于此时，session还没有键为“ROLE”的值，所以返回Login.jsp页面，如图2所示：

图２ Login.jsp

选中Employee，点击Submit，出现图3所示页面：

图3 ShowRole.jsp

总结

拦截器是Struts 2比较重要的一个功能。通过正确地使用拦截器，我们可以编写高可复用的代码。

posted on 2006-12-06 20:10 Max 阅读(3782) 评论(14) 编辑 收藏 引用 所属分类: Struts 2.0系列


在Struts 2中实现IoC - Max On Java - BlogJava

在Struts 2中实现IoC

IoC(Inversion of Control，以下译为控制反转）随着Java社区中轻量级容器（Lightweight Contianer）的推广而越来越为大家耳熟能详。在此，我不想再多费唇舌来解释“什么是控制反转”和“为什么需要控制反转”。因为互联网上已经有非常多的文章对诸如此类的问题作了精彩而准确的回答。大家可以去读一下Rod Johnson和Juergen Hoeller合著的《Expert one-on-one J2EE Development without EJB》或Martin Fowler所写的《Inversion of Control Containers and the Dependency Injection pattern》。

言归正传，本文的目的主要是介绍在Struts 2中实现控制反转。

历史背景

众所周知，Struts 2是以Webwork 2作为基础发展出来。而在Webwork 2.2之前的Webwork版本，其自身有一套控制反转的实现，Webwork 2.2在Spring 框架的如火如荼发展的背景下，决定放弃控制反转功能的开发，转由Spring实现。值得一提的是，Spring确实是一个值得学习的框架，因为有越来越多的开源组件（如iBATIS等）都放弃与Spring重叠的功能的开发。因此，Struts 2推荐大家通过Spring实现控制反转。

具体实现

首先，在开发环境中配置好Struts 2的工程。对这部分仍然有问题的朋友，请参考我的早前的文章。

然后，将所需的Spring的jar包加入到工程的构建环境（Build Path）中，如下图1所示：

图1 所依赖的Spring的jar包
图1 所依赖的Spring的jar包

本文使用的是Spring 2.0，Spring强烈建议大家在使用其jar包时，只引用需要的包，原因是Spring是一个功能非常强大的框架，其中有些功能是您不需要的；而且Spring提倡的是“按需所取”，而不是EJB的“爱我就要爱我的一切”。当然，如果你怕麻烦或者是不清楚每个包的作用，引用一个Spring的总包也未尝不可。

接下来，就要修改WEB-INF\web.xml文件了，内容为：

<? xml version="1.0" encoding="UTF-8" ?>
< web-app version ="2.4" xmlns ="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" >

< display-name > Struts 2 IoC Demo </ display-name >

< filter >
< filter-name > struts-cleanup </ filter-name >
< filter-class >
org.apache.struts2.dispatcher.ActionContextCleanUp
</ filter-class >
</ filter >

< filter >
< filter-name > struts2 </ filter-name >
< filter-class >
org.apache.struts2.dispatcher.FilterDispatcher
</ filter-class >
</ filter >

< filter-mapping >
< filter-name > struts-cleanup </ filter-name >
< url-pattern > /* </ url-pattern >
</ filter-mapping >

< filter-mapping >
< filter-name > struts2 </ filter-name >
< url-pattern > /* </ url-pattern >
</ filter-mapping >

< listener >
< listener-class >
org.springframework.web.context.ContextLoaderListener
</ listener-class >
</ listener >

< welcome-file-list >
< welcome-file > index.html </ welcome-file >
</ welcome-file-list >
</ web-app >
清单1 WEB-INF\web.xml

大家一看便知道，主要是加入Spring的ContextLoaderListener监听器，方便Spring与Web容器交互。

紧接着，修改Struts.properties文件，告知Struts 2运行时使用Spring来创建对象（如Action等），内容如下：

struts.objectFactory = spring
清单2 classes\struts.properties

再下来，遵循Spring的原则——面向接口编程，创建接口ChatService，代码如下：

package tutorial;

 import java.util.Set;

 public interface ChatService [image] {
Set < String > getUserNames();
}
清单3 tutorial.ChatService.java

然后，再创建一个默认实现ChatServiceImpl，代码如下：

package tutorial;

 import java.util.HashSet;
 import java.util.Set;

 public class ChatServiceImpl implements ChatService [image] {

 public Set < String > getUserNames() [image] {
Set < String > users = new HashSet < String > ();
users.add( " Max " );
users.add( " Scott " );
users.add( " Bob " );
 return users;
}

}
清单4 tutorial.ChatServiceImpl.java

接下来，就该新建Action了。tutorial.ChatAction.java的代码如下：

package tutorial;

 import java.util.Set;

 import com.opensymphony.xwork2.ActionSupport;

 public class ChatAction extends ActionSupport [image] {
 private static final long serialVersionUID = 8445871212065L ;

private ChatService chatService;
 private Set < String > userNames;

 public void setChatService(ChatService chatService) [image] {
 this .chatService = chatService;
}

public Set < String > getUserNames() [image] {
 return userNames;
}

@Override
 public String execute() [image] {
userNames = chatService.getUserNames();
 return SUCCESS;
}

}
清单5 tutorial.ChatAction.java

ChatAction类使用属性（Getter/Setter）注入法取得ChatService对象。

然后，配置Spring的applicationContext.xml（位于WEB-INF下）文件，内容如下：

<? xml version="1.0" encoding="UTF-8" ?>
< beans xmlns ="http://www.springframework.org/schema/beans"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" >
< bean id ="chatService" class ="tutorial.ChatServiceImpl" />
< bean id ="chatAction" class ="tutorial.ChatAction" scope ="prototype" >
< property name ="chatService" >
< ref local ="chatService" />
</ property >
</ bean >
</ beans >
清单6 WEB-INF\applicationContext.xml

上述代码有二点值得大家注意的：

  1. Struts 2会为每一个请求创建一个Action对象，所以在定义chatAction时，使用scope="prototype"。这样Spring就会每次都返回一个新的ChatAction对象了；
  2. 因为ChatServiceImpl被配置为默认的scope（也即是singleton，唯一的），所以在实现时应保证其线程安全（关于编写线程安全的代码的讨论已经超出本文的范围，更超出了本人的能力范围，大家可以参考Addison Wesley Professional出版的《Java Concurrency in Practice》）。

接下来，在classes/struts.xml中配置Action，内容如下：

<! DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd" >
< struts >
< include file ="struts-default.xml" />

< package name ="Struts2_IoC" extends ="struts-default" >
< action name ="Chat" class ="chatAction" >
< result > /UserList.jsp </ result >
</ action >
</ package >
</ struts >
清单7 classes\struts.xml

这里的Action和平常不同的就是class属性，它对应于Spring所定义的bean的id，而不是它的类全名。

最后，让我们看看/UserList.jsp，内容如下：

<% @ page contentType = " text/html; charset=UTF-8 " %>
<% @ taglib prefix = " s " uri = " /struts-tags " %>
< html >
< head >
< title > User List </ title >
</ head >

< body >
< h2 > User List </ h2 >
< ol >
< s:iterator value ="userNames" >
< li >< s:property /></ li >
</ s:iterator >
</ ol >
</ body >
</ html >
清单8 /UserList.jsp

大功告成，分布运行应用程序，在浏览器中键入http://localhost:8080/Struts2_IoC/Chat.action，出现如图2所示页面：

图2 /ListUser.jsp

总结

通过Spring在Struts 2上实现控制反转是强烈推荐的做法，当然您也可以组合其它的实现（如Pico等）。

posted on 2006-12-28 17:37 Max 阅读(3212) 评论(56) 编辑 收藏 引用 所属分类: Struts 2.0系列

评论:
# re: 在Struts 2中实现IoC 2006-12-28 17:44 | BeanSoft
呵呵,支持一个... 偶正在初学 spring... 回复 更多评论
# re: 在Struts 2中实现IoC 2006-12-29 00:52 | AlleNny
我有个问题：

你在文中提到“告知Struts 2运行时使用Spring来创建对象（如Action等”，但是例子中由spring创建的是业务逻辑的实现：
< bean id ="chatService" class ="tutorial.ChatServiceImpl" />
为何要将业务接口注入Action中，我觉得直接在Action中调用也无妨啊，难道不允许容器“依赖”业务逻辑？

个人愚见，请指教
回复 更多评论
# re: 在Struts 2中实现IoC 2006-12-29 09:27 | Tendy
@AlleNny
---
为何要将业务接口注入Action中，
---
re: 是业务接口的实现(tutorial.ChatServiceImpl) 注入 action

---
我觉得直接在Action中调用也无妨啊，难道不允许容器“依赖”业务逻辑？
---
re: 在 action 中 new tutorial.ChatServiceImpl() 也行
但当你需要使用不同的实现时，要改源代码，还要编译
用注入的方式，修改配置文件即可 回复 更多评论
# re: 在Struts 2中实现IoC 2006-12-29 14:16 | Max
@AlleNny
Tendy说得很正确，我来补充几点：
1、松耦合（Loose coupling）可以给程序的测试和维护带来很多好处；
2、通过Spring的IoC方式，可以简单地创建Singleton对象。
建议读一下Martin Fowler所写的《Inversion of Control Containers and the Dependency Injection pattern》，你可以google一下，应该有中文翻译的。 回复 更多评论
# re: 在Struts 2中实现IoC 2006-12-29 22:21 | Goingmm[匿名]
@"为何要将业务接口注入Action中"？ 其实这也不是唯一的选择！！

这样配置<ref local="chatService" />， 他们之间的关联关系会比较明确（这种关系至少在配置文件的映射上很直观）
如想达到更简洁的配置效果（放弃上面这种直观的关联关系）可以这样配置（其他地方都不需要修改）
<bean id="chatService" class="tutorial.ChatServiceImpl" autowire="byName"/>
<bean id="chatAction" class = "tutorial.ChatAction" scope="prototype" autowire="byName"/>
回复 更多评论


在Struts 2中实现文件上传 - Max On Java - BlogJava

在Struts 2中实现文件上传

前一阵子有些朋友在电子邮件中问关于Struts 2实现文件上传的问题， 所以今天我们就来讨论一下这个问题。

实现原理

Struts 2是通过Commons FileUpload文件上传。Commons FileUpload通过将HTTP的数据保存到临时文件夹，然后Struts使用fileUpload拦截器将文件绑定到Action的实例中。从而我们就能够以本地文件方式的操作浏览器上传的文件。

具体实现

前段时间Apache发布了Struts 2.0.6 GA，所以本文的实现是以该版本的Struts作为框架的。以下是例子所依赖类包的列表：

依赖类包的列表
清单1 依赖类包的列表

首先，创建文件上传页面FileUpload.jsp，内容如下：

<% @ page language = " java " contentType = " text/html; charset=utf-8 " pageEncoding = " utf-8 " %>
<% @ taglib prefix = " s " uri = " /struts-tags " %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html xmlns ="http://www.w3.org/1999/xhtml" >
< head >
< title > Struts 2 File Upload </ title >
</ head >
< body >
< s:form action ="fileUpload" method ="POST" enctype ="multipart/form-data" >
< s:file name ="myFile" label ="Image File" />
< s:textfield name ="caption" label ="Caption" />
< s:submit />
</ s:form >
</ body >
</ html >
清单2 FileUpload.jsp

在FileUpload.jsp中，先将表单的提交方式设为POST，然后将enctype设为multipart/form-data，这并没有什么特别之处。接下来，<s:file/>标志将文件上传控件绑定到Action的myFile属性。

其次是FileUploadAction.java代码：

package tutorial;

 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Date;

 import org.apache.struts2.ServletActionContext;

 import com.opensymphony.xwork2.ActionSupport;

 public class FileUploadAction extends ActionSupport [image] {
 private static final long serialVersionUID = 572146812454l ;
 private static final int BUFFER_SIZE = 16 * 1024 ;

 private File myFile;
 private String contentType;
 private String fileName;
 private String imageFileName;
 private String caption;

 public void setMyFileContentType(String contentType) [image] {
 this .contentType = contentType;
}

public void setMyFileFileName(String fileName) [image] {
 this .fileName = fileName;
}

public void setMyFile(File myFile) [image] {
 this .myFile = myFile;
}

public String getImageFileName() [image] {
 return imageFileName;
}

public String getCaption() [image] {
 return caption;
}

public void setCaption(String caption) [image] {
 this .caption = caption;
}

private static void copy(File src, File dst) [image] {
 try [image] {
InputStream in = null ;
OutputStream out = null ;
 try [image] {
in = new BufferedInputStream( new FileInputStream(src), BUFFER_SIZE);
out = new BufferedOutputStream( new FileOutputStream(dst), BUFFER_SIZE);
 byte [] buffer = new byte [BUFFER_SIZE];
 while (in.read(buffer) > 0 ) [image] {
out.write(buffer);
}
} finally [image] {
 if ( null != in) [image] {
in.close();
}
if ( null != out) [image] {
out.close();
}
}
} catch (Exception e) [image] {
e.printStackTrace();
}
}

private static String getExtention(String fileName) [image] {
 int pos = fileName.lastIndexOf( " . " );
 return fileName.substring(pos);
}

@Override
 public String execute() [image] {
imageFileName = new Date().getTime() + getExtention(fileName);
File imageFile = new File(ServletActionContext.getServletContext().getRealPath( " /UploadImages " ) + " / " + imageFileName);
copy(myFile, imageFile);
 return SUCCESS;
}

}
清单3 tutorial/FileUploadAction.java

在FileUploadAction中我分别写了setMyFileContentType、setMyFileFileName、setMyFile和setCaption四个Setter方法，后两者很容易明白，分别对应FileUpload.jsp中的<s:file/>和<s:textfield/>标志。但是前两者并没有显式地与任何的页面标志绑定，那么它们的值又是从何而来的呢？其实，<s:file/>标志不仅仅是绑定到myFile，还有myFileContentType（上传文件的MIME类型）和myFileFileName（上传文件的文件名，该文件名不包括文件的路径）。因此，<s:file name="xxx" />对应Action类里面的xxx、xxxContentType和xxxFileName三个属性。

FileUploadAction作用是将浏览器上传的文件拷贝到WEB应用程序的UploadImages文件夹下，新文件的名称是由系统时间与上传文件的后缀组成，该名称将被赋给imageFileName属性，以便上传成功的跳转页面使用。

下面我们就来看看上传成功的页面：

<% @ page language = " java " contentType = " text/html; charset=utf-8 " pageEncoding = " utf-8 " %>
<% @ taglib prefix = " s " uri = " /struts-tags " %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html xmlns ="http://www.w3.org/1999/xhtml" >
< head >
< title > Struts 2 File Upload </ title >
</ head >
< body >
< div style ="padding: 3px; border: solid 1px #cccccc; text-align: center" >
< img src ='UploadImages/<s:property value ="imageFileName" /> ' />
< br />
< s:property value ="caption" />
</ div >
</ body >
</ html >
清单4 ShowUpload.jsp

ShowUpload.jsp获得imageFileName，将其UploadImages组成URL，从而将上传的图像显示出来。

然后是Action的配置文件：

<? 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 ="fileUploadDemo" extends ="struts-default" >
< action name ="fileUpload" class ="tutorial.FileUploadAction" >
< interceptor-ref name ="fileUploadStack" />
< result name ="success" > /ShowUpload.jsp </ result >
</ action >
</ package >
</ struts >
清单5 struts.xml

fileUpload Action显式地应用fileUploadStack的拦截器。

最后是web.xml配置文件：

<? xml version="1.0" encoding="UTF-8" ?>
< web-app id ="WebApp_9" version ="2.4"
xmlns ="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" >

< display-name > Struts 2 Fileupload </ display-name >

< filter >
< filter-name > struts-cleanup </ filter-name >
< filter-class >
org.apache.struts2.dispatcher.ActionContextCleanUp
</ filter-class >
</ filter >

< filter >
< filter-name > struts2 </ filter-name >
< filter-class >
org.apache.struts2.dispatcher.FilterDispatcher
</ filter-class >
</ filter >

< filter-mapping >
< filter-name > struts-cleanup </ filter-name >
< url-pattern > /* </ url-pattern >
</ filter-mapping >

< filter-mapping >
< filter-name > struts2 </ filter-name >
< url-pattern > /* </ url-pattern >
</ filter-mapping >

< welcome-file-list >
< welcome-file > index.html </ welcome-file >
</ welcome-file-list >

</ web-app >
清单6 WEB-INF/web.xml

发布运行应用程序，在浏览器地址栏中键入：http://localhost:8080/Struts2_Fileupload/FileUpload.jsp，出现图示页面：

清单7 FileUpload页面

选择图片文件，填写Caption并按下Submit按钮提交，出现图示页面：

清单8 上传成功页面

更多配置

在运行上述例子，如果您留心一点的话，应该会发现服务器控制台有如下输出：

Mar 20 , 2007 4 : 08 : 43 PM org.apache.struts2.dispatcher.Dispatcher getSaveDir
INFO: Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir
Mar 20 , 2007 4 : 08 : 43 PM org.apache.struts2.interceptor.FileUploadInterceptor intercept
INFO: Removing file myFile C:\Program Files\Tomcat 5.5 \work\Catalina\localhost\Struts2_Fileupload\upload_251447c2_1116e355841__7ff7_00000006.tmp
清单9 服务器控制台输出

上述信息告诉我们，struts.multipart.saveDir没有配置。struts.multipart.saveDir用于指定存放临时文件的文件夹，该配置写在struts.properties文件中。例如，如果在struts.properties文件加入如下代码：

struts.multipart.saveDir = /tmp
清单10 struts配置

这样上传的文件就会临时保存到你根目录下的tmp文件夹中（一般为c:\tmp），如果此文件夹不存在，Struts 2会自动创建一个。

错误处理

上述例子实现的图片上传的功能，所以应该阻止用户上传非图片类型的文件。在Struts 2中如何实现这点呢？其实这也很简单，对上述例子作如下修改即可。

首先修改FileUpload.jsp，在<body>与<s:form>之间加入“<s:fielderror />”，用于在页面上输出错误信息。

然后修改struts.xml文件，将Action fileUpload的定义改为如下所示：

< action name ="fileUpload" class ="tutorial.FileUploadAction" >
< interceptor-ref name ="fileUpload" >
< param name ="allowedTypes" >
image/bmp,image/png,image/gif,image/jpeg
</ param >
</ interceptor-ref >
< interceptor-ref name ="defaultStack" />
< result name ="input" > /FileUpload.jsp </ result >
< result name ="success" > /ShowUpload.jsp </ result >
</ action >
清单11 修改后的配置文件

显而易见，起作用就是fileUpload拦截器的allowTypes参数。另外，配置还引入defaultStack它会帮我们添加验证等功能，所以在出错之后会跳转到名称为“input”的结果，也即是FileUpload.jsp。

发布运行应用程序，出错时，页面如下图所示：

清单12 出错提示页面

上面的出错提示是Struts 2默认的，大多数情况下，我们都需要自定义和国际化这些信息。通过在全局的国际资源文件中加入“struts.messages.error.content.type.not.allowed=The file you uploaded is not a image”，可以实现以上提及的需求。对此有疑问的朋友可以参考我之前的文章《在Struts 2.0中国际化(i18n)您的应用程序》。

实现之后的出错页面如下图所示：

清单13 自定义出错提示页面

同样的做法，你可以使用参数“maximumSize”来限制上传文件的大小，它对应的字符资源名为：“struts.messages.error.file.too.large”。

字符资源“struts.messages.error.uploading”用提示一般的上传出错信息。

多文件上传

与单文件上传相似，Struts 2实现多文件上传也很简单。你可以将多个<s:file />绑定Action的数组或列表。如下例所示。

< s:form action ="doMultipleUploadUsingList" method ="POST" enctype ="multipart/form-data" >
< s:file label ="File (1)" name ="upload" />
< s:file label ="File (2)" name ="upload" />
< s:file label ="FIle (3)" name ="upload" />
< s:submit />
</ s:form >
清单14 多文件上传JSP代码片段

如果你希望绑定到数组，Action的代码应类似：

private File[] uploads;
 private String[] uploadFileNames;
 private String[] uploadContentTypes;

 public File[] getUpload() [image] { return this .uploads; }
public void setUpload(File[] upload) [image] { this .uploads = upload; }

public String[] getUploadFileName() [image] { return this .uploadFileNames; }
public void setUploadFileName(String[] uploadFileName) [image] { this .uploadFileNames = uploadFileName; }

public String[] getUploadContentType() [image] { return this .uploadContentTypes; }
public void setUploadContentType(String[] uploadContentType) [image] { this .uploadContentTypes = uploadContentType; }
清单15 多文件上传数组绑定Action代码片段

如果你想绑定到列表，则应类似：

private List < File > uploads = new ArrayList < File > ();
 private List < String > uploadFileNames = new ArrayList < String > ();
 private List < String > uploadContentTypes = new ArrayList < String > ();

 public List < File > getUpload() [image] {
 return this .uploads;
}
public void setUpload(List < File > uploads) [image] {
 this .uploads = uploads;
}

public List < String > getUploadFileName() [image] {
 return this .uploadFileNames;
}
public void setUploadFileName(List < String > uploadFileNames) [image] {
 this .uploadFileNames = uploadFileNames;
}

public List < String > getUploadContentType() [image] {
 return this .uploadContentTypes;
}
public void setUploadContentType(List < String > contentTypes) [image] {
 this .uploadContentTypes = contentTypes;
}
清单16 多文件上传列表绑定Action代码片段

总结

在Struts 2中实现文件上传的确是轻而易举，您要做的只是使用<s:file />与Action的属性绑定。这又一次有力地证明了Struts 2的简单易用。

posted on 2007-03-21 00:48 Max 阅读(3125) 评论(28) 编辑 收藏 引用 所属分类: Struts 2.0系列


在Struts 2中实现CRUD - Max On Java - BlogJava

在Struts 2中实现CRUD

CRUD是Create（创建）、Read（读取）、Update（更新）和Delete（删除）的缩写，它是普通应用程序的缩影。如果您掌握了某框架的CRUD编写，那么意味可以使用该框架创建普通应用程序了，所以大家使用新框架开发OLTP（Online Transaction Processing）应用程序时，首先会研究一下如何编写CRUD。这类似于大家在学习新编程语言时喜欢编写“Hello World”。

本文旨在讲述Struts 2上的CRUD开发，所以为了例子的简单易懂，我不会花时间在数据库的操作上。取而代之的是一个模拟数据库的哈希表（Hash Map）。

具体实现

首先，让我们看看的“冒牌”的DAO（Data Access Object，数据访问对象），代码如下：

package tutorial.dao;

import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import tutorial.model.Book;

public class BookDao [image] {
private static final BookDao instance;
private static final ConcurrentMap<String, Book> data;

static [image] {
instance = new BookDao();
data = new ConcurrentHashMap<String, Book>();
data.put("978-0735619678", new Book("978-0735619678", "Code Complete, Second Edition", 32.99));
data.put("978-0596007867", new Book("978-0596007867", "The Art of Project Management", 35.96));
data.put("978-0201633610", new Book("978-0201633610", "Design Patterns: Elements of Reusable Object-Oriented Software", 43.19));
data.put("978-0596527341", new Book("978-0596527341", "Information Architecture for the World Wide Web: Designing Large-Scale Web Sites", 25.19));
data.put("978-0735605350", new Book("978-0735605350", "Software Estimation: Demystifying the Black Art", 25.19));
}

private BookDao() [image] {}

public static BookDao getInstance() [image] {
return instance;
}

public Collection<Book> getBooks() [image] {
return data.values();
}

public Book getBook(String isbn) [image] {
return data.get(isbn);
}

public void storeBook(Book book) [image] {
data.put(book.getIsbn(), book);
}

public void removeBook(String isbn) [image] {
data.remove(isbn);
}

public void removeBooks(String[] isbns) [image] {
for(String isbn : isbns) [image] {
data.remove(isbn);
}
}
}
清单1 src/tutorial/dao/BookDao.java

以上代码相信不用解释大家也清楚，我使用ConcurrentMap数据结构存储Book对象，这主要是为了方便检索和保存Book对象；另外，我还将data变量设为静态唯一来模拟应用程序的数据库。

接下来是的数据模型Book类，代码如下：

package tutorial.model;

public class Book [image] {
private String isbn;
private String title;
private double price;

public Book() [image] {
}

public Book(String isbn, String title, double price) [image] {
this.isbn = isbn;
this.title = title;
this.price = price;
}

public String getIsbn() [image] {
return isbn;
}

public void setIsbn(String isbn) [image] {
this.isbn = isbn;
}

public double getPrice() [image] {
return price;
}

public void setPrice(double price) [image] {
this.price = price;
}

public String getTitle() [image] {
return title;
}

public void setTitle(String title) [image] {
this.title = title;
}
}
清单2 src/tutorial/model/Book.java

Book类有三个属性isbn,、title和price分别代表书籍的编号、名称和价格，其中编号用于唯一标识书籍（相当数据库中的主键）。

然后，我们再来看看Action类的代码：

package tutorial.action;

import java.util.Collection;

import tutorial.dao.BookDao;
import tutorial.model.Book;

import com.opensymphony.xwork2.ActionSupport;

public class BookAction extends ActionSupport [image] {
private static final long serialVersionUID = 872316812305356L;

private String isbn;
private String[] isbns;
private Book book;
private Collection<Book> books;
private BookDao dao = BookDao.getInstance();

public Book getBook() [image] {
return book;
}

public void setBook(Book book) [image] {
this.book = book;
}

public String getIsbn() [image] {
return isbn;
}

public void setIsbn(String isbn) [image] {
this.isbn = isbn;
}

public String[] getIsbns() [image] {
return isbns;
}

public void setIsbns(String[] isbns) [image] {
this.isbns = isbns;
}

public Collection<Book> getBooks() [image] {
return books;
}

public void setBooks(Collection<Book> books) [image] {
this.books = books;
}

public String load() [image] {
book = dao.getBook(isbn);
return SUCCESS;
}

public String list() [image] {
books = dao.getBooks();
return SUCCESS;
}

public String store() [image] {
dao.storeBook(book);
return SUCCESS;
}

public String remove() [image] {
if(null != isbn) [image] {
dao.removeBook(isbn);
} else [image] {
dao.removeBooks(isbns);
}
return SUCCESS;
}
}
清单3 src/tutorial/action/BookAction.java

BookAction类中属性isbn用于表示待编辑或删除的书籍的编号，属性isbns用于表示多个待删除的书籍的编号数组，属性book表示当前书籍，属性books则表示当前的书籍列表。BookAction有四个Action方法分别是load、list、store和remove，也即是CRUD都集中在BookAction中实现。

再下来是Action的配置代码：

<?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="Struts2_CRUD_DEMO" extends="struts-default" namespace="/Book">
<action name="List" class="tutorial.action.BookAction" method="list">
<result>List.jsp</result>
</action>
<action name="Edit" class="tutorial.action.BookAction" method="load">
<result>Edit.jsp</result>
</action>
<action name="Store" class="tutorial.action.BookAction" method="store">
<result type="redirect">List.action</result>
</action>
<action name="Remove" class="tutorial.action.BookAction" method="remove">
<result type="redirect">List.action</result>
</action>
</package>
</struts>
清单4 src/struts.xml

以上的配置中，我使用了四个Action定义。它们都在“/Book”名值空间内。这样我就可以分别通过“http://localhost:8080/Struts2_CRUD/Book/List.action”、“http://localhost:8080/Struts2_CRUD/Book/Edit.action”、“http://localhost:8080/Struts2_CRUD/Book/Store.action”和“http://localhost:8080/Struts2_CRUD/Book/Remove.action”来调用BookAction的四个Action方法进行CRUD操作。当然，这只是个人喜好，你大可以只定义一个Action（假设其名称为“Book”），之后通过“http://localhost:8080/Struts2_CRUD/Book!list.action”的方式来访问，详细做法请参考《Struts 2.0的Action讲解》。另外，我由于希望在完成编辑或删除之后回到列表页，所以使用类型为redirect（重定向）的result。

下面是列表页面的代码：

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Book List</title>
<style type="text/css">
table {
border: 1px solid black;
border-collapse: collapse;
}

table thead tr th {
border: 1px solid black;
padding: 3px;
background-color: #cccccc;
}

table tbody tr td {
border: 1px solid black;
padding: 3px;
}
</style>
</head>
<body>
<h2>Book List</h2>
<s:form action="Remove" theme="simple">
<table cellspacing="0">
<thead>
<tr>
<th>Select</th>
<th>ISBN</th>
<th>Title</th>
<th>Price</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<s:iterator value="books">
<tr>
<td><input type="checkbox" name="isbns" value='<s:property value="isbn" />' /></td>
<td><s:property value="isbn" /></td>
<td><s:property value="title" /></td>
<td>$<s:property value="price" /></td>
<td>
<a href='<s:url action="Edit"><s:param name="isbn" value="isbn" /></s:url>'>
Edit
</a>
&nbsp;
<a href='<s:url action="Remove"><s:param name="isbn" value="isbn" /></s:url>'>
Delete
</a>
</td>
</tr>
</s:iterator>
</tbody>
</table>
<s:submit value="Remove" /><a href="Edit.jsp">Add Book</a>
</s:form>
</body>
</html>
清单5 WebContent\Book\List.jsp

以上代码，值得注意的是在<s:form>标签，我设置了theme属性为“simple”，这样可以取消其默认的表格布局。之前，有些朋友问我“如果不希望提交按钮放在右边应该怎样做？”，上述做汗是答案之一。当然，更佳的做法自定义一个theme，并将其设为默认应用到整个站点，如此一来就可以得到统一的站点风格。我会在以后的文章中会对此作详细的描述。

编辑或添加书籍的页面代码如下：

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Book</title>
</head>
<body>
<h2>
<s:if test="null == book">
Add Book
</s:if>
<s:else>
Edit Book
</s:else>
</h2>
<s:form action="Store" >
<s:textfield name="book.isbn" label="ISBN" />
<s:textfield name="book.title" label="Title" />
<s:textfield name="book.price" label="Price" />
<s:submit />
</s:form>
</body>
</html>
清单6 WebContent/Book/Edit.jsp

如果book为null，则表明该页面用于添加书籍，反之则为编辑页面。

为了方便大家运行示例，我把web.xml的代码也贴出来，如下：

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<display-name>Struts 2 Fileupload</display-name>

<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>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

</web-app>
清单7 WebContent/WEB-INF/web.xml

大功告成，下面发布运行应用程序，在浏览器中键入：http://localhost:8080/Struts2_CRUD/Book/List.action，出现如下图所示页面：

清单8 列表页面

点击“Add Book”，出现如下图所示页面：

清单9 添加书籍页面

后退回到列表页面，点击“Edit”，出现如下图所示页面：

清单10 编辑书籍页面

总结

本文只是粗略地了介绍Struts 2的CRUD实现方法，所以有很多功能没有实现，如国际化和数据校验等。大家可以在上面例子的基础将其完善，当作练习也不错。如果过程中有不明白之处，可以参考我早前的文章或给我发E-Mail：max.m.yuan@gmail.com。

posted on 2007-04-13 01:37 Max 阅读(2906) 评论(26) 编辑 收藏 引用 所属分类: Struts 2.0系列

评论:
# re: 在Struts 2中实现CRUD 2007-04-13 12:21 | Long
CRUD - Create, Retrieve, Update, and Delete ？
虽然没有必要为这些字眼来讨论什么 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-13 22:47 | Max
@Long
谢谢，你的提醒。 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-14 18:54 | furong
MAX
很感谢你这一系列的文章
不知道能不能传个关于struts2.0中树控件运用的例子?
非常感谢 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-15 12:10 | hushsky
MAX你好，谢谢你的教程，我按照你的这个例子写了一个可以正常运行，但是我加入对book.isbn的requiredstring校验(在BookAction包下加入BookAction-validation.xml，主要部分如下：)
<validators>
<field name="book.isbn">
<field-validator type="requiredstring">
<message> The id is requiredstring!!</message>
</field-validator>
</field>
</validators>
另将struts.xml中Store.action修改为
<action name="Store" class="..." method="store">
<result type="redirect">List.action</result>
<result name="input">edit.jsp</result>

在edit.jsp中可以正常对isbn校验
但是由于设置的是对BookAction校验，因此在执行List.action时也会进行检验并返回"input”，导致无法运行BookAction.list()方法
这种情况应该怎么进行校验，请指教，不胜感激
回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-15 14:34 | Max
最简单的方法就是将你的BookAction-validation.xml文件改名为“BookAction-Store-validation.xml”。这样validation的配置只对Store起作用。 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-16 14:48 | 千山鸟飞绝
@Max
请问“BookAction-Store-validation.xml”必须这样写吗？为什么需要这样写呢？

还有请问strust.xml必须写在根目录下吗？
回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-16 14:53 | hushsky
MAX你好，不好意思又麻烦你
本例中，我对book.isbn进行rquiredstring校验,一切正常，但是我在“BookAction-Store-validation.xml”文件中的的<field-validator >加入short-circuit属性实现短路校验（我从webwork里看的，不知道struts2.0里是否可行）。
代码如下：<field-validator type="requiredstring" short-circuit="true">
之后，程序可以运行，但是校验框架就不起作用了，即便在Edit.jsp里不输入isbn仍可以提交

能请教一下原因吗？ 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-16 19:19 | crayz
感谢,你的struts2系列文章
我是新手,这个例子我调试成功了
但我发现这个。不能输入中文。一出来就是乱码
我查了查资料。自己加了个spring 里包含的一个filter 解决了encoding
问题,添加是没问题了,但是编辑仍然有问题。一点就是空白
再添上去就是。新添加非。编辑了。不知道如果解决
可能我问的太初级。可struts2资源实在有限 没办法了麻烦你了
或者能提供些编码方面的参考资料呢。歇息 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-16 23:41 | Max
@千山鸟飞绝
1、请参考我的《在Struts 2.0中实现表单数据校验（Validation）》中“配置文件查找顺序”
2、不是，你可以通过以下设置改变struts.xml位置：
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>struts-default.xml,struts-plugin.xml,config/struts.xml</param-value>
</init-param>
</filter> 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-17 10:02 | Max
@hushsky
加入<field-validator type="requiredstring" short-circuit="true"> 之后，运行，例子会出现
org.xml.sax.SAXParseException: Attribute "short-circuit" must be declared for element type "field-validator".
但是，按照DTD的定义，short-ciruit是合法的属性。可能是Struts 2的BUG。 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-17 10:07 | Max
@crayz
乱码问题不是三言两语能够说得清楚的，因为这跟你的JVM区域（locale）、页面编码和IE的设置都有关。你可以GOOGLE一下，应该有很多不错的文章。
通常的做法是尽量将所有编码类型都认为utf-8。 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-17 10:59 | hermit
Max在线么？validate验证如何使一个.xml文件的验证只对action的一个方法有效？就是说我一个action有CRUD方法，我如何让在删除和查询时跳过验证？ 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-18 10:18 | hermit
谢谢，你指的是“BookAction-Store-validation.xml”对么？我昨天看到了。但是还是没有配置出来，后来我把Store改成了action访问的全路径（action！方法名）就可以了。但是还有个问题就是，增删改我想让他返回不同的页面。昨天调了一天。没搞定！好像这个1.x的版本也是有这个问题。 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-19 01:56 | Z
你好，我在调试你的这个例子时，为什么从struts.xml的result里跳到

List.action时会出现
The requested resource (/test/Book/List.action) is not available.
错误，

而直接用http://localhost:8080/test/Book/List.action访问时却可以呢？

麻烦解释一下，谢谢 回复 更多评论
# re: 在Struts 2中实现CRUD 2007-04-24 16:10 | ddd
很好的文章。。。谢谢。。
我这2天也才知道Struts2.0（汗），马上看了看。。
LZ的教程非常有帮组。。

另外， 我对apache struts官方网上的教程也作了一次。。。
http://struts.apache.org/2.x/docs/tutorials.html
那个Struts 2 + Spring 2 + JPA + AJAX 里提到的CRUD，
与LZ介绍的方法一致，不过，偶连Spring都没用过，所以这2天
就看了Spring的一些文章， 才有点明白了。。开始。。。

不过，对于web.xml中关于encoding的设置，还是有点迷惑。。
spring的filter没搞定， 不知道使用自己写的方法是否可行。。
自己定义servletFilter类，web.xml中记述servletFilter, <init-param>
回复 更多评论


Struts 2中的OGNL - Max On Java - BlogJava

Struts 2中的OGNL

本人是一个EL（Expression Language，以下译为表达式语言）的支持者。因为我对<% %>写法极为反感，忘记了在那本书上看到的一句话——“使用标志（Tag）的一个目的就是避免在JSP页面中出现过多的<%%>的语句，使页面与后台代码分离。”

表达式语言主要有以下几大好处：

  1. 避免(MyType) request.getAttribute()和myBean.getMyProperty()之类的语句，使页面更简洁；
  2. 支持运算符（如+-*/），比普通的标志具有更高的自由度和更强的功能；
  3. 简单明了地表达代码逻辑，使用代码更可读与便于维护。

Struts 2中的表达式语言

Struts 2支持以下几种表达式语言：

  1. OGNL（Object-Graph Navigation Language），可以方便地操作对象属性的开源表达式语言；
  2. JSTL（JSP Standard Tag Library），JSP 2.0集成的标准的表达式语言；
  3. Groovy，基于Java平台的动态语言，它具有时下比较流行的动态语言（如Python、Ruby和Smarttalk等）的一些起特性；
  4. Velocity，严格来说不是表达式语言，它是一种基于Java的模板匹配引擎，具说其性能要比JSP好。

Struts 2默认的表达式语言是OGNL，原因是它相对其它表达式语言具有下面几大优势：

  1. 支持对象方法调用，如xxx.doSomeSpecial()；
  2. 支持类静态的方法调用和值访问，表达式的格式为@[类全名（包括包路径）]@[方法名 | 值名]，例如：@java.lang.String@format('foo %s', 'bar')或@tutorial.MyConstant@APP_NAME；
  3. 支持赋值操作和表达式串联，如price=100, discount=0.8, calculatePrice()，这个表达式会返回80；
  4. 访问OGNL上下文（OGNL context）和ActionContext；
  5. 操作集合对象。

OGNL的用法

OGNL是通常要结合Struts 2的标志一起使用，如<s:property value="xx" />等。大家经常遇到的问题是#、%和$这三个符号的使用。下面我想通过例子讲述这个问题：

首先新建名为Struts2_OGNL的Web工程，配置开发环境。之前很多朋友在使用Struts 2的过程中都遇到乱码问题。当然乱码问题由来已久，而且涉及多方面的知识，所以并非三言两语可以说明白，而且互联网上也已经有很多这方便的文章，大家可以Google一下。不过，如果你在开发的过程，多注意一下，避免乱码问题也不难。乱码多数是由于编码与解码所使用的方式不同造成的，所以我建议大家将编码方式都设为“utf-8”，如<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>。另外，在配置web.xml时使用ActionContextCleanUp过滤器（Filter），如下面代码所示：

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<display-name>Struts 2 OGNL</display-name>

<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ActionContextCleanUp
</filter-class>
</filter>

<filter-mapping>
<filter-name>struts-cleanup</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<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>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

</web-app>
清单1 WebContent/WEB-INF/web.xml

“#”主要有三种用途：

  1. 访问OGNL上下文和Action上下文，#相当于ActionContext.getContext()；下表有几个ActionContext中有用的属性：
    名称 | 作用 | 例子
    parameters | 包含当前HTTP请求参数的Map | #parameters.id[0]作用相当于request.getParameter("id")
    request | 包含当前HttpServletRequest的属性（attribute)的Map | #request.userName相当于request.getAttribute("userName")
    session | 包含当前HttpSession的属性（attribute）的Map | #session.userName相当于session.getAttribute("userName")
    application | 包含当前应用的ServletContext的属性（attribute）的Map | #application.userName相当于application.getAttribute("userName")
    attr | 用于按request > session > application顺序访问其属性（attribute） | #attr.userName相当于按顺序在以上三个范围（scope）内读取userName属性，直到找到为止
  2. 用于过滤和投影（projecting)集合，如books.{?#this.price<100}；
  3. 构造Map，如#{'foo1':'bar1', 'foo2':'bar2'}。

下面让我们它们的具体写法，首先是Action类代码：

package tutorial.action;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.SessionAware;
import org.apache.struts2.util.ServletContextAware;

import tutorial.model.Book;

import com.opensymphony.xwork2.ActionSupport;

public class OgnlAction extends ActionSupport implements ServletRequestAware, SessionAware, ServletContextAware [image] {
private static final long serialVersionUID = 1L;

private HttpServletRequest request;
private Map<String, String> session;
private ServletContext application;
private List<Book> books;

public void setServletRequest(HttpServletRequest request) [image] {
this.request = request;
}

@SuppressWarnings("unchecked")
public void setSession(Map session) [image] {
this.session = session;
}

public void setServletContext(ServletContext application) [image] {
this.application = application;
}

public List<Book> getBooks() [image] {
return books;
}

@Override
public String execute() [image] {
request.setAttribute("userName", "Max From request");
session.put("userName", "Max From session");
application.setAttribute("userName", "Max From application");

books = new LinkedList<Book>();
books.add(new Book("978-0735619678", "Code Complete, Second Edition", 32.99));
books.add(new Book("978-0596007867", "The Art of Project Management", 35.96));
books.add(new Book("978-0201633610", "Design Patterns: Elements of Reusable Object-Oriented Software", 43.19));
books.add(new Book("978-0596527341", "Information Architecture for the World Wide Web: Designing Large-Scale Web Sites", 25.19));
books.add(new Book("978-0735605350", "Software Estimation: Demystifying the Black Art", 25.19));

return SUCCESS;
}
}
清单2 src/tutorial/action/OgnlAction.java

以上代码分别在request、session和application的范围内添加“userName”属性，然后再在JSP页面使用OGNL将其取回。我还创建了Book对象的列表用于演示“用于过滤和投影（projecting)集合”的功能，至于Book的代码大家可以在我前一文章《在Struts 2中实现CRUD》看到。

下面是Ognl.jsp的代码，内容如下：

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Struts OGNL Demo</title>
</head>
<body>
<h3>访问OGNL上下文和Action上下文</h3>
<p>parameters: <s:property value="#parameters.userName" /></p>
<p>request.userName: <s:property value="#request.userName" /></p>
<p>session.userName: <s:property value="#session.userName" /></p>
<p>application.userName: <s:property value="#application.userName" /></p>
<p>attr.userName: <s:property value="#attr.userName" /></p>
<hr />
<h3>用于过滤和投影（projecting)集合</h3>
<p>Books more than $35</p>
<ul>
<s:iterator value="books.{?#this.price > 35}">
<li><s:property value="title" /> - $<s:property value="price" /></li>
</s:iterator>
</ul>
<p>The price of "Code Complete, Second Edition" is: <s:property value="books.{?#this.title=='Code Complete, Second Edition'}.{price}[0]"/></p>
<hr />
<h3>构造Map</h3>
<s:set name="foobar" value="#{'foo1':'bar1', 'foo2':'bar2'}" />
<p>The value of key "foo1" is <s:property value="#foobar['foo1']" /></p>
</body>
</html>
清单3 WebContent/Ognl.jsp

以上代码值得注意的是“<s:property value="books.{?#this.title=='Code Complete, Second Edition'}.{price}[0]"/>”，因为“books.{?#this.title=='Code Complete, Second Edition'}.{price}”返回的值是集合类型，所以要用“[索引]”来访问其值。

最后是Struts 2的配置文件struts.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>
<constant name="struts.devMode" value="true" />
<package name="Struts2_OGNL_DEMO" extends="struts-default">
<action name="Ognl" class="tutorial.action.OgnlAction">
<result>/Ognl.jsp</result>
</action>
</package>
</struts>
清单4 src/struts.xml

发布运行应用程序，结果如下所示：

点击查看放大图片
清单5 示例运行结果1

“%”符号的用途是在标志的属性为字符串类型时，计算OGNL表达式的值。例如在Ognl.jsp中加入以下代码：

<hr />
<h3>%的用途</h3>
<p><s:url value="#foobar['foo1']" /></p>
<p><s:url value="%{#foobar['foo1']}" /></p>
清单6 演示%用途的代码片段

刷新页面，结果如下所示：

清单7 示例运行结果2

“$”有两个主要的用途

  1. 用于在国际化资源文件中，引用OGNL表达式，例子请参考《在Struts 2.0中国际化(i18n)您的应用程序》
  2. 在Struts 2配置文件中，引用OGNL表达式，如
    <action name="AddPhoto" class="addPhoto">
    <interceptor-ref name="fileUploadStack" />
    <result type="redirect">ListPhotos.action?albumId=${albumId}</result>
    </action>
    清单8 演示$用途的代码片段

总结

OGNL是一种功能很大的表达式语言，熟悉它可以使我们的开发变得更快捷。

posted on 2007-04-28 19:43 Max 阅读(1856) 评论(7) 编辑 收藏 引用 所属分类: Struts 2.0系列


Strus 2的新表单标志的使用 - Max On Java - BlogJava

Strus 2的新表单标志的使用

Struts 2为大家提供了不少常用的很酷的表单标志，简化了我们程序员的工作。不过，由于这些都是新标志，大家可能在使用上还存在不少疑问。本文将就朋友们的回复、留言或Email上的问题，分别对这些酷标志进行讲述。

表单标志使用小技巧

Struts 2的表单标志在输出（render）HTML时，使用了模板的概念，增加了复杂性（因为它不像Struts 1.x的表单标志，它通常都是一个标志对应HTML的一个元素），因此大家在使用时，需要一些技巧：

  1. Struts 2的UI标志的表单标志默认是以表格布局，按钮是右对齐的。如果你不喜欢此风格，你可以简单地将<s:form />标志的“theme”属性设为“simple”，然后用以往的做法自已布局表单元素（注意：此法有利有弊，弊就是当你将“theme”属性设为“simple”时，表单标志以最简单方式输出HTML，所以你可能失去一些默认输出提供的便利，如：友好的错误信息的显示，或客户端的表单验证等）。当然更好的做法是通过CSS或自定义主题（theme）然后应用到整个应用程序，这样可以获得一致的页面风格，加强用户体验（我会在以后的文章对此进行讲解）；
  2. 当你在页面上加入某些标志（如：<s:doubleselect />等）时，应该通过action来访问页面，而不是通过*.jsp的URL直接访问。

下面我将分别对这些标志进行讲述：

1、<s:checkboxlist />

大家对<s:checkboxlist />的最大的疑问可能是：“如何在默认情况下，选中某些checkbox？”

答案其实很简单，只需要将其“value”属性设为你的要选中的值，如以代码所示：

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Struts 2 Cool Tags - &lt;s:checkboxlist/ &gt;</title>
<s:head />
</head>
<body>
<h2>&lt;s:checkboxlist/&gt;</h2>
<s:form action="Store" >
<s:checkboxlist name="skills1"
label="Skills 1"
list="{ 'Java', '.Net', 'RoR', 'PHP' }"
value="{ 'Java', '.Net' }" />
<s:checkboxlist name="skills2"
label="Skills 2"
list="#{ 1:'Java', 2: '.Net', 3: 'RoR', 4: 'PHP' }"
listKey="key"
listValue="value"
value="{ 1, 2, 3 }"/>
</s:form>
</body>
</html>
清单1 WebContent/checkboxlist.jsp

分布运行应用程序，在浏览器中键入：http://localhost:8080/Struts2_CoolTags/checkboxlist.jsp，出现如下图所示页面：

清单2 checkboxlist.jsp页面

2、<s:doubleselect />

大家看Struts 2的showcase的例子，<s:doubleselect />的用法如下所示：

    <s:doubleselect
tooltip="Choose Your State"
label="State"
name="region" list="{'North',
'South'}"
value="'South'"
doubleValue="'Florida'"
doubleList="top == 'North' ? {'Oregon',
'Washington'} : {'Texas', 'Florida'}"
doubleName="state"
headerKey="-1"
headerValue="---------- Please Select
----------"
emptyOption="true" />
清单3 Showcase中<s:doubleselect />

很多朋友问：“上面的‘list’属性只有两个值，如果我有三个或更多的值，‘doublelist’属性应该如何设定呢？”

我建议的做法是先定义一个Map类型的对象，键为“list”的集合，值则为“doubleList”的集合，然后“doubleList”的OGNL写成“#myMap[top]”，如以下代码所示：

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Struts 2 Cool Tags - &lt;s:doubeselect/ &gt;</title>
<s:head />
</head>
<body>
<h2>&lt;s:doubleselect/&gt;</h2>
<s:form action="Store" >
<s:set name="foobar"
value="#{'Java': {'Spring', 'Hibernate', 'Struts 2'}, '.Net': {'Linq', ' ASP.NET 2.0'}, 'Database': {'Oracle', 'SQL Server', 'DB2', 'MySQL'}}" />
<s:doubleselect list="#foobar.keySet()"
doubleName="technology"
doubleList="#foobar[top]"
label="Technology" />
</s:form>
</body>
</html>
清单4 WebContent/doubleselect.jsp

分布运行应用程序，在浏览器中键入：http://localhost:8080/Struts2_CoolTags/doubleselect.action，出现如下图所示页面：

清单5 doubleselect.jsp页面

3、<s: token />

这个标志可能大家不常用，不过本人认为它还是挺有用的。在使用Struts 1.x时，因为跳转通常是用Forward（而不是Redirect）实现的，所以当用户完成请求后，按“F5”刷新页面时，就会重新提交上次的请求，这样经常会出错。要解决这个问题，<s:token />可以帮你忙。

实现原理

在页面加载时，<s: token />产生一个GUID（Globally Unique Identifier，全局唯一标识符）值的隐藏输入框如：

<input type="hidden" name="struts.token.name" value="struts.token"/>
<input type="hidden" name="struts.token" value="BXPNNDG6BB11ZXHPI4E106CZ5K7VNMHR"/>
清单6 <s:token />的HTML输出

同时，将GUID放到会话（session）中；在执行action之前，“token”拦截器将会话token与请求token比较，如果两者相同，则将会话中的token删除并往下执行，否则向actionErrors加入错误信息。如此一来，如果用户通过某种手段提交了两次相同的请求，两个token就会不同。

具体实现

首先看一下Action的代码：

package tutorial;

import com.opensymphony.xwork2.ActionSupport;

public class CoolTagAction extends ActionSupport [image] {
private static final long serialVersionUID = 6820659617470261780L;

private String message;

public String getMessage() [image] {
return message;
}

public void setMessage(String message) [image] {
this.message = message;
}

@Override
public String execute() [image] {
System.out.println("Executing action, your message is " + message);
return SUCCESS;
}
}
清单7 src/tutorial/CoolTagAction.java

以上代码一目了然，再看看JSP的写法：

%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Struts 2 Cool Tags - &lt;s:token/ &gt;</title>
<s:head />
</head>
<body>
<h2>&lt;s:token/&gt;</h2>
<s:actionerror />
<s:form action="Token" >
<s:textfield name="message" label="Message" />
<s:token />
<s:submit />
</s:form>
</body>
</html>
清单8 WebContent/token.jsp

JSP也很简单，就是加入<s:token />标志。接下来是Actoin配置的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="Struts2_COOL_TAGS_DEMO" extends="struts-default">
<action name="Token" class="tutorial.CoolTagAction">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="token" />
<result name="invalid.token">/token.jsp</result>
<result>/token.jsp</result>
</action>
<action name="*">
<result>/{1}.jsp</result>
</action>
</package>
</struts>
清单9 src/struts.xml

以上XML片段值注意的是加入了“token”拦截器和“invalid.token”结果，因为“token”拦截器在会话token与请求token不一致时，将会直接返回“invalid.token”结果。

发布运行应用程序，在浏览器中键入：http://localhost:8080/Struts2_CoolTags/token.jsp，出现如下图所示页面：

清单10 正常显示的token.jsp页面

随便填点东西并提交页面，一切正常返回以上页面，然后按“F5”刷新页面，在弹出的对话框中点击“Retry”，出现如下图所示页面：

重复提交出错显示
清单11 重复提交出错显示

4、<s:datetimepicker />、<s:optiontransferselect />和<s:updownselect />

这几个标志的使用相对简单，所以我想小举一例即可，以下是JSP的代码：

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Struts 2 Cool Tags - Others</title>
<s:head />
</head>
<body>
<h2>Others</h2>
<s:form action="Store" >
<s:datetimepicker name="birthday" label="Birthday" />
<s:updownselect
label = "Favourite Countries"
list="#{'england':'England', 'america':'America', 'germany':'Germany'}"
name="prioritisedFavouriteCountries"
headerKey="-1"
headerValue="--- Please Order Them Accordingly ---"
emptyOption="true" />
<s:optiontransferselect
label="Favourite Cartoons Characters"
name="leftSideCartoonCharacters"
leftTitle="Left Title"
rightTitle="Right Title"
list="{'Popeye', 'He-Man', 'Spiderman'}"
multiple="true"
headerKey="headerKey"
headerValue="--- Please Select ---"
emptyOption="true"
doubleList="{'Superman', 'Mickey Mouse', 'Donald Duck'}"
doubleName="rightSideCartoonCharacters"
doubleHeaderKey="doubleHeaderKey"
doubleHeaderValue="--- Please Select ---"
doubleEmptyOption="true"
doubleMultiple="true" />
</s:form>
</body>
</html>
清单12 WebContent\others.jsp页面

发布运行应用程序，在浏览器中键入：http://localhost:8080/Struts2_CoolTags/others.jsp，出现如下图所示页面：

点击查看大图
清单13 其它表单标志页面

总结

Struts 2在标志上的确比Struts 1.x丰富了许多，同时模板机制也给程序员带来不少方便（如果你不太喜欢个性化的风格）。另外，Struts 2还有一些AJAX（如<s: autocompleter />等）的标志和非表单的UI标志（如<s: tree />等），我会在以后的文章中讲述其使用。

posted on 2007-05-18 19:34 Max 阅读(278) 评论(3) 编辑 收藏 引用 所属分类: Struts 2.0系列


Struts 2与AJAX（第一部分） - Max On Java - BlogJava

Max On Java
The value of hands-on experience as compared to book learning is smaller in software development than in many other fields.
BlogJava 首页 新随笔 联系 聚合 [image] 管理
随笔-17 评论-532 文章-0 trackbacks-0
Struts 2与AJAX（第一部分）

在当今——Web 2.0概念铺天盖地的Internet环境下，简易的AJAX集成对于一个成功的WEB框架来说是不可或缺的。因此，Struts 2其中的一个重要的功能（Feature）就是“First-class AJAX support - Add interactivity and flexibility with AJAX tags that look and feel just like standard Struts tags（大意：一流的AJAX支持——通过AJAX标志增加互动性和灵活性，而且使用这些AJAX标志与普通的Struts标志同样简单）”。

实现原理

基于不重新发明轮子的原则，Struts 2并没有开发新的AJAX框架，而是使用时下Java EE平台中比较流行的AJAX框架——Dojo和DWR。

最近在Musachy Barroso等同志的无私奉献下，开发了Struts 2的JSON插件（Plugin），极大地方便了我们输出JSON结果（Result）。

JSON插件（Plugin）

在Struts 2的showcase中的AJAX部分，JSON的结果输出是通过Freemaker模板实现。这种方法在简易性和灵活性上都比不上JSON插件，所以JSON插件值得向大家五星推荐。

下面让我们看一个JSON插件的例子。

首先到以下网址http://code.google.com/p/jsonplugin/downloads/list下载JSON插件的JAR包，并将其加入你的WebContent\WEB-INF\lib下。

接下是本例子的Action代码：

package tutorial;

import java.util.ArrayList;
import java.util.List;

import com.googlecode.jsonplugin.annotations.JSON;
import com.opensymphony.xwork2.ActionSupport;

public class JsonPluginAction extends ActionSupport [image] {
private static final long serialVersionUID = -6784977600668791997L;

private int bookId;
private String title;
private double price;
private List<String> comments;
private transient String secret1;
private String secret2;

@JSON(name="ISBN")
public int getBookId() [image] {
return bookId;
}

public void setBookId(int bookId) [image] {
this.bookId = bookId;
}

public List<String> getComments() [image] {
return comments;
}

public void setComments(List<String> comments) [image] {
this.comments = comments;
}

public double getPrice() [image] {
return price;
}

public void setPrice(double price) [image] {
this.price = price;
}

public String getTitle() [image] {
return title;
}

public void setTitle(String title) [image] {
this.title = title;
}

@Override
public String execute() [image] {
bookId = 15645912;
title = "Max On Java";
price = 0.9999d;
comments = new ArrayList<String>(3);
comments.add("It's no bad!");
comments.add("WOW!");
comments.add("No comment!");
secret1 = "You can't see me!";
secret2 = "I am invisible!";
return SUCCESS;
}
}
清单1 src/tutorial/JsonPluginAction.java

以上代码值得注意的是，通过@JSON的JAVA注释（Annotation），我们可以改变JSON结果的属性名称，另外带有transient修饰符与没有Getter方法的字段（field）都不会被串行化为JSON。

然后，我们来配置一下此Action，代码如下：

<?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="Struts2_AJAX_DEMO" extends="json-default">
<action name="JsonPlugin" class="tutorial.JsonPluginAction">
<result type="json" />
</action>
</package>
</struts>
清单2 src/struts.xml

上面配置文件的“package”元素和以往不同的是，它扩展了“json-default”而不是“struts-default”。“json-default”是在jsonplugin-0.11.jar包里的struts-plugin.xml中定义的。该文件同时定义了“json”的结果类型，有兴趣的朋友可以打开此文件看看。

发布运行应用程序，在浏览器中键入：http://localhost:8080/Struts2_Ajax/JsonPlugin.action，出现下载文件对话框，原因是JSON插件将HTTP响应（Response）的MIME类型设为“application/json”。把文件下载下来，用记事本打开，内容如下：

{"ISBN":15645912,"comments":["It's no bad!","WOW!","No comment!"],"price":0.9999,"title":"Max On Java"}
清单3 例子1输出的JSON串

当然这还不是一个完整的AJAX的例子，下面让我们写一个HTML文件将其完成，HTML代码如下：

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>JSON Plugin</title>
<script type="text/javascript">
var bXmlHttpSupport = (typeof XMLHttpRequest != "undefined" || window.ActiveXObject);

if (typeof XMLHttpRequest == "undefined" && window.ActiveXObject) {
function XMLHttpRequest() {
var arrSignatures = ["MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0",
"MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP",
"Microsoft.XMLHTTP"];

for (var i=0; i < arrSignatures.length; i++) {
try {
var oRequest = new ActiveXObject(arrSignatures[i]);
return oRequest;
} catch (oError) { /*ignore*/ }
}

throw new Error("MSXML is not installed on your system.");
}
}

function retrieveBook() {
if(bXmlHttpSupport) {
var sUrl = 'JsonPlugin.action';
var oRequest = new XMLHttpRequest();
oRequest.onreadystatechange = function() {
if(oRequest.readyState == 4) {
var oBook = eval('(' + oRequest.responseText + ')');
var bookHolder = document.getElementById('bookHolder');
var sBook = '<p><b>ISBN: </b>' + oBook.ISBN + '</p>';
sBook += ('<p><b>Title: </b>' + oBook.title + '</p>');
sBook += ('<p><b>Price: </b>$' + oBook.price + '</p>');
sBook += ('<b><i>Comments: </i></b><hr/>');
for(i = 0; i < oBook.comments.length; i++) {
sBook += ('<p><b>#' + (i + 1) + ' </b>' + oBook.comments[i] + '</p>');
}
bookHolder.innerHTML = sBook;
}
};
oRequest.open('POST', sUrl);
oRequest.send(null);
}
}
</script>
</head>
<body>
<input type="button" value="Retrieve Book" onclick="retrieveBook()" />
<div id="bookHolder"></div>
</body>
</html>
清单4 WebContent/JsonPlugin.html

以上代码中，我没有使用任何的AJAX的Javascript包，而是参考《Professional Javascript For Web Developer》手工创建XHR（XMLHttpRequest），并在XHR完成后使用eval()方法将JSON字符串变为JSON对象。需要注意的是，要调用eval函数时，必须使用“(”和“)”将JSON字符串括起来，否则会出错的。

打开http://localhost:8080/Struts2_Ajax/JsonPlugin.html，点击“Retrieve Book”按钮，页面如下图所示：

图1 JsonPlugin.html页面输出

Struts 2与Dojo

Dojo是开源Javascript工具包，它引了Widget的概念，方便了Javascript面向对象编程（OOP），改进Javascript的事件模型。在此我不打算对此进行深入的讲解，有兴趣的朋友的可以找网上找一些关于Dojo的资料学习。

Struts 2基于Dojo编写一些AJAX标志（在Dojo中称为Widget），要使用这些标志的AJAX功能，需要将标志的“theme”属性设为“ajax”。同时，亦需要将加入在<head>与</head>之间加入<s:head theme="ajax" />。当使用这些标志的AJAX功能，有些属性可能会经常用到，所以我会对这些属性稍作解释。

名称 | 描述
href | XHR（XMLHttpRequest）请求的地址
listenTopics | 监听的Dojo话题（Topic）以触发自身，如可以在可以通过发布（Publish）相应的话题，通知<s:autocompleter />重新加载其备选项（Options）
notifyTopics | 完成远程调用后，发出通知，触发相应的Javascript函数或Dojo Widget
formId | 需要提交到服务器的表单的ID
formFilter | 过滤表单字段的Javascript函数名称
indicator | 在XHR处理过程中，包含用户提示的信息的HTML元素的ID，如图片或DIV等
表1 常用的AJAX标志属性

这些标志包括：<s:a />、<s: submit />、<s:autocompleter />和<s:tree />等，下面我将分别讲解。

1、<s:a />和<s:submit />

这两个标志方便了我们的调用XHR实现AJAX，所以上面的HTML如果使用了这两标志将会变得更简单，因为我们不用再去理会繁锁的XHR创建和设定的工作。下面是示例代码：

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>JSON Plugin</title>
<s:head theme="ajax" />
<script type="text/javascript">
dojo.addOnLoad(function() {
dojo.event.topic.subscribe('retrieveBook', this, function(data, type, e){
if(type == 'load') {
showBook(data);
} else if(type == 'error') {
alert('Can not retrieve the book');
}
});
});

function showBook(strBook) {
var oBook = eval('(' + strBook + ')');
var bookHolder = document.getElementById('bookHolder');
var sBook = '<p><b>ISBN: </b>' + oBook.ISBN + '</p>';
sBook += ('<p><b>Title: </b>' + oBook.title + '</p>');
sBook += ('<p><b>Price: </b>$' + oBook.price + '</p>');
sBook += ('<b><i>Comments: </i></b><hr/>');
for(i = 0; i < oBook.comments.length; i++) {
sBook += ('<p><b>#' + (i + 1) + ' </b>' + oBook.comments[i] + '</p>');
}
bookHolder.innerHTML = sBook;
}
</script>
</head>
<body>
<s:url id="bookUrl" value="/JsonPlugin.action" />
<s:submit href="%{bookUrl}" theme="ajax" indicator="indicator"
value="Retrieve Book" align="left" notifyTopics="retrieveBook" />
<s:a theme="ajax" href="%{bookUrl}" indicator="indicator"
notifyTopics="retrieveBook">Retrieve Book</s:a>
<img id="indicator"
src="${pageContext.request.contextPath}/images/indicator.gif"
alt="Loading [image] " style="display:none" />
<div id="bookHolder"></div>
</body>
</html>
清单5 WebContent/LinkButton.jsp

可能上述代码还不够简洁，因为我将HTML格式化的工作都放在Javascript中完成。但如果你的XHR返回的是HTML片段，你可以简单地将<s:a />或<s:submit />的“targets”属性设为“bookHolder”即可，详情大家可以参考Struts 2 Showcase。至于返回HTML片段，可以通过Action + Freemaker完成。

2、<s:autocompleter />

Autocomplete是比较经典的AJAX应用，虽然谷歌已经停止使用这一功能，但就Autocompleter自身而言的确是很酷的。下面是一个<s:autocompleter />的例子。

首先，我要伪造一些字符串数据，代码如下：

package tutorial;

import java.util.ArrayList;
import java.util.List;

public final class Datas [image] {
public static final List<String> NAMES;
static [image] {
NAMES = new ArrayList<String>();
NAMES.add("Alabama");
NAMES.add("Alaska");
NAMES.add("American Samoa");
NAMES.add("Arizona");
NAMES.add("Arkansas");
NAMES.add("Armed Forces Europe");
NAMES.add("Armed Forces Pacific");
NAMES.add("Armed Forces the Americas");
NAMES.add("California");
NAMES.add("Colorado");
NAMES.add("Connecticut");
NAMES.add("Delaware");
NAMES.add("District of Columbia");
NAMES.add("Federated States of Micronesia");
NAMES.add("Florida");
NAMES.add("Georgia");
NAMES.add("Guam");
NAMES.add("Hawaii");
NAMES.add("Idaho");
NAMES.add("Illinois");
NAMES.add("Indiana");
NAMES.add("Iowa");
NAMES.add("Kansas");
NAMES.add("Kentucky");
NAMES.add("Louisiana");
NAMES.add("Maine");
NAMES.add("Marshall Islands");
NAMES.add("Maryland");
NAMES.add("Massachusetts");
NAMES.add("Michigan");
NAMES.add("Minnesota");
NAMES.add("Mississippi");
NAMES.add("Missouri");
NAMES.add("Montana");
NAMES.add("Nebraska");
NAMES.add("Nevada");
NAMES.add("New Hampshire");
NAMES.add("New Jersey");
NAMES.add("New Mexico");
NAMES.add("New York");
NAMES.add("North Carolina");
NAMES.add("North Dakota");
NAMES.add("Northern Mariana Islands");
NAMES.add("Ohio");
NAMES.add("Oklahoma");
NAMES.add("Oregon");
NAMES.add("Pennsylvania");
NAMES.add("Puerto Rico");
NAMES.add("Rhode Island");
NAMES.add("South Carolina");
NAMES.add("South Dakota");
NAMES.add("Tennessee");
NAMES.add("Texas");
NAMES.add("Utah");
NAMES.add("Vermont");
NAMES.add("Virgin Islands, U.S.");
NAMES.add("Virginia");
NAMES.add("Washington");
NAMES.add("West Virginia");
NAMES.add("Wisconsin");
NAMES.add("Wyoming");
}
}
清单6 src/tutorial/Datas.java

然后是用于获取和过滤数据的Action，代码如下：

package tutorial;

import java.util.ArrayList;
import java.util.List;

import com.opensymphony.xwork2.ActionSupport;

public class AutocompleterAction extends ActionSupport [image] {
private static final long serialVersionUID = -8201401726773589361L;

private List<String[]> names;
private String start;

public void setStart(String start) [image] {
this.start = start;
}

public List<String[]> getNames() [image] {
return names;
}

@Override
public String execute() [image] {
names = new ArrayList<String[]>();
if(start == null || "".equals(start.trim())) [image] {
start = "a";
}
for(String s : Datas.NAMES) [image] {
if(s.toLowerCase().startsWith(start.toLowerCase())) [image] {
names.add(new String[] [image] { s, s });
}
}
return SUCCESS;
}
}
清单7 src/tutorial/AutocmpleterAction.java

上述Action会以JSON的形式返回以start开头的Datas.NAMES的中字符串，以下是此Action的配置：

<action name="Autocompleter" class="tutorial.AutocompleterAction">
<result type="json">
<param name="root">names</param>
</result>
</action>
清单8 Autocompleter Action的配置代码片段

在JSON类型结果的参数中加入“root”参数可以设定输出JSON结果的根，以上述情况为例，如果没有“root”参数，输出将为“{ "names": [ ["xxx", "xxx"]...] }”，加了之后变就会成“[ ["xxx", "xxx"]...] ”。接下来，让我们看看页面的代码：

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Struts 2 AJAX - Autocompleter</title>
<s:head theme="ajax" />
</head>
<body>
<h2>
Autocompleter
</h2>
<s:form action="autocompleterForm">
<s:textfield label="abc" name="abc" />
<tr>
<td class="tdLabel">
<label class="label">
No AJAX Autocompleter:
</label>
</td>
<td>
<s:autocompleter theme="simple" name="user"
list="@tutorial.Datas@NAMES" />
</td>
</tr>
<tr>
<td class="tdLabel">
<label class="label">
AJAX Autocompleter:
</label>
</td>
<td>
<s:url id="dataUrl" value="/Autocompleter.action" />
<s:autocompleter theme="ajax" name="start" href="%{dataUrl}"
loadOnTextChange="true" loadMinimumCount="1" indicator="indicator"
autoComplete="false" showDownArrow="false" />
<img id="indicator"
src="${pageContext.request.contextPath}/images/indicator.gif"
alt="Loading [image] " style="display:none" />
</td>
</tr>
</s:form>
</body>
</html>
清单9 WebContent/Autocompleter.jsp

上述页面包含两个<s:autocompleter />标志，前者使用“simple”模板，所以不具有AJAX功能，它的数据将以HTML方式输出到最终页面里；而后者则使用了“ajax”模板，每当输入框的值发生改变时，它都向URL“/Autocompleter.action”发送请求，Action根据请求中的start参数的值，返回相当的JSON，在请求完成后页面通过回调函数改变输入框的下拉提示，效果如下图所示：

图2 Autocompleter.jsp页面输出

3、<s:tree />

树是是比较常用的数据结构，因为它可以很好地体现真实世界中对象之间的关系。<s:tree />的使用也相对简单，但需要说明的是——Struts 2.0.6 GA版本的<s:tree />是有BUG的，大家可以点击这个链接https://issues.apache.org/struts/browse/WW-1813了解详细的情况。这个BUG主要是在<s:tree />的通过“treeCollapsedTopic”、“treeExpandedTopic”和“treeSelectedTopic”设定的话题（Topic）都没有起作用，上述链接相应给出了解决方法，但我认为该方法太麻烦（需要自己重新编译和打包Struts 2），所以下面的例子，我将另辟徯径，请参考以下代码。

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Struts 2 AJAX - Tree</title>
<s:head theme="ajax" debug="true" />
<script type="text/javascript">
function treeNodeSelected(arg) {
alert(arg.source.title + ' selected');
}
dojo.addOnLoad(function() {
var s = dojo.widget.byId('parentId').selector;
dojo.event.connect(s, 'select', 'treeNodeSelected');
});
</script>
</head>
<body>
<h2>
Tree
</h2>
<div style="float:left; margin-right: 50px;">
<s:tree label="parent" id="parentId" theme="ajax"
templateCssPath="/struts/tree.css" showRootGrid="true"
showGrid="true">
<s:treenode theme="ajax" label="child1" id="child1Id">
<s:treenode theme="ajax" label="grandchild1" id="grandchild1Id" />
<s:treenode theme="ajax" label="grandchild2" id="grandchild2Id" />
<s:treenode theme="ajax" label="grandchild3" id="grandchild3Id" />
</s:treenode>
<s:treenode theme="ajax" label="child2" id="child2Id" />
<s:treenode theme="ajax" label="child3" id="child3Id" />
<s:treenode theme="ajax" label="child4" id="child4Id" />
<s:treenode theme="ajax" label="child5" id="child5Id">
<s:treenode theme="ajax" label="gChild1" id="gChild1Id" />
<s:treenode theme="ajax" label="gChild2" id="gChild2Id" />
</s:treenode>
</s:tree>
</div>
</body>
</html>
清单10 WebContent/Tree.jsp

因为Dojo的树控件，即使在没有设定“selector”情况下，也会自动生成一个默认的Selector，所以只要将其事件绑定到特定的事件处理函数即可。

打开http://localhost:8080/Struts2_Ajax/Tree.jsp，点击任一树节点，页面如下图所示：

图3 Tree.jsp页面输出

总结

我原本打算用一篇文章写完这个“Struts 2与AJAX”。不过在写的过程中，发现内容越来越多。如果勉强写成一篇，朋友们读起来也会很麻烦，所以我决定分开几部分，本文为第一部分。

另外，之前有的朋友建议我建一个Google的讨论组，方便大家讨论问题。我觉得这个提议非常好，一直以来都是“一人写，大家留言”，这种相对单向的方式不免有所欠缺，而且本人所知也有限，开个讨论组大家可以相互讨论，共同进步。

因为Struts2已经被创建，所以申请了“struts2cn”，有兴趣的朋友，欢迎加入。

posted on 2007-06-12 18:31 Max 阅读(98) 评论(2) 编辑 收藏 引用 所属分类: Struts 2.0系列

评论:
# re: Struts 2与AJAX（第一部分）[未登录] 2007-06-12 19:53 | yy
我一直在关注你的blog，在这里学到了不少关于struts2的东西，可否写一下关于struts2和ext集成的例子，谢谢！ 回复 更多评论
# re: Struts 2与AJAX（第一部分） 2007-06-12 20:34 | 海边沫沫
好文章，顶 回复 更多评论
刷新评论列表

标题 | 请输入标题
姓名 | 请输入你的姓名
主页
请输入验证码
验证码 *
内容(提交失败后,可以通过“恢复上次提交”恢复刚刚提交的内容) 请输入评论内容
登录 使用高级评论 新用户注册 返回页首 恢复上次提交 清除cookie
[使用Ctrl+Enter键可以直接提交]
相关文章:
  • Struts 2与AJAX（第一部分）
  • Strus 2的新表单标志的使用
  • Struts 2中的OGNL
  • 在Struts 2中实现CRUD
  • 在Struts 2中实现文件上传
  • 在Struts 2中实现IoC
  • Struts 2的基石——拦截器（Interceptor）
  • 在Struts 2.0中实现表单数据校验（Validation）
  • 转换器（Converter）——Struts 2.0中的魔术师
  • 在Struts 2.0中国际化(i18n)您的应用程序

许多朋友可能对于Struts 2.0与WebWork关系还搞不清楚。下面是Apache的Struts官网中的一段话：
Apache Struts 2 was originally known as WebWork 2. After working independently for several years, the WebWork and Struts communities joined forces to create Struts2.

原文大意为： Apache Struts 2即是之前大家所熟知的WebWork 2。在经历了几年的各自发展后，WebWork和Struts社区决定合二为一，也即是Struts 2。

< 2007年6月 >
日 | 一 | 二 | 三 | 四 | 五 | 六
27 | 28 | 29 | 30 | 31 | 1 | 2
3 | 4 | 5 | 6 | 7 | 8 | 9
10 | 11 | 12 | 13 | 14 | 15 | 16
17 | 18 | 19 | 20 | 21 | 22 | 23
24 | 25 | 26 | 27 | 28 | 29 | 30
1 | 2 | 3 | 4 | 5 | 6 | 7

常用链接

  • 我的随笔
  • 我的评论
  • 我参与的随笔

留言簿(34)

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔分类

  • Struts 2.0系列(13)
  • 方法与技巧(Tips & tricks)(4)

随笔档案

  • 2007年6月 (1)
  • 2007年5月 (1)
  • 2007年4月 (2)
  • 2007年3月 (2)
  • 2007年1月 (2)
  • 2006年12月 (3)
  • 2006年11月 (3)
  • 2006年10月 (3)

搜索

最新评论

  • 1. re: Struts 2与AJAX（第一部分）
  • 好文章，顶
  • --海边沫沫
  • 2. re: Struts 2与AJAX（第一部分）[未登录]
  • 我一直在关注你的blog，在这里学到了不少关于struts2的东西，可否写一下关于struts2和ext集成的例子，谢谢！
  • --yy
  • 3. re: 为Struts 2.0做好准备
  • 问一个相对无关的问题：就是你的eclipse用的哪些插件啊？？？
    在哪里有下？？？？

    官网我去过N次了，不过没找到。。。。。
  • --EasefulMaple
  • 4. re: 在Struts 2.0中实现表单数据校验（Validation）
  • 评论内容较长,点击标题查看
  • --wldandan
  • 5. attempt to create saveOrUpdate event with null entity
  • 这是什么错误?
  • --sapphire

阅读排行榜

  • 1. 为Struts 2.0做好准备(13931)
  • 2. Struts 2.0的Action讲解(8352)
  • 3. 常用的Struts 2.0的标志（Tag）介绍(7907)
  • 4. 转换器（Converter）——Struts 2.0中的魔术师(5754)
  • 5. 在Struts 2.0中实现表单数据校验（Validation）(5106)

评论排行榜

  • 1. 为Struts 2.0做好准备(119)
  • 2. 在Struts 2中实现IoC(57)
  • 3. 常用的Struts 2.0的标志（Tag）介绍(56)
  • 4. Struts 2.0的Action讲解(49)
  • 5. 在Struts 2.0中国际化(i18n)您的应用程序(43)
Powered by: 博客园 模板提供：沪江博客 Copyright ©2007 Max