Struts2的出现在Web2.0纷争的年代,以Ajax为代表的富客户端(Rich Client)应用正唱着Web2.0的主角。虽然Struts2本身对Ajax的应用也提供了自己的Ajax标签,但是这种比较牵强的支持也是赶鸭子上架,不是Struts本身的特长。
这里,就Struts对JSON支持的技术特点,来构建一个Rich Client应用,UI层使用的是YUI工具包,详情参考Yahoo的YUI网站。http://developer.yahoo.com/yui/。
本文中所涉及的Web应用的大致结构图如下:
先一睹为快,该应用的实际运行界面如下:
页面功能大致是,用户加入一个New Test User和New Message,然后点击按钮Add Now,页面无刷新添加新加入的消息。
首先,建立一个Struts的Action类StrutsTestAction,代码如下:
package com.tail.test.actions;
import java.util.ArrayList;
import java.util.List;
//import org.apache.log4j.Logger;
import com.opensymphony.xwork2.ActionSupport;
import com.tail.test.objects.Message;
public class StrutsTestAction extends BaseAction {
// Our logger.
// private static Logger logger = Logger.getLogger(StrutsTestAction.class);
private String newUser;
private String newMessage;
private List<Message> messageList;
public List<Message> getMessageList() {
return messageList;
}
public void setMessageList(List<Message> messageList) {
this.messageList = messageList;
}
public String getNewMessage() {
return newMessage;
}
public void setNewMessage(String newMessage) {
this.newMessage = newMessage;
}
public String getNewUser() {
return newUser;
}
public void setNewUser(String newUser) {
this.newUser = newUser;
}
@Override
public String execute() throws Exception {
return super.execute();
}
public String loadMessages() {
messageList = new ArrayList<Message>();
messageList.add(new Message("tail", "This is an piece of initial message."));
return result(SUCCESS);
}
public String addMessageToUser() {
messageList.add(new Message(newUser, newMessage));
//logger.debug("Add user='" + newUser + "', message='" + newMessage + "'");
return result(SUCCESS);
}
}
其中,newUser和newMessage分别对应界面上的New Test User和New Message这两个输入框中的value。messageList则是下方显示的消息内容,注意messageList本身内含多个Message对象的List,Message类的定义如下:
package com.tail.test.objects;
public class Message {
private String userName;
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Message() {
}
public Message(String userName, String content) {
this.userName = userName;
this.content = content;
}
}
loadMessages()方法负责初始化装载数据,比如说我们从数据库或第三方资源中取出数据来初始化现有的List,它在页面上的实现,实际上也是结合Ajax来进行的。
addMessageToUser()方法对应了界面上Add Now按钮的动作内容,他们中间的交互过程也是通过Ajax来完成的,这也是这个应用的核心所在。
注意,这里的Struts Action Bean本身的默认的excute方法也被override过来了,而方法体本身是空的,这里这样做的目的就是,可以让界面操作用户一进入此页面,界面能马上构造出来,而不用等待服务器端的数据装载和返回,在初始装载数据量较大的时候,这一点尤其重要。这一点,大家可以结合后部分讲到的YUI部分的代码来体会。
现在已经有了StrutsTestAction这个工作类,那么这里的Struts核心的配置文件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>
<package name="test" namespace="/" extends="json-default">
<global-results>
<!-- any exceptions should redirect to the exception page -->
<result name="exception" type="redirect-action">
<param name="actionName">exception</param>
<param name="namespace">/</param>
</result>
</global-results>
<action name="exception" class="com.tail.test.actions.ExceptionAction">
<result>/exception.jsp</result>
</action>
<action name="home" class="com.tail.test.actions.StrutsTestAction">
<result>/userMessage.jsp</result>
<result name="input">/userMessage.jsp</result>
</action>
</package>
<package name="tail-json" namespace="/page" extends="test">
<global-results>
<!-- Don't redirect to login for ajax requests. -->
<result name="login" type="httpheader">
<param name="status">403</param>
</result>
<!-- Send a 503 for errors in ajax requests. -->
<result name="error" type="httpheader">
<param name="status">503</param>
</result>
</global-results>
</package>
<package name="tail-home" namespace="/home" extends="tail-json">
<global-results>
<result name="input" type="json">
<param name="includeProperties">
result, actionErrors.*, fieldErrors.*
</param>
</result>
</global-results>
<action name="loadMessages" method="loadMessages"
class="com.tail.test.actions.StrutsTestAction">
<result type="json">
<param name="includeProperties">
result, messageList.*
</param>
</result>
</action>
<action name="addMessageToUser" method="addMessageToUser"
class="com.tail.test.actions.StrutsTestAction">
<result type="json">
<param name="includeProperties">
result, messageList.*
</param>
</result>
</action>
</package>
</struts>
其中定义了三个package,前两个package没有什么好讲的,如果要详细了解其配置原理,可以参考Struts2的官方文档。
大家注意到,针对StrutsTestAction事实上已经在第一个package中定义了一次,为什么在第三个tail-home包中也要定义一遍呢,看到其中的json result的定义,大家应该也就看出其中的异样了。
不错,这里的定义主要是为了利用Struts JSON的插件功能将Action中的一个或多个属性/对象转换为json对象,提供到UI层去使用。而第一个package中定义的action,仅仅是页面第一次进入时的入口。
这里的result和messageList.*则是对StrutsTestAction的result和messageList属性的对外公布,includeProperties节点是支持正则表达式的,当然还有其他的json param,详细地大家可以参考struts2-jsonplugin-0.6.jar中的定义。
这里提到一个messageList,主要是给大家谈谈Struts2的conversion的支持,看看StrutsTestAction-conversion.properties文件的内容,大家应该就明白了。
Element_messageList = com.tail.test.objects.Message
CreateIfNull_messageList = true
不错,这里的定义主要是为了让json对象能被解析和传输。精确一点地说,在Action->UI的时候,他事实上用处不大,或者说可以省去。但是,如果如果经历UI->Action的时候,这个conversion定义就是必须的,否则Struts Action无法理解UI传送过来的数据。