Struts2 学习笔记- jonsion - JavaEye技术网站
1. Struts2 与 Struts1 似乎没什么血缘关系了,承自于 WebWork,直害 WebWork 不再推出新版本了,原先的 WebWork 使用者只需顺其然,即是水道渠成;相反 Struts1 的开发经验对于 Struts2 却没有太大的帮助。

2. Struts1 的核心控制器是 ActionServlet,而 Struts2 的核心控制器是 FilterDispatcher。Struts2 的业务 Action 会在运行时生成 Action 代理。

3. Struts1 的 Action 须继承抽象 Action 类,侵入了 Servlet API;而 Struts2 的 Action 可以实现 ActionSupport,或者只要一个纯净的 POJO 来充当,xx与 Servlet 绝缘。也因此带来测试性 Struts2 要比 Struts1 强,可把 Struts TestCase 扔到一旁去了。

4. Struts1 在运行时一个 Action 只会生成一个实例,而 Struts2 中的 Action 会应对每个请求生成新的实例,在线程安全性方面,Struts1 要多些考虑。

5. Struts1 依靠 ActionForm 来接受数据,它也是依赖于 Servlet API ,并非普通的 JavaBean,而 Struts2 可在 Action 属性中获取请求的属性,当然也能封装在一个独立的 POJO 中。不过提醒一下,在 Struts1.3 中也可以用 POJO 来接受请求属性,但使用起来有点烦琐。

6. Struts2 使用了新的 OGNL 的表达式语言,可以访问值栈,在对集合和索引属性的支持上功能更强大。

7. Struts1 的配置文件只需 struts-config.xml;Struts2 的配置文件有 struts.xml 和 struts.properties。struts.properties 中有两个 struts.devMode = fase 和 struts.configuration.xml.reload = true 很可爱,分别设置当前是开发还是生产模式,以及 struts.xml 改变后是否自动重新加载,免去了改配置手工重启应用的必须步骤。

8. Struts2 Action 的 execute() 无参,返回的是一个字符串,不像 Struts1 Action 的 execute() 有一大串且不说,光是返回 ActionForward,就让这个 Action 与 Struts1 脱不了干系;还是 Struts2 来得聪明,只返回个中性的 String,反正名字吗,猛然间这个 Action 也成 POJO 了,复用性增强了,难怪专门有一书,名为《POJO In Action》。

9. Struts1 的视图只支持 JSP,这也不怪它,只怨出身太早,那时就只有 JSP 一种表现层。相比,Struts2 更有艳福,能贴近于 FreeMarker、Velocity、XSLT、PDF 等视图技术。

10. Struts2 提供了功能强大复杂的标签库,统一化为 "s" 前缀。并顺应时代潮流地加入了 AJAX 的支持。还提供了更友好的验证功能。


1. 记得 Struts1 的 struts-config.xml 通常是放在 WEB-INF 目录下的,Struts2 的 struts.xml 却要放到 WEB-INF/classes 下的,傻眼了吧,反正我是。没看仔细之前,总给我报 There is no Action mapped for namespace / and action name Login. - [unknown location]  错误。因未在 web.xml 指定 struts.xml,所以用 ClassLoader 来加载它了。

2. 最简单的 Struts2 应用都要用到 freemarker-xxx.jar 包,因为 UI 标签要用到 FreeMarker ftl 的模板。

3. FilterDispatcher 要处理所有的 Web 请求,所以它的 url-pattern 是 /*,并自动对 *.action 转给业务 Action 处理。

4. struts.xml 中引入一个 <package> 节点来包裹 <action>,必须指定 <package> 的 name 属性,自定义命名即可,与 class 所在包无多大关系。

5. 把 ActionContext.getContext().getSession() 这个 Map 当 HttpSession 用即可,它与 HttSession 之间的转换 Struts2 的拦截器帮你完成。

6. 在 Action 接受客户端请求的数据,并且还可存放送给客户端的数据,这种做法个人觉得不怎么清爽,数据与控制器杂揉一块,也许是受 Struts1 的 ActionForm 的影响。Action 中的属性值会存放在类型为 ValueStack、名为 struts.valueStack 的属性中。

7. Struts2 的标签更是与 Struts1 的千差万别,只有 <s:xxx 的标签了,所以只需在 jsp 前加 < %@taglib prefix="s" uri="/struts-tags" % >,哪用 Struts1 那么分门别5类,但功能更强,这个吗,谁用谁知道。

8. 国际化支持要先在 struts.properties 中指定资源束名,如 struts.custom.i18n.resources=messageResource,则在 classpath 下找 messageResource_语言代码_国家代码.properties。用 <s:text name="key"> 或 <s:property value='%{getText("key")}'/> 输出国际化消息。

9. Struts2 支持在 JSP 页面中临时加载资源文件,也支持通过全局属来加载资源文件,上一条就是全局的方式。

10. Action 中增加数据校验功能,可能它继承 ActionSupport 类,并实现 validate() 方法,产生的错误放到 ActionSupport 类的 fieldErrors 域中,由 OGNL 负责输出。有错误请求将转发到 input 逻辑视图。Struts2 的 <s:form .../> 默认已提供了输出校验错误的能力。是否觉得这种校验忒不尽人情了,别急,Struts2 可让你xx用配置来校验表单的,让你的 Action 还是那个 Action (继承 ActionSupport,无须实现 validate() 方法),只需写一个 ActionName-validattion.xml 文件扔在与 Action 同位置的目录中即可,这实质就是用的基于 XWork 的验证框架。




1. WebWork 的核心控制器是 ServletDispatcher,映射到 *.action 的 <url-pattern> 即可。在 WebWork 与 Struts2 的 struts.xml 和 struts.properties 相对应的分别是 xwork.xml 和 webwork.properties,文件内容几乎一样,只几个关键字的差异。

2. Struts2 的 struts.xml 中 <action ../> 的 name 属性相当于 struts1 的 <action ../> 的 path 属性,但在这里不需要以 "/" 开始。

3. Action 中使用业务逻辑组件实例时,通常不直接 New,而会通过工厂方法或 IOC 容器。虽然 WebWork 有 IOC 容器,但 Struts2 通常会使用 Spring 的 IOC。

4.  可把 struts.xml 拆成多个文件,如 struts-part1.xml、struts-part2.xml ... 等,然后在 struts.xml 中逐个 <include file="struts-part1.xml"/>、<include file="struts-part1.xml"/> ... 即可。看看 struts2-core-x.x.x.jar 中的 struts-default.xml 为你内建了多少东西吧。

5. Struts2 的官方说是它要求在支持 Servlet2.4/JSP2.0 的容器中运行,不过我仍然把那个简单的例子(web.xml 中用 web-app_2_3.dtd) 放到 Tomcat 4.1.30(支持到 Servlet2.3/JSP1.2) 下运行了一番,能正常工作,不知可能产生的问题有哪些。

6. Struts2 还提供一种类似 Eclipse 那种可插拔的方式来安装插件,看到下载的 Struts2 的 lib 目录下有好多 struts2-xxx-plugin.jar,这些包中都有一个 struts-plugin.xml(就是一个 struts.xml 配置文件),当把 struts2-xxx-plugin.jar 拷入到 WEB-INF/lib 下,这个 struts-plugin.xml 就会自动被加载。你也可以像这样做自己的插件包。struts.properties 中属性 struts.configuration.files=struts-default.xml,struts- plugin.xml,struts.xml。

7.  struts.properties 中本人认为比较重要的属性 struts.action.extension、struts.serve.static.browserCache、 struts.enable.SlashesInActionNames、struts.devMode、struts.i18n.reload、 struts.configuration.xml.reload、struts.custom.i18n.resources、 struts.i18n.encoding。好多属性值都支持逗号分隔的多个值。默认的属性配置在 default.properties。

8. 默认情况,*.action 请求交给 Struts2 处理,你也可以改成其他扩展名,只要改 struts.properties 中的 struts.action.extension 属性值,如 struts.action.extension=unmi,action,则*.unmi和*.action都会被 Struts2 处理了。

9. Struts2 比起 Struts1 来在开发时不仅改了 struts.xml 不用重启应用,就连改了国际化资源文件都可以不需要重启应用。设置 struts.i18n.reload = true 即可。

10. Struts2 提供了两种方式来管理 Struts2 属性,既可以写在 struts.properties,也可以写在 struts.xml 中配置为 constant 元素,如 <constant name="struts.i18n.reload" value="true"/>。相信大多数人还是倾向于写在 struts.properties 中。


1. 按这个顺序加载 Struts2 的常量,struts-default.xml、struts-plugin.xml、struts.xml、web.xml;后加载的会覆盖前面的同名常量。强烈不推荐在 web.xml 中配置 Struts2 常量。

2. Struts1 中有一种 Action 类型是 ForwardAction,而在 Struts2 中相应的配置方式是 <action name="Showcase"><result>/showcase.jsp</result>< /action>,Showcase.action 直接映射到 /showcase.jsp。

3. struts.xml 中 <package ../> 的 namespace (命名空间) 相当于 Struts1 的模块的概念,但比 Struts1 的模块间切换要便。例如,对于以下的配置
    <package name="get" extends="struts-default" namespace="/book">
          <action name="GetBooks" class="...">
     因 action GetBooks 所在的包指定了命名空间,所以 URL 就需要 /book/GetBooks.action 与它映射了。
     如果未指定命名空间或指定为 "" 就是默认命名空间,指定 "/" 就是根命名空间,URL 就应该是 /GetBooks.action。配置在默认命名空间中的 Action 相当于全局的,即类似于 Struts1 的 <global-action .../>。也就是说当找不到指定命名空间(例如 /barspace/bar.action) 中的 Action (bar.action) 时,就会尝试去默认命名空间 ("") 里去找。

4. 系统不会严格区分 Action 里哪个属性是用于封装请求参数的属性,那个属性是封装处理结果的属性。对系统而言,封装请求参数的属性和封装处理结果的属性是xx平等的。

5. Struts2 的 Action 可以是一个 POJO,可以实现 Action 接口,也可以继承 ActionSupport。实际上 ActionSupport 类是 Struts2 默认的 Action 处理类,就是说,配置 Action 时未指定 class 属性时,系统自动指定为 ActionSupport 类。

6. Struts2 中要访问 Servlet API 必须借助于 ActionContext 类,其中有访问对 HttpServletRequest(request),HttpSession(session),ServletContext(application) 进行操作的方法 (操作的是各自的 attribute 属性),不过还没发现如何操作 HttpServletResponse(response) 对象。注意,这些方法都转换成了对 Map 实例的操作,而非真实的 Servlet API 实例,Struts2 会完成与实际存储的映射,所以 Action 仍然是脱离 Servlet API 的。ActionContext 直接的 get() 和 put() 方法针对的是 request 的属性。

7. Struts2 也可以直接访问 Servlet API 实例,让你的 Action 实现后面其中一个接口就能获取到相应 Servlet API 实例:ServletContextAware、ServletRequestAware、ServletResponseAware。注意要实现的接口 方法。如果觉得实现接口的方式麻烦,那么可以借助 ServletActionContext 的方法来拿到 Servlet API 的各个实例。这样却是让 Action 与 Servlet API 耦合起来了。

8. 即使我们在 Struts2 的 Action 中获得了 HttpServletResponse 对象,也不要尝试直接在 Action 中生成对客户端的输出,没效果的,因为 Action 只是一个控制器,它并不直接对浏览者生成任何响应。这也是为什么 ActionContext 未提供对 HttpServletResponse 的操作,只是操作 Cookie 要用到 response。在 Struts1 中,如果 Action 返回 null 时,可以通过 response.getWriter().println("Hello World."); 输出内容到页面。

9. Struts2 也有像 Struts1 那样的对 Action 动态方法调用的特性,它是通过指定 form 的 action="ActionName!methodName.action" 来实现的,例如某表单的 action="Login!regist.action",提交后将会调用 name="Login" 的 Action 类的 regist() 方法,而非默认的 execute() 方法。这种方式可以在一个 Action 中包含多个处理逻辑。是不是 Struts1 要方便,不需要在配置文件中对这个 Action 配置额外的属性。

10. 可为 action 配置指定一个 method 属性,同上,这种方式也在一个 Action 类中定义多个逻辑,每个处理方法映射成一个逻辑 Action,有不同的 name 属性,非常类似于 Struts1 的 MappingDispatchAction。缺点就是不像上面那样,被分开的多个逻辑 Action 不能共享 <result> 配置,并且 action 配置的 class 属性值产生冗余。


1. 动态 Action:<action ../> 元素的一个前所未有的特性是,name 属性可以用通配符,class 和 method 属性中可以用 name 中的匹配参数,{0}/{1} 的形式。举两个例子说明:
    ① <action name="*Action" class="com.unmi.LoginRegistAction" method="{1}">
         URL 是 registAction.action 时,会执行 LoginRegistAction 类实例的 regist() 方法
    ② <action name="*_*" class="actions.{1}Action" method="{2}">
         URL 是 Book_save.action 时,会执行 actions.BookAction 类实例的 save() 方法
   说 明:{1},{2}是用来匹配 name 属性中的 *,这和正则表达式一样的,{0} 表示的完整的 name 属性值。这也是托 Struts2 的每请求产生新的 Action 的实例才能这么用的,试想一下,Struts1 是没法针对通配符来预先加载好所有的 Action 实例的。
    可由此定义一个能用的 Action,<result> 里也能用参数。
    <action name="*">
        <result>/{1}.jsp</result>
    </action>

2. 关于在 <action .../> 中使用通配符时的校验文件命名规则。校验文件的搜索规则是:
    ① <ActionClassName>-<ActionAliasName>-validation.xml              <ActionAliasName> 为 name 属性值
    ② <ActionClassName>-validation.xml
    同时有这两个文件时,后面的规则能与前面的规则叠加或覆盖,例如 ① 中有name域的校验,② 中有password域的校验,这两个文件同时存在 <ActionClassName>所在路径时,会同时校验name和password域。
    例如对于 <action name="*Action" class="com.unmi.LoginRegistAction" method="{1}">
    URL 是 registAction.action 时,会搜索校验文件 LoginRegistAction-registAction-validation.xml 和 LoginRegistAction-validation.xml。

3. 使用通配符就会涉及到 URL 与哪个 Action 匹配的问题。例如匹配有name为 "*"、"*Action"、"LoginAction" 的 <action .../>,如果 URL 与某个 Action 的 name xx相同(如 LoginAction.action),否则按顺序来匹配,而不是按匹配度来对应。如 abcAction.action 会匹配到 "*",而不是 "*Action"。

4. 可配置默认的 Action,URL 匹配不到对应的 Action 时就用它,用 <default-action-ref ../> 配置在 <package .../> 中。

5. Struts2 支持两种 <result ../>,配置在 <action .../> 中的局部 result,配置在 <global-results .../> 中的全局 result。<result .../> 默认的 name 属性是 "success";默认的 type 属性是 "dispatcher",即使 JSP 类型。

6. <result .../> type="plaintext" 的 <result .../> 会显示页面的源文件,如果其中有中文一般会产生乱码,这时候可设置它的 charSet 属性为 "GBK",用 <param .../> 标记。

7. <result .../> 的 dispatcher 和 redirect 类型的区别就是一个是转发(带请求参数、属性、址址栏不变)和重定向(丢失请求参数、属性、重新产生请求,所以地址栏会变)。

8. redirect-action 类型是重定(不是转发)向到一个 Action 上,那么要为 <result .../> 指定两个参数 actionName 和 namespace。简写为 <result name="..." rediect-action">actionName</result>。这种类型相当于在 Struts1 中的   <forward name="..." redirect="true">/anotherAction.do</forward> 写法。当然在 Struts2 中也可以用 redirect 类型写成相同的形式。

9. 动态结果资源有两种,根据 <action .../> 的 name 属性的模式匹配后的参数和请求参数决定结果。
    ① <action name="crud_*" class="com.unmi.CrudAction" method="{1}">
            <result>/{1}.jsp</result>
         </action>
     URL 为 crud_delete.action 处理成功后会转到 delete.jsp 页。
    ② <action name="..." class="com.unmi.ShowAction">
            <result>/show${user.type}</result>
        </action>
       要在 ShowAction 的设置了属性 ${user.type}。例如 ${user.type} 设置为 "admin",就会转到 showadmin.jsp 页。

10. Struts2 的 <result .../>、<global-result .../> 意义与效果xx对应于 Struts1 的 <forward .../>、<global-forward .../> 。


1. Struts2 的 Action 中可直接用属性来封装请求参数和处理结果,此谓之属性驱动。也可以像 Struts1 那样使用专门的 ActionForm 来处理请求参数和结果,只不过 Struts2 用的是一个更单纯的 POJO,这就做模型驱动;此时的 Action 需要实现 ModelDriven 接口,并实现其 getModel() 方法将 Action 与对应 Model 相关联。

2. 例子说明采用模型驱动的 Action (UserBean 是一个普通 JavaBean,其中定义了 username 和 password 两个属性):
    public class LoginAction implments Action, ModelDriven<UserBean>{
        UserBean model = new UserBean();
        public UserBean getModel(){
            return model;
       }
    }
    上面例子使用了泛型,Struts2 的 Action 要使用一个模型不需要在配置文件中作额外的配置。

3. 请求参数自动封装到模型中是由配置 struts-default.xml 中的 ModelDrivenIntercepter 来完成的。要输出模型中值时用标记 <s:property value="model.username"/>。而倘若写成了<s:property value="username"/>,Struts2 也会作智能处理,要是这个 Action 中未定义 username 属性,并且彩用了模型驱动模式,也会输出模型 model.username 属性,就像 Struts1 显示 ActionForm 一样可不写 name 属性。

4. Struts2 的异常处理,基本和 Struts1 一样的思维,都提供了声明式异常处理方式,在 struts.xml 中配置。Struts2 的 <global-exception-mappings .../>、<exception-mapping .../> 分别与 Struts1 的 <global-exceptions .../>、<exception .../> 对应,作用都是声明 Action 中出现何种类型的异常,转到对应页面,你的 Action 的 execute() 方法只管 throws Exception 就行,剩下的事框架帮你处理。

5. 异常信息的输出,<s:property value="exception"/> 输出异常对象本身;<s:property value="exceptionStack"/> 输出异常堆栈信息,这是 Struts1 没有的功能;<s:property value="exception.message"/> 输出异常的 message 属性。至于标签输出异常国际化消息,可就要借鉴前面的知识稍加斟酌一番,其实就是:如 execute() throw new Exception("name.error"),要输出 name.error 对应消息用 <s:property value="%{getText(exception.message)}"/> 即可,如果找不到 name.error 对应消息,会直接把 "name.error" 原本输出。

6. 页面请求数据或待显示的结果(它们总是字符串)与Java对象之间要一个类型转换器,例如,"2007-01-01" 提交后就是一个 Date 对象,"Unmi,000" 一提交就是一个 UserBean 对象("Unmi"和"000"分别对应 UserBean 的用户名和密码),用 <s:property value="userbean"/> 显示这个 UserBean 对象形式为 "Unmi,000"。可想而知,Struts2 肯定为我们内置了不少类型转换器。

7. 可以自定义类型转换器,实现 TypeConverter 接口或继承 DefaultTypeConverter 类。一般采用继承的方法。注意 DefaultTypeConverter 的 Object convertValue(map context, Object value, Class toType) 方法的写法,了解该方法的参数及返回值的类型与意义;该方法中依据 toType 的类型分别实现两个方向的转换逻辑,如果是从请求到 Java 对象的转换时,value 参数是一个字符串数组,实质是用 request.getParameterValues(name) 获取的值。

8. 自定义的类型转换器可注册为局部的(为某些个 Action 所用)或全局的(为所有 Action 所用)。局部类型转换器的注册方式是提供一个名为 ActionName-conversion.properties 放在该 Action 的 class 所在路径下(记得校验文件也是放在同样的位置)。这个文件的内容格式为:
  
     Action中的属性名=类型转换器的全限类名

例如:

   user=com.unmi.struts2.converter.UserConverter

   user 该 Action 中的属性的名称,对该 Action 中的 user 属性用 UserConverter 转换。可有多行,分别为不同的属性设置转换器。

9. 注册为全局的类型转换器的方法。提供一个 xwork-conversion.property 放在 classpath 下(基本就是 WEB-INF/classes 下),然后里面可加多个 "属性类型=类型转换器的全限类名"。如

   com.unmi.vo.User=com.unmi.struts2.converter.UserConverter

   com.unmi.vo.User 为 Action 中的属性类型,碰到 User 类型属性就用 UserConverter 转换。可有多行,分别用于转换不同的属性类型。

10. 再回过头来与 Struts1 作个对比,Struts1 的类型转换器只能注册全局的,而不能只为个别 Action 服务,并且要通过编写代码来注册,注册代码如下:
        ConvertUtils.register(new DateConverter(), Date.class);


1. 前面讲的自定义类型转换器是基于 OGNL 的 DefaultTypeConverter 类并实现 convertValue() 方法,两个转换方向的逻辑都写在这一个方法中。而 Struts 2 为我们提供了一个 DefaultTypeConverter 的抽象子类 StrutsTypeConverter 来继承,并实现其中两个抽象方法 convertFromString() 和 convertToString(),这要简单易懂。对比 Struts 1 的转换器是要实现 org.apache.commons.beanutils.Converter 接口,以及它的 convert() 方法的。

2. 注意,上面的 convertFromString() 的第二个参数是一个字符串数组,所以可为请求的数组(如请求串为 ?u=1&u=2&u=3)定义转换器,Action 中相应的属性就应为数组或 List,这一方法的返回值也该为相应的类型(数组或List,要通过第三个参数 toClass 来判断是返回数组还是 List 了)。

3. 字符串(如 "user,pass") 转换成 Action 中的复合属性(如 User user) 前面是自定了类型转换器。除此之外,还可以 Struts 2 内置的 OGNL 表达式,更简单的转换,不用写转换器。例如,你的 Action 有属性 User user,只要在 jsp 页面的输入框命名为 user.name  和 user.pass 即可:
        <input type="text" name="user.name"/> 或用标签:<s:textfield name="user.name" label="用户名"/>
        <input type="text" name="user.pass"/>  或用标签:<s:textfield name="user.pass" label="密 码"/>
    提交后,Struts 2 即会帮你构造 User 对象(user = new User()),并赋上属性值(user.setName(),user.setPass()),{zh1} user 对象赋给 Action (xxxAction.setUser(user))。所以要明白有三个必备的东西:
        1) User 要用一个默认构造方法 2) User 要有对应 name 和 pass 的设置方法 setName() 和 setPass() 3) Action 要有 user 属性的设置方法 setUser(),getUser() 也是要的,至于功用后面能看到。
其实在 Struts 1 中也有这种用法,不过那是在 BeanUtils 中实现的。

4. 如果 Action 中的属性是 Map<String, User> users; 那么与此对应的表单写法就是:(用标签来写)
        <s:textfield name="users['one'].name" label="{dy}个用户名"/>
        <s:textfield name="users['one'].name" label="{dy}个密码"/>
        <s:textfield name="users['two'].name" label="第二个用户名"/>
        <s:textfield name="users['two'].name" label="第二个密码"/>
    应该不难想像,这个表单提交后,users  中存储的是什么吧!
    如果是对于 Action 中的  List 属性,List<User> users; 那么与此对应的表单写法就是:
        <s:textfield name="users[0].name" label="{dy}个用户名"/>
        <s:textfield name="users[0].name" label="{dy}个密码"/>
        <s:textfield name="users[1].name" label="第二个用户名"/>
        <s:textfield name="users[1].name" label="第二个密码"/>

5. 归纳前面3、4、5 几点,Struts2 的 Action 在设置每一个属性时都会 get 一下相应的元素 getUser() 或 getUsers()。
    对于 3,在设置 user.name 和 user.pass 之前都会 getUser() 来获取 user 属性,如果 user 为 null 就构造 User 对象,然后设置相应的值。假如声明的时候就已构造好 User 对象,如有其他属性如 age=18,并不会被覆盖。
   对于 4 和 5,也是在设置每一个属性前都会调用 getUsers() 判断声明的 Map 或 List 是否为 null,是则构造对应的 HashMap 或  ArrayList() 对象;接着根据 Key 或下标去获取相应位置的元素,如果不存在或为 null 则构造之,然后设置相应属性值。由此可见,若某元素的某个属性未重设值则保留原值,若原来Map或List 已有多个元素,也只会改变到 Key 或索引所对应元素的某个属性。对于 List 有可能出现跳空的情况,如页面只有索引不从 0 开始
         <s:textfield name="users[1].name" label="第二个用户名"/>
        <s:textfield name="users[1].name" label="第二个密码"/>
提交后就会发现,List 属性 users 的{dy}个元素为 null 了。同时如果尝试一下,你就会发现这里的 List 不能替代为数组 User[] users。
    这种样法,可在 Struts 1 中实现,但要略施些小节,见我的另一篇日志:提交多行数据到Struts的ActionForm的List属性中 ,行为表现xx一致,只是换到 Struts 2 中一切都不用自己操心。

6. 看第四点,Action 之所以知道该构造什么类型的元素xx是由泛型告诉它的。如果不用泛型(比如用的是 JDK1.4),Action 中仅仅声明的是 Map users; 或 List users; Action 该如何处理呢?它也不知道,只能够帮你构造出无类型的 HashMap 和 ArrayList(),填充不了元素。这就必须在局部类型转换的配置文件中来指定集合元素的类型。例如 Action 为 LoginAction,就要在 LoginAction-conversion.properties 中声明了,格式如下:

    #Element_xxx=复合类型,基中 Element 是固定的,xxx 为属性名
    #下面表示为 List 属性 users 的元素为 com.unmi.vo.User 类型
     Element_users=com.unmi.vo.User

    对于 Map,须分别指定 Key 的类型和 Value 的类型
    #Key_xxx=复合类型,基中 Key 是固定的,xxx 为 map 属性名,下面写成 String 都不行的
    Key_users=java.lang.String
    指定 Map 的 Value 的类型与指定 List 元素类型是一样的
    Element_users=com.unmi.vo.User

难怪 Struts 2 要与 1.5 以上 JDK  使用,泛型比配置来得方便。如果硬要用 1.4 JDK,就只有配置类型了,会多很多 conversion 文件的。在 提交多行数据到Struts的ActionForm的List属性中 中类型的确定由 AutoArrayList() 的构造参数完成。

7. Set 是无序集合,所以无法像 List 那样用数字下标来访问,幸好 Struts 2 可为其指定索引属性。例如,LoginAction 声明为 Set users; (这里好像用泛型只能省得了 Element_users 说明,KeyProperty_users 少不了)。则需在 LoginAction-conversion.properties 中写下:
    #指定 Set 的元素类型
    Element_users=com.unmi.vo.User

    #KeyProperty_集合属性名=集合元素的索引属性名,这里为 User 的 name 属性
    KeyProperty_users=name

此时提交页面这么写,{zh0}提交前能根据输入的用户名自动修动输入框的 name。
        用户名: <input name="users('scott').name"/>
        密 码: <input name="users('scott').pass"/>
显示的时候页面可用标签
        用户名: <s:property value="users('scott').name"/>
        密 码: <s:property value="users('scott').pass"/>
注意前面,访问 Set 元素是用的圆括号,而不同于 Map、List、数组是用中括号。我想一般也犯不着非要用 Set 而不用 List,Struts 2 中用 Set 比在 Struts 1 中似乎还麻烦。

8. Struts 2 内建了一批转换器:boolean、char、int、long、float、double 和它们的包装类型;Date,日期格式使用请求所在 Locale 的 SHORT 格式;数组,默认元素为字符串,其他类型则要转换每一个元素?(好像是一次性转换完成的); 集合,默认元素为字符串 XWorkList(String.class, Object[]),其他如 List<Integer> ids,类型为 XWorkList(Integer.class, Object[]),XWorkList 继承自 ArrayList。

9. 类型转换出错由 Struts 来帮你处理,在默认拦截器栈中提供了 conversionError 拦截器,不用你写一点代码逻辑。conversionError 在出错时将错误封装成 fieldError,并放在 ActionContext 中。你所要做的就是遵循它的规则,1) 你的 Action 要继承自 ActionSupport,2)在 struts.xml 中声明名为 "input" 的 result,出错时会在 input 逻辑视图显示信息。3)尽量用标签来写输入域(如<s:textfield name="number" label="数量"/>),这样转换出错后,就会像校验失败一样把错误信息显示在每个输入框上面(视模板而定),否则要手工用 <s:fielderror/> 输出在某处。
默认时输出错误信息为(比如是属性 number,输入的是字符串时):Invalid field value for field "number".你可以改变默认显示,在全局国际化资源文件中加上 xwork.default.invalid.fieldvalue={0}字段类型转换失败!。在某些时候,可能还需要对特定字段指定特别的提示信息,那么在名为 ActionName.properties 的局部资源文件中加上 invalid.fieldvalue.属性名=提示信息 (如 invalid.fieldvalue.number=数量格式错误)

10. {zh1}是集合属性转换错误时的显示,对于页面中的同名输入框,有多个出错误,如果手工用 <s:fieldError/> 只会显示一条错误,但要是输入页是用标签(如<s:textfield name="number" label="数量"/>),仍会在每一个出错的输入框上都提示。至此类型转换的内容也就完结了。



1. Struts 2 标签库以 OGNL 表达式为基础,对集合、对象的访问功能更强了。Struts 2 的标签用统一的前缀,一般为 "s",不再像 Struts1 那样分 <html:.../>、<bean:.../>、<logic:.../> 等多个命名空间的标签。如果在 JSP 2.0 之前用过 JSTL,也就知道什么是 OGNL 表达式的。

2. Struts2 提供了很多 Struts1 标签没有的东西,如日历、树型控件、Tab页等;同时可借助 DWR,有标签支持 Ajax,除此还提供了主题、模板,还允许在页面中使用自定义组件。Struts2 标签都定义在 URI 为 "/struts-tags" 的命名空间下,Struts2 的标签库功能很强大,使用起来却更简单。

3. 同 Struts1 相比,Struts2 的标签库不依赖于作何表现层技术,即大部分标签,不光能在 JSP 中用,还能在 Velocity 和 FreeMarker 等模板中用。Struts1 中要支持 EL,必须引入 struts-el.jar 和 JSTL 类库;而 Struts2 默认支持 OGNL、JSTL、Groovy 和 Velocity 表达式。

4. Struts2 的标签定义文件是在 Struts2-core-2.x.x.jar 的 META-INF 目录中的 struts-tags.tld。相比 Struts1 的标签定义是在 struts.jar 的 META-INF/tld 目录下的 struts-bean.tld、struts-html.tld等(Struts1 从 1.3 开始,包也分成了 struts-core-1.3.x.jar 和 struts-taglib-1.3.x.jar,所以 struts-bean.tld、struts-html.tld 等就在 struts-taglib-1.3.x.jar 的 META-INF/tld 目录下)。

5. Servlet 2.4 开始的应用在启动时会递归搜索 classpath 下的 tld 文件加载;Servlet 2.3 的应用在启动时好像会自动搜索 WEB-INF 目录下的 tld 文件加载,再之前的 Servlet 版本就必须在 web.xml 文件中加上  <taglib> 来指定 tld。

6. XWork 在原有的 OGNL 基础上增加了对 ValueStack 的支持。Struts2 的 Stack Context 需要多个“根”对象,其中 ValueStack 只是多个“根”对象的其中之一。如果要访问的属性属于根对象,则可以直接访问该属性,否则必须使用一个命名空间,如 #bar。
    ValueStack 是以属性名为 "struts.valueStack" 的 com.opensymphony.xwork2.util.OgnlValueStack 对象存在于 request 中的。

7. Struts2 中,系统的 ValueStatck 是 OGNL 表达式的{dy}个根对象(key=com.opensymphony.xwork2.util.ValueStack.ValueStack),如果最近的 Action 存在,则 Action 上下文是第二个根对象(key=com.opensymphony.xwork2.ActionContext.name,只是 Action 的名字而已)。可以用 <s:debug/>显示出 Stack Contenxt 和 Value Stack Contents。

8. 标准 OGNL 表达式是:"#" 表示从根开始,例如 request 是一个根
      <s:property value = "#request['struts.request_uri']" />  输出当前请求的 URI,因为 "struts.request_uri" 包含有 ".",不然也可以用 #request.xxx 的形式。
      如果,只写成属性名,说明是根对象的 name 属性,但 Struts2 会有多个根对象,会不明确,因此这个时候 <s:property value="${name}"/> 就会访问 ValueStack 中的对象的属性 name -- ValueStack 是 OGNL 表达式的一个根对象。

9. Struts2 提供了一个特殊的 OGNL 访问器,对 ValueStack 从上至下搜索匹配。例如,ValueStack 中 有两个实例 Animal(name,species) 和 Login(name,salary),括号中表示所含属性。如下的 Action 类
    public class LoginAction implements ModelDriven<Animal>{
       private Animal animal = new Animal();
       private String name = "Unmi";
       private String salary = "little";
       public Animal getModel(){
            return animal;
      }
       ...............................................
   }
   就会在 ValueStack 中压入两个对象,分别是  LoginAction 实例和 animal 实例,animal 在栈顶,那么
  <s:property value="species"/>    显示 animal 的 species 属性
  <s:property value="salary"/>    显示 LoginAction 实例 的 salary 属性
  <s:property value="name"/>    显示 animal 的 name 属性,因为自栈顶向下先匹配到 animal 的 name 属性
  此时,如果我需要获取 LoginAction 实例的 name 谁能告诉我如何取,如果已知 LoginAction 的实例名的话,还能
  <s:property value="?LoginAction?.name" />  这里怎么才能行得通呢?压栈也不带实例名称的啊!
  幸好,还可以通过索引来访问  ValueStack 中的对象
  <s:property value="[1].name"/>  显示 LoginAction 实例的 name 属性 "Unmi"。这样写就会从栈的第二个位置找起,首先找到的就是 LoginAction 实例了。

这一部分还有些含混不清,ValueStack 还能如何压入值。

10. {zh1},Struts2 还在 Stack Context 中放了一些命名对象,parameters、request、session、application、attr 来访问对应属性。如 request['name'] 或 request.name 访问请求 name 属性。特别说明一下 attr 对象,依次搜索 PageContext、HttpServletReqeust、HttpSession、ServletContext 中相应属性。


1. OGNL 中有生成 List 和 Map 的表达式,分别是:{e1,e2,e3,...} 和 #{key1:val1,key2:val2,...}。对集合 OGNL 提供了两个操作符 in 和 not in,如:
   <s:if test="'foo' in {'foo','bar'}" ... </s:if>            ---- not in 的用法当然是一样的了。
之外,OGNL 还允许通过某个规则取集合的子集
    ·? :取出所有符合条件逻辑的元素
    ·^:取出符合条件的{dy}个元素
    ·$:取出符合条件的{zh1}一个元素
请将上面这三个符号与正则表达式的表示法联系起来
例了:persons.relatives.{? #this.gender == 'male'}        //取出集合 persons 中所有 gender 属性为 'male' 的子集。
OGNL 还支持基本的 Lambda(λ) 表达式语法,不过好像有点复杂,暂不关顾。

2. Struts2 的各路标签已是面目全非了。
    <s:if test="exp">...</s:if><s:elseif test="exp">...</s:elseif><s:else>...</s:else> 对应了 java 的 if/else if/else
    <s:iterator.../> value 未指定是默认为 ValueStack 栈顶集合。id 为当前元素变量名。status 为 IteratorStatus 实例,包含奇还是偶行,当前索引,是否{dy}或{zh1}一条记录。
   
3. iterator 可以迭代 List、Set 和数组,也可以迭代 Map,用法如下:
    <s:iterator value="{'e1','e2','e3'}" id="name" status="st">   //这是用 OGNL 生成的 List
         <s:property value="name"/>           //也可以写成 <s:property value="#name"/>
           <s:if test="#st.odd">偶数位</s:if>
           // st 其他属必如:#st.count、#st.index、#st.even、#st.first、#st.last,前面的 # 号不能少
    </s:iterator>
   迭代 Map,用 key 和 map 对应
   <s:iterator value="#{'key1':'val1','key2':'val2','key3':'val3'}">
      <s:property value="key"/> | <s:property value="value"/>   //不能写成 #key 和 #value,对 OGNL 还不熟
   </s:iterator>

4. <s:append.../> 用于将多个集合拼成一个新集合,从而能用一个<s:iterator .../> 对多个集合迭代,也能拼 Map 的。<s:merge .../> 也是用来拼接集合,只是不像 <s:append .../> 那样依次保持着顺序,<s:merge .../> 是交错的。可以理解为前一个是深度拼接,后一个是广度拼接。

5. 在学习 <s:generator .../> 时我们能同时体验一下 Struts2 标签是如何操作栈的,当然这里指的是 ValueStack,而非 Java 的方法栈。
    <s:generator val="'1,2,3'" separator=",">  <!--字符串'123'以","分隔生成一个集合,并压栈-->
        <s:iterator>      <!-- 这里没有指定 value 属性,默认弹栈,并取一个元素压栈 -->             
           <s:property/> <!-- 也没有指定 value 属性,默认也是取栈顶元素 -->
        </s:iterator>     <!-- 迭代完成则从集合遍历的{zh1}一个元素出栈 -->
    </s:generator>      <!-- {zh1},自动出栈,generator 生成的集合出栈 -->
    汇编是基于寄存器操作的,而 Java 是基于栈的操作,Struts2 引入了存放在 request 中的 ValueStack 正好xx再现了 Java 的这一原始本性,并可使标签使用更简洁,灵活。Struts2 还有很多标签也有这个特性:
    1) 生成的新实例,压栈,标签结束后自动出栈
    2) 在未指定数据来源时,默认从栈顶取数据。
    不禁回想起 Struts1 的 <nested:root> 等 nested 标签要显示的声明操作的根元素,是多么麻烦的一件事。
    补充:如果指定了 <s:generator .../> 的 id 属性的话,同时还会把生成的集合放到 pageContext 中,key 就是 id 指定的值。

6. <s:subset .../> 是用来取集合子集的,可从 source (未指定则为栈顶集合) 集合的 start 位置起取 count 个元素。或者按自定义的 decider 条件,取符合条件的子集。你的 decider 要实现 SubsetIteratorFilter.Decider 接口的 decide(object element) 方法,符合条件的返回 true。此处不列出具体用法的实例。

7. <s:sort .../> 依据 comparator 指定的比较器,对 source (未指定则为栈顶集合) 集合排序。排序头的集合当然也是压入栈中,标签结束自动出栈。

8. <s:debug/> 会在页面生成一个 debug 链接,展开能看到 ValueStack  和 Stack Context 中的内容,该页面有显示用 #key 能获取到 Stack Context 中的值。<s:property .../> 在前面用很多次了,就相当于 Struts1 的  <bean:write .../>,value 未指定,输出栈顶值;若要输出的值为 null 时,指定了 default 属性则输出它;escape 指定是否忽略 HTML 代码,同 <bean:write .../> 的 ignore 属性。

9. <s:push .../> 用于将某个值压栈,标签结束后自动出栈,可方便某些操作,Struts1 的 <nested:root> 有类似功能。<s:set .../> 标签用于把某个值放入指定范围内,用 scope 指定,如 application、session、request、page、action。若未指定 scope 则放到 Stack Context 中;name 为新变量名;value 为欲处理的变量,未指定则取栈顶值。

10. 从前面的标签,你也许已经注意到,Struts2 常操作的几个数据结构有 ValueStack、pageContext 和 StackContext。例如:generator、sort、subset 等生成的新实例会压到栈顶,并在标签结束自动出栈;如果 sort、subset 未指定 source 源集合,则从栈顶取,iterator、property 也是一样的;若为 generator 指定了 id 属性,则生的集合会存到 pageContext 中,key 就是 id 对应值;如果为 bean 指定了 id 属性,则会把 bean 实例存入到 Stack Context 中,key 就是 id 对应值,Stack Conext 中的值可以用 #key 取得。其他标签类似的地方应该要注意到。

{zh1}再来一贴,理解 ValueStack 和 Stack Context:
   1)  ValueStack 可以用 request.getAttribute("struts.valueStack") 取得的一个 com.opensymphony.xwork2.util.OgnlValueStack 实例,它实现为一个栈,有 peek()、pop()、push(obj) 方法。
   2)  Stack Context 是在前面的 ValueStack 的上下中的一个 java.util.Stack 实例:
   //stack 为 ValueStack 实例,COMPONENT_STACK="__component_stack"
   // org.apache.struts2.components.Component.getComponentStack() 中的代码
   Stack componentStack = (Stack) stack.getContext().get(COMPONENT_STACK);


1. <s:action .../> 可以用来在 JSP 页面直接调用 Action,用 name 指定 Action,executeResult(默认为 false) 指定是否包含那个 Action 转向视图的内容;executeResult 会把视图的所有内容输出在标签位置。ignoreContextParams(默认为 false),是否将本页的请求参数传到那个 Action。

2. <s:bean .../> 用来实例化 JavaBean,标签内使用 <s:param name="" value=""/> 来指定属性。在标签内,bean 实例位于 ValueStack 顶端,标签结束则自动xx。如果指定的 id 属性,还会把该实例放到 Stack Context 中。name 属性必须,指定 Bean 类的全限名称。不知道复杂的属性该如何用 <s:param .../> 或其他方式来指定。

3. <s:date .../> 用来格式化输出日期,还可计算指定日期与当前的时差。name 是必须的,指定要输出的 Date 类型实例,format 指定格式。如果指定了 nice(默认为false) 属性为 true,则输出指定日期与当前的时差(样式:92 days, 9 hours ago),即使指定了 format 属性也如此。输出日期时若未指定 format 属性,则找资源文件中的 struts.date.format,不存在这个 key 就按 DateFormmat.MEDIUM 格式输出了,Struts1 也是采用一样的方式。
<% pageContext.setAttribute("now",new Date(107,12,23,13,23,24)); %>
<s:date name="#attr.now" format="yyyy年MM月dd日" nice="false"/>

再试试 <s:date name="#attr.now" format="yyyy年MM月dd日" nice="true"/> 的输出。

4. <s:include .../> 用来包含一个资源,JSP 或 Servlet 等,用 value 属性指定。标签内用 <s:param .../> 指定要向被包含资源传递的参数。
<s:include value="include-file.jsp">
    <s:param name="name" value="Unmi"/>
</s:include>

对 照    <jsp:include page="include-file.jsp?name=Unmi"/> 和 <bean:include id="" page=""/>。想见一下,它们大概都是用 RequestDispatcher.include(request,response) 实现的?

5. <s:param .../> 是服务于外层标签来指定参数的,如 bean、include、url 等。<s:url .../> 用来生成一个 URL 地址,value 或 action 属性来指定 url 地址值,用了 action 会在属性后自动补上 .action,并可用 method 来指定 Action 的方法。value 和 action 都未指定就相当于 "#" 了。includeParams 说明是否把当前页面的参数也包含到这个 url 上。用例子说明:
假如当前 url 是 http://localhost/TestStruts2/index.jsp?name=unmi,以下不同写法的输出
1)  <s:url value="Login.action">
        <s:param name="pass">password</s:param>
    </s:url>
2)  <s:url value="/Login.action" includeParams="none">
        <s:param name="pass">中国</s:param>
     </s:url>
3)  <s:url action="Login" includeContext="false" method="register">
        <s:param name="pass">password</s:param>
     </s:url>

...1) Login.action?name=unmi&pass=password
...2) /TestStruts2/Login.action?pass=%E4%B8%AD%E5%9B%BD
...3) /Login!register.action?name=unmi&pass=password
其实可能无论 encode 属性是 true 或 false,都会对参数进行编码的。对比 Struts1 的 <html:link>。

6. 数据标签还有 <s:i18n name=""/> 用于指定国际化资源文件的 baseName;<s:text name=""/> 用于输出国际化信息。

7. 下面要介入 UI 标签的使用了,但事先不得不提到 Struts2 的主题和模板。Struts2 所有的 UI 标签都是基于主题和模板的,主题和模板是 Struts2 所有 UI 标签的核心。模板是 UI 标签的外在表现形式。例如,当我们使用 <s:select .../> 标签时,Struts2 会根据对应 select 模板来生成一个有模板特色的下位框。如果为所有的 UI 标签提供了对应的模板,那么这一系列的模板就形成了一个主题。

8. 相较而言,Struts1 的 UI 标签可没这些东西,因为它们是直接与 HTML 相应元素挂勾的,所有的 UI 标签的样式要用 style 或 styleClass 来指定。

9. 因为模板是以主题的形式组织在一起的,所以为了界面的统一,我们应该选择特定主题,由主题来负责加责相应的模板,而不是强制要求使用某个模板来表现一个 UI 标签。

10. 设置主题的方法按优先级依次为:UI 标签的 theme 属性、外围的 form 标签的 theme 属性、从 page/request/session/application 找 "theme" 的属性值、配置在 struts.properties 或 struts.xml 中指定的 struts.ui.theme 常量值(默认为 xhtml)。一个原则:表单的主题,设置 form 标签的 theme;用户会话的主题,在 session 中设置 theme 变量;整个应用的主题,就用 struts.ui.theme 常量来实现。


1. 模板目录->主题目录->主题(模板文件) 这是模板/主题的目录组织方式。以实际为例,打开 struts2-core-2.x.x.jar 可以看到里面有一个 template,在 template 下有5个目录 ajax、css_xhtml、simple、xhtml 和 archive,其中前四个分别是 ajax、css_xhtml、simple、xhtml 主题的目录,每个主题目录中有各自的模板文件,主要是 ftl 文件,还有 css 和 js 文件。{zh1}一个 archive 是归档的主题目录,其下又有 ajax、simple、xhtml、模板文件是 .vm 文件。由此可知 Struts2 大力推荐的模板语言是 FreeMarker,而不是 Velocity,以后要好好看看 FreeMarker,只知道 FreeMarkder 更 XML 化。

2. 上面看到模板目录名是 template,是 struts2-core-2.x.x.jar 中,其实目录名是由 struts.ui.templateDir 常量来指定的,只是默认值是 template。意味着 Struts 2 从 Web 应用的 template 目录或 CLASSPATH 的 template 目录(包) 中依次加载特定的模板文件。

3. 比如我们使用一个 select 标签,且指定主题为 xhtml,则加载模板文件的顺序为 (1) Web 应用/template/xhmlt/select.flt    (2) CLASSPATH/template/xhtml/select.ftl。Struts2 默认是用的 FreeMarkder 模板技术,可设置常量 struts.ui.templateSuffix 来改变默认的模板技术,可选值有 ftl、vm、jsp。但是对于 vm 和 jsp 要自己提供完整的实现,Struts2 可没帮你做这些。

4. 有时候我们想要自定义主题,如你希望输入框前的标签显示红颜色,你不想要校验错误提示在输入框正上方而是右边。当然你可以修改 struts2-core-2.x.x.jar 中 template 下某个主题的模板文件,或拷一份到 Web 应用目录的 template 目录修改要定制的模板,这样做总有些不爽。Struts2 还支持两种更灵活的主题定制方式。包装和继承现有主题,可以同时使用。
最简单的主题定 制方式是利用主题模板的加载优先级,把自定义的模板文件放在优先级高的目录,比如放一个 text.ftl 在 WEB-INF/classes/template/xhtml/ 目录下,它将覆盖掉 struts2-core-2.x.x.jar 里的 template/xhtml/text.ftl 的定义。

5. 先看一个包装的例子,在 xhtml 下的 combobox.ftl 的内容如下:
<#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
<#include "/${parameters.templateDir}/simple/combobox.ftl" />
    <#include "/${parameters.templateDir}/xhtml/controlfooter.ftl" /><#nt/>
它就是对 simple/combobox.ftl 的基础上包装上一个 controlheader.ftl 和一个 controlfooter.ftl,包装的局限性是你仍然是要为每一个 UI 组件提供单独模板文件,即全套的。
和设计模式中的包装(装饰)模式如出一辙,如 BufferedInputStream 包装了 InputStream,但在 BufferedInputStream 提供了全套的和 InputStream  一样的操作方法。

6. 再说继承来自定义主题,如果简单改变个别 UI 的风格,继承就是最为高效的了。此继承与 Java 的继承(即extends) 也是一马事。要改变或要新加的用自己定义的,其他的延用父主题的。举个例子,自定义一个名为 custom 的主题继承自 xhtml,只改变 select 标签的风格,你要做的就是编辑自己的一个 select.ftl 放在 WEB-INF/classes/template/custom 下,并在此目录下放一文件 theme.properties,内容是:
#指定该主题以 xhtml 为基础进行扩展
parent=xhml

使 用可指定给 UI 标签,例如 <s:form name="aa" theme="custom" ...,效果就会是 form 下的 select 使用了在 custom 目录下自定义的 select.ftl,其他的直接使用父主题 xhtml 中的模板文件。Struts2 提供的 ajax 主题就是继承自 xhtml 主题的。

7. 简单说一下 Struts2 的内建主题,包括 simple、xhtml、css_xhtml 和 ajax。simple 主题不用多说,比Struts1 的 html 标签还弱些,只对应简单的 html 元素,不生成额外内容。xhtml 是默认主题,是对 simple 主题的包装和扩展(也就是继承),该主题下有一个 head.ftl 用来导入 javascript 类库(如 dojo)。xhtml 在 simple 的基础上增加了以下特性:
    1) 针对 HTML 标签(如 textfield和select标签) 使用标准的两列表格布局
    2) 每个 HTML 标签有 label 属性,默认左边显示,可通过 labelposition 属性设定位置
    3) 自动输出后台校验错误或 javascript 前端校验错误

8. 继续 Struts2 的内建主题的话题。css_xhtml 主题是对 xhtml 的扩展,显示是加入了 css 样式控制特性。ajax 主题是对 xhtml 主题的扩展,在 xhtml 主题的每个标签增加了 ajax 的支持(以 Djoj 和 DWR 为基础)。所增 Ajax 特性有:
    1) Ajax 方式的客户端校验
    2) 远程表单的异步提交
    3) 高级 div 标签,允许局部更新
    4) 高级 a 标签,允许动态加载并执行远端的 javascript 代码。
    5) 提供支持 ajax 的 tabbedPanel
    6) 提供“富客户端” 模型的 pub-sub 事件模型

9. 先前有网友问过我,他用了 Struts2 的校验,但是错误输出是在输入框的上方,但希望错误信息是显示在输入框的右方,该如何做。当时我只告诉了他要修改模板文件,也只是大概告诉了他是在某个 template 目录下的一个 ftl 文件,因那时具体操作自己也不太清楚。现在知道了线索,但实际修改还是很麻烦的。
    输入框 <s:textfield .../> 默认是用的 xhtml/text.flt 模板,text.ftl 包装了 smple/text.ftl,错误信息可以追溯发现是在 controlheader-core.ftl 中定义显示的,所以你可以把 xthml/text.ftl 和 controlheader-core.ftl 拷到 WEB-INF/classes/template/xhtml 目录中进行修改,WEB-INF/classes/template/xhtml 中的模板文件是优先于 struts2-core-2.x.x.jar 里的 template/xhtml 目录中的模板文件加载。

10. 看有些地方只笼统介绍说:所有表单元素都存在一个特殊的属性:form,这个属性引用元素所在表单,通过该属性实现表单元素与表单间的交互,例如可通过 ${parameters.form.id} 访问表单的 ID。对这句话我只是感到一头雾水,查看像 <s:textfield .../> 等标签并无 form 属性,用 <s:textfield value="${parameters.form.id}" name="aa"/> 也看不到输出所在表单的 ID。去网上找找,才知道前面那句话有出入,其实说的是在主题模板文件里的用法,打开一些主题模板文件,如 combobox.ftl 或 controlheader-core.ftl 文件,你就能看到许多的 parameters 的表示法-- parameters.required、parameters.id。想见一下 parameters 属性才是根本,它代表了表单元素的属性集,parameters 说来还有点像 this,this.id、this.form.id、this.required 等等...





郑重声明:资讯 【Struts2 学习笔记- jonsion - JavaEye技术网站】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——