Struts2 把所有标签都定义在URI为/struts-tags的命名空间下。可以分为3类:UI标签(主要用于生成HTML元素的标签,可分为表单标签和非表单标签)、非UI标签(主要用于数据访问和逻辑控制等,分为数据标签和控制标签)和Ajax标签(主要用于Ajax支持的标签)。
OGNL(Object Graphic Navigation Language,对象图导航语言),是个开源项目。OGNL是一种功能强大的EL(Expression Language,表达式语言),可以通过简单的表达式来访问Java对象中的属性。
OGNL首先在WebWork项目中得到应用,OGNL是Struts2框架视图默认的表达式语言,OGNL表达式是Struts 2框架的特点之一。
标准的OGNL会设定一个根对象(root对象)。假设使用标准OGNL表达式来求值(不是Struts 2 OGNL),如果OGNL上下文有两个对象,foo对象和bar对象,同时foo对象被设置为根对象(root),则利用下面的OGNL表达式求值。
#foo.blah // 返回foo.getBlah() #bar.blah // 返回bar.getBlah() blah // 返回foo.getBlah(),因为foo为根对象
如果要访问的不是根对象,需要使用命名空间,用“#”表示。如果是根对象,则不用指定。
在Struts 2框架中,值栈(Value Stack)就是OGNL的根对象。假设值栈中存在两个对象实例Man和Animal,这两个对象实例都有一个name属性,Animal有一个species属性,Man有一个salary属性。假设Animal在值栈的顶部,Man在Animal后面,如图所示。
一个包含了Animal和Man的值栈
下面的代码片段能更好地理解OGNL表达式。
species // 调用animal.getSpecies() salary // 调用man.getSalary() name // 调用animal.getName(),因为Animal位于值栈的顶部
最后一行实例代码返回的是animal.getName()返回值,即返回了Animal的name属性,因为Animal是值栈的顶部元素,OGNL将从顶部元素搜索,所以会返回Animal的name属性值。如果要获得Man的name值,则需要如下代码:
man.name
Struts 2允许在值栈中使用索引,实例代码如下:
[0].name // 调用animal.getName() [1].name // 调用man.getName()
Struts 2中的OGNL Context是ActionContext,如图所示。
Struts 2的OGNL Context结构示意图
由于值栈是Struts 2中OGNL的根对象。如果用户需要访问值栈中的对象,则可以通过如下代码访问值栈中的属性:
${foo} // 获得值栈中的foo属性
如果访问其他Context中的对象,由于不是根对象,在访问时需要加#前缀。
application对象:用来访问ServletContext,如#application.userName或者#application ["userName"],相当于调用Servlet的getAttribute("userName")。
session对象:用来访问HttpSession,如#session.userName或者#session["userName"],相当于调用session.getAttribute("userName")。
request对象:用来访问HttpServletRequest属性的Map,如#request.userName或者#request["userName"],相当于调用request.getAttribute("userName")。如在3.2.1节中StrutsAction类中代码:
Map request=(Map)ActionContext.getContext().get("request"); request.put("name",getName());
这就是先得到request对象,然后把值放进去,在该例的success.jsp中有:
<s:property value="#request.name"/>
其中#request.name相当于调用了request.getAttribute("name")。
使用如下代码直接生成一个List对象:
{e1, e2, e3…}
下面的代码可以直接生成一个Map对象:
#{key: value1, key2: value2, …}
对于集合类型,OGNL表达式可以使用in和not in两个元素符号。其中,in表达式用来判断某个元素是否在指定的集合对象中;not in判断某个元素是否不在指定的集合对象中,代码如下所示:
<s: if test="'foo' in {'foo', 'bar'}"> … </s: if>
或
<s: if test="'foo' not in {'foo', 'bar'}"> … </s: if>
除了in和not in之外,OGNL还允许使用某个规则获得集合对象的子集,常用的有以下3个相关操作符。
?:获得所有符合逻辑的元素。
^:获得符合逻辑的第一个元素。
$:获得符合逻辑的最后一个元素。
如下面的代码:
Person.relatives.{?# this.gender=='male'}
该代码可以获得Person的所有性别为male的relatives集合。
数据标签属于非UI标签,主要用于提供各种数据访问相关的功能,数据标签主要包括以下几个。
property:用于输出某个值。
set:用于设置一个新变量。
param:用于设置参数,通常用于bean标签和action标签的子标签。
bean:用于创建一个JavaBean实例。
action:用于在JSP页面直接调用一个Action。
date:用于格式化输出一个日期。
debug:用于在页面上生成一个调试链接,当单击该链接时,可以看到当前值栈和Stack Context中的内容。
il8n:用于指定国际化资源文件的baseName。
include:用于在JSP页面中包含其他的JSP或Servlet资源。
push:用于将某个值放入值栈的栈顶。
text:用于输出国际化(国际化内容会在后面讲解)。
url:用于生成一个URL地址。
property标签的作用是输出指定值。property标签输出value属性指定的值。如果没有指定的value属性,则默认输出值栈栈顶的值。该标签有如下几个属性:
default:该属性是可选的,如果需要输出的属性值为null,则显示default属性指定的值。
escape:该属性是可选的,指定是否escape HTML代码。
value:该属性是可选的,指定需要输出的属性值,如果没有指定该属性,则默认输出值栈栈顶的值。该属性也是最常用的,如前面用到的:
<s:property value="#request.name"/>
id:该属性是可选的,指定该元素的标志。
对值栈中的表达式进行求值,并将结果赋给特定作用域中的某个变量名。该标签有如下几个属性:
name:该属性是必选的,重新生成新变量的名字。
scope:该属性是可选的,指定新变量的存放范围。一般为application、session、request、page和action。如果没有指定该属性,则默认放置在值栈中。
value:该属性是可选的,指定赋给新变量的值。如果没有指定该属性,则将值栈栈顶的值赋给新变量。
id:该属性是可选的,指定该元素的引用id。
下面是一个简单例子,展示了property标签访问存储于session中的user对象的多个字段:
<s:property value="#session['user'].username"/> <s:property value="#session['user'].age"/> <s:property value="#session['user'].address"/>
由于每次都要重复使用#session['user'],不仅烦人还容易引发错误,更好的做法是定义一个临时变量,使用set标签使得代码易于阅读:
<s:set name="user" value="#session['user'] " /> <s:property value="#user.username"/> <s:property value="#user.age" /> <s:property value="#user.address" />
param标签主要用于为其他标签提供参数,该标签有如下几个属性:
name:该属性是可选的,指定需要设置参数的参数名。
value:该属性是可选的,指定需要设置参数的参数值。
id:该属性是可选的,指定引用该元素的id。
例如,要为name为fruit的参数赋值:
<s:param name= "fruit">apple</s:param>
或者
<s:param name="fruit" value="apple" />
指定一个名为fruit的参数,该参数的值为apple对象的值,如果该对象不存在,则fruit的值为null。
如果想指定fruit参数的值为apple字符串,则应该这样写:
<s:param name="fruit" value="'apple'" />
用于创建一个JavaBean实例。创建JavaBean实例时,可以在该标签内使用param标签为该JavaBean实例传入属性。如果要使用param标签传入属性值,则应该为该JavaBean类提供setter方法。如果还希望访问该属性值,则必须为该属性提供getter方法。该标签有如下几个属性:
name:该属性是必选的,用来指定要实例化的JavaBean的实现类。
id:该属性是可选的,如果指定了该属性,则该JavaBean实例会被放入Stack Context中,从而允许直接通过id属性来访问该JavaBean实例。下面是一个简单的例子:
有一个Student类,该类中有name属性,并有其getter和setter方法:
public class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name=name; } }
然后在JSP文件的body体中加入下面的代码:
<s:bean name="Student"> <s:param name="name" value="'zhangsan'"/> <s:property value="name"/> </s:bean>
在项目中导入Struts 2的Jar包,再把Student类放在项目的src文件夹下(如果Student类在在某个包下面,那么<s:bean>标签的name要加上包名。例如org.bean.Student),<s:bean>标签内容放在一个JSP文件的body体内,再修改web.xml文件,就可以部署运行该项目,会得到如图所示的界面。
bean标签实例界面
也可以把bean标签的内容改为:
<s:bean name="Student" id="s" > <s:param name="name" value="'zhangsan'"/> </s:bean> <s:property value="#s.name"/>
使用action标签可以允许在JSP页面中直接调用Action。该标签有以下几个属性:
id:该属性是可选的,该属性将会作为该Action的引用标志id。
name:该属性是必选的,指定该标签调用哪个Action。
namespace:该属性是可选的,指定该标签调用的Action所在的namespace。
executeResult:该属性是可选的,指定是否要将Action的处理结果页面包含到本页面。如果值为true,就是包含,false就是不包含,默认为false。
ignoreContextParam:该属性是可选的,指定该页面中的请求参数是否需要传入调用的Action。如果值为false,将本页面的请求参数传入被调用的Action。如为true,不将本页面的请求参数传入到被调用的Action。
date标签主要用于格式化输出一个日期。该标签有如下属性:
format:该属性是可选的,如果指定了该属性,将根据该属性指定的格式来格式化日期。
nice:该属性是可选的,该属性的取值只能是true或false,用于指定是否输出指定日期和当前时刻之间的时差。默认为false,即不输出时差。
name:属性是必选的,指定要格式化的日期值。
id:属性是可选的,指定引用该元素的id值。
nice属性为true时,一般不指定format属性。因为nice为true时,会输出当前时刻与指定日期的时差,不会输出指定日期。当没有指定format,也没有指定nice="true"时,系统会到国际化资源文件中寻找key为struts.date.format的消息,将该消息当成格式化文本来格式化指定日期,如果无法找到,则默认采用DateFormat.MEDIUM格式输出。其用法为:
<s:date name="指定日期取值" format="日期格式"/><!-- 按指定日期格式输出 --> <s:date name="指定日期取值" nice="true"/><!-- 输出时间差 --> <s:date name="指定日期取值"/><!—默认格式输出-->
例子:
java
import java.util.Date; public class Student { private Date birthday; public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
jsp
<s:bean name="Student"> <s:param name="birthday" value="'2011-12-10'"/> <s:date name="birthday" /><br/> <s:date name="birthday" format="yyyy-MM-dd hh:mm:ss"/><br/> <s:date name="birthday" nice="true"/><br/> </s:bean>
include标签用于将一个JSP页面或一个Servlet包含到本页面中。该标签有如下属性:
value:该属性是必选的,指定需要被包含的JSP页面或Servlet。
id:该属性是可选的,指定该标签的id引用。
用法如下:
<s:include value="JSP或Servlet文件" id="自定义名称"/>
属于非UI标签,主要用于完成流程的控制,以及对值栈的控制。控制标签有以下几个:
if:用于控制选择输出的标签。
elseif:用于控制选择输出的标签,必须和if标签结合使用。
else:用户控制选择输出的标签,必须和if标签结合使用。
append:用于将多个集合拼接成一个新的集合。
generator:用于将一个字符串按指定的分隔符分隔成多个字符串,临时生成的多个子字符串可以使用iterator标签来迭代输出。
iterator:用于将集合迭代输出。
merge:用于将多个集合拼接成一个新的集合,但与append的拼接方式不同。
sort:用于对集合进行排序。
subset:用于截取集合的部分元素,形成新的子集合。
这3个标签可以组合使用,但只有if标签可以单独使用,而elseif和else标签必须与if标签结合使用。if标签可以与多个elseif标签结合使用,但只能与一个else标签使用。其用法格式如下:
<s:if test="表达式"> 标签体 </s:if> <s:elseif test="表达式"> 标签体 </s:elseif> <!--允许出现多次elseif标签--> ... <s:else> 标签体 </s:else>
该标签主要用于对集合进行迭代,这里的集合包含List、Set,也可以对Map类型的对象进行迭代输出。该标签的属性如下:
value:该属性是可选的,指定被迭代的集合,被迭代的集合通常都由OGNL表达式指定。如果没有指定该属性,则使用值栈栈顶的集合。
id:该属性是可选的,指定集合元素的id。
status:该属性是可选的,指定迭代时的IteratorStatus实例,通过该实例可判断当前迭代元素的属性。如果指定该属性,其实例包含如下几个方法:
int getCount():返回当前迭代了几个元素。
int getIndex():返回当前被迭代元素的索引。
boolean isEven:返回当前被迭代元素的索引元素是否是偶数。
boolean isOdd:返回当前被迭代元素的索引元素是否是奇数。
boolean isFirst:返回当前被迭代元素是否是第一个元素。
boolean isLast:返回当前被迭代元素是否是最后一个元素。
应用举例:
<%@ page language="java" pageEncoding="utf-8"%> <%@taglib uri="/struts-tags" prefix="s" %> <html> <head> <title>控制标签</title> </head> <body> <table border="1" width="200"> <s:iterator value="{'apple','orange','pear','banana'}"id="fruit" status="st"> <tr <s:if test="#st.even">style="background-color:silver"</s:if>> <td><s:property value="fruit"/></td> </tr> </s:iterator> </table> </body> </html>
通过添加Struts 2必须的Jar包,再建立上面JSP文件,修改web.xml后,就可以部署运行,运行结果如图所示。
iterator标签实例运行结果
用于将多个集合对象拼接起来,组成一个新的集合。必须指定一个id属性,该属性确定拼接的新集合的名称。该标签通过param标签来指定每一个集合,然后把这些集合拼接起来。
应用举例,可以把上例的JSP文件进行修改,其代码为:
<%@ page language="java" pageEncoding="utf-8"%> <%@taglib uri="/struts-tags" prefix="s"%> <html> <head> <title>控制标签</title> </head> <body> <s:append id="newList"> <s:param value="{'apple','orange','pear','banana'}"></s:param> <s:param value="{'chinese','english','french'}"></s:param> </s:append> <table border="1" width="200"> <s:iterator value="#newList" id="fruit" status="st"> <tr <s:if test="#st.even">style="background-color:silver"</s:if>> <td> <s:property value="fruit" /> </td> </tr> </s:iterator> </table> </body> </html>
部署运行,运行结果如图所示。
append标签实例运行界面
与append标签的区别,只是拼接方式不同。
假设有2个集合,第一个集合包含3个元素,第二个集合包含2个元素,分别用append标签和merge标签方式进行拼接,它们产生新集合的方式有所区别。下面分别列出:
用append方式拼接,新集合元素顺序为:
第1个集合中的第1个元素
第1个集合中的第2个元素
第1个集合中的第3个元素
第2个集合中的第1个元素
第2个集合中的第2个元素
用merge方式拼接,新集合元素顺序为:
第1个集合中的第1个元素
第2个集合中的第1个元素
第1个集合中的第2个元素
第2个集合中的第2个元素
第1个集合中的第3个元素
代码如下:
<%@ page language="java" pageEncoding="utf-8"%> <%@taglib uri="/struts-tags" prefix="s"%> <html> <head> <title>控制标签</title> </head> <body> <s:merge id="newList2"> <s:param value="{'apple','orange','pear','banana'}"></s:param> <s:param value="{'chinese','english','french'}"></s:param> </s:merge> <table border="1" width="200"> <s:iterator value="#newList2" id="fruit2" status="st2"> <tr <s:if test="#st2.even">style="background-color:silver"</s:if>> <td> <s:property value="fruit2" /> </td> </tr> </s:iterator> </table> </body> </html>
大部分的表单标签和HTML表单元素是一一对应的关系,如下面的代码片段:
<s:form action="login.action" method="post"/>
对应着:
<form action="login.action" method="post"/> <s:textfield name="username" label="用户名" />
对应着:
用户名:<input type="text" name="username"> <s:password name="password" label="密码"/>
对应着:
密码:<input type="password" name="pwd">
还有下面这种情况,如果有这样一个JavaBean类,类名为“User”,该类中有两个属性:一个是username;另一个是password,并分别生成它们的getter和setter方法,在JSP页面的表单中可以这样为表单元素命名:
<s:textfield name="user.username" label="用户名" /> <s:password name="user.password" label="密码"/>
下面介绍和HTML标签元素不是一一对应的几个重要的表单标签:
创建多个复选框。该标签需要指定一个list属性。用法举例:
<s:checkboxlist label="请选择你喜欢的水果" list="{'apple','oranger','pear','banana'}" name="fruit"> </s:checkboxlist>
或者为:
<s:checkboxlist label="请选择你喜欢的水果" list="#{1:'apple',2:'oranger',3:'pear',4:'banana'}" name="fruit"> </s:checkboxlist>
这两种方式的区别:前一种根据name取值时取的是选中字符串的值;后一种在页面上显示的是value的值,而根据name取值时取的却是对应的key,这里就是1、2、3或4。
combobox标签生成一个单行文本框和下拉列表框的组合。两个表单元素只能对应一个请求参数,只有单行文本框里的值才包含请求参数,下拉列表框只是用于辅助输入,并没有name属性,故不会产生请求参数。用法举例:
<s:combobox label="请选择你喜欢的水果" list="{'apple','oranger','pear','banana'}" name="fruit"> </s:combobox>
datetimepicker标签用于生成一个日期、时间下拉列表框。当使用该日期、时间列表框选择某个日期、时间时,系统会自动将选中日期、时间输出指定文本框中。用法举例:
<s:form action="" method=""> <s:datetimepicker name="date" label="请选择日期"></s:datetimepicker> </s:form>
注意:在使用该标签时,要在HTML的head部分加入<s:head/>。
select标签用于生成一个下拉列表框,通过为该元素指定list属性的值,来生成下拉列表框的选项。用法举例:
<s:select list="{'apple','oranger','pear','banana'}" label="请选择你喜欢的水果"></s:select>
或者为:
<s:select list="fruit" list="#{1:'apple',2:'oranger',3:'pear',4:'banana'}" listKey="key"listValue="value"></s:select>
radio标签的用法与checkboxlist用法很相似,唯一的区别就是checkboxlist生成的是复选框,而radio生成的是单选框。用法举例:
<s:radio label="性别" list="{'男','女'}" name="sex"></s:radio>
或者为:
<s:radio label="性别" list="#{1:'男',0:'女'}" name="sex"> </s:radio>
head标签主要用于生成HTML页面的head部分。在介绍<s:datetimepicker>标签时说过,要在head中加入该标签,主要原因是<s:datetimepicker>标签中有一个日历小控件,其中包含了JavaScript代码,所以要在head部分加入该标签。
如果需要在页面中使用Ajax组件,就需要在head标签中加入theme="ajax"属性。
非表单标签主要用于在页面中生成一些非表单的可视化元素。这些标签不经常用到,下面大致介绍一下这些标签:
a:生成超链接。
actionerror:输出Action实例的getActionMessage()方法返回的消息。
component:生成一个自定义组件。
div:生成一个div片段。
fielderror:输出表单域的类型转换错误、校验错误提示。
tablePanel:生成HTML页面的Tab页。
tree:生成一个树形结构。
treenode:生成树形结构的节点。