Struts2

来源:互联网 发布:查看文件大小 linux 编辑:程序博客网 时间:2024/06/05 04:40
 

Struts2 学习笔记(一)
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 来充当,完全与 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 的支持。还提供了更友好的验证功能。

 

 

Struts2 学习笔记(二)
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 可让你完全用配置来校验表单的,让你的 Action 还是那个 Action (继承 ActionSupport,无须实现 validate() 方法),只需写一个 ActionName-validattion.xml 文件扔在与 Action 同位置的目录中即可,这实质就是用的基于 XWork 的验证框架。

 

 

Struts2 学习笔记(三)
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 中。

 

 

Struts2 学习笔记(四)
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 里哪个属性是用于封装请求参数的属性,那个属性是封装处理结果的属性。对系统而言,封装请求参数的属性和封装处理结果的属性是完全平等的。
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 属性值产生冗余。

 

 

Struts2 学习笔记(五)
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 完全相同(如 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 .../> 意义与效果完全对应于 Struts1 的 <forward .../>、<global-forward .../> 。

 

 

Struts2 学习笔记(六)
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);

 

 

Struts2 学习笔记(七)
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()),最后 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="第一个用户名"/>
        <s:textfield name="users['one'].name" label="第一个密码"/>
        <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="第一个用户名"/>
        <s:textfield name="users[0].name" label="第一个密码"/>
        <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 的第一个元素为 null 了。同时如果尝试一下,你就会发现这里的 List 不能替代为数组 User[] users。
    这种样法,可在 Struts 1 中实现,但要略施些小节,见我的另一篇日志:提交多行数据到Struts的ActionForm的List属性中 ,行为表现完全一致,只是换到 Struts 2 中一切都不用自己操心。
6. 看第四点,Action 之所以知道该构造什么类型的元素完全是由泛型告诉它的。如果不用泛型(比如用的是 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
此时提交页面这么写,最好提交前能根据输入的用户名自动修动输入框的 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. 最后是集合属性转换错误时的显示,对于页面中的同名输入框,有多个出错误,如果手工用 <s:fieldError/> 只会显示一条错误,但要是输入页是用标签(如<s:textfield name="number" label="数量"/>),仍会在每一个出错的输入框上都提示。至此类型转换的内容也就完结了。

 

 

Struts2 学习笔记(八)
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 表达式的第一个根对象(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. 最后,Struts2 还在 Stack Context 中放了一些命名对象,parameters、request、session、application、attr 来访问对应属性。如 request['name'] 或 request.name 访问请求 name 属性。特别说明一下 attr 对象,依次搜索 PageContext、HttpServletReqeust、HttpSession、ServletContext 中相应属性。

s-if.jsp 的异常引出对 EL 和 OGNL 的思考:
我用 MyEclipse 建了一个 Web 项目,配置了能支持 Struts2。在验证标签应用时,把 《Struts2权威指南--基于WebWork核心的MVC开发》一书的例子 10.3 controlTag 中的 s-if.jsp 拷入当前应用。该 jsp 文件的内容是:想像一下我访问它时得到了什么样的结果?

<%@ page contentType="text/html; charset=GBK" language="java"%>    
<%@taglib prefix="s" uri="/struts-tags"%>    
<html>    
    <head>    
        <title>s:if标签测试</title>    
    </head>    
    <body>    
        <s:set name="age" value="29"/>    
        <s:if test="${age > 60}">    
            老年人     
        </s:if>    
        <s:elseif test="${age > 35}">    
            中年人     
        </s:elseif>    
        <s:elseif test="${age > 15}" id="wawa">    
            青年人     
        </s:elseif>    
        <s:else>    
            少年     
        </s:else>    
    </body>    
</html>  
<%@ page contentType="text/html; charset=GBK" language="java"%> 
<%@taglib prefix="s" uri="/struts-tags"%> 
<html> 
 <head> 
  <title>s:if标签测试</title> 
 </head> 
 <body> 
  <s:set name="age" value="29"/> 
  <s:if test="${age > 60}"> 
   老年人  
  </s:if> 
  <s:elseif test="${age > 35}"> 
   中年人  
  </s:elseif> 
  <s:elseif test="${age > 15}" id="wawa"> 
   青年人  
  </s:elseif> 
  <s:else> 
   少年  
  </s:else> 
 </body> 
</html>

然后把该应用 TestStruts2 部署到 Tomcat 5.0.28 下,启动 Tomcat,地址栏输入 http://localhost:8080/TestStruts2/s-if.jsp,确定,执行结果让我傻眼了,任凭我百般刷新,也都是赫然显示着:
少年
按理应显示 "青年人" 才对啊,代码上看不出什么问题啊!难道是原本的 controlTag 就有问题?于是把 controlTag 复制到 Tomcat 的 webapps 目录,再访问http://localhost:8080/controlTag/s-if.jsp 时,页面显示为:
青年人
这更让我迷惑。这两个应用到底有何差异?先是采用一种十分愚笨的方法,先把 controlTag 精简出一个最小可用的目录,然后看 TestStruts2 中那块有疑问的地方就改成与 controlTag 一样的配置,strtus.xml 改一样了,lib 下的 jar 也替换掉了,web.xml 中的 <filter.../> 和 <filter-mapping.../> 配置也改一样了,结果发现仍旧为 "少年"。但要是把 controlTag 的整个 WEB-INF 覆盖过去,又是可以的,继而又做了不少尝试,Tomcat 不停的重启,还是未找到问题所在。看来真的似乎是没辙了。

好吧,什么叫做君子性非异也,善假于物也!不得已安装上 BeyondCompare,两个应用一对比就发现原来还是 web.xml 内容不同。 有人要问了,前面不是说已经把 web.xml 的内容改成一样的吗?是的,但单就犯了一个错误,web.xml 文件的声明处没注意:

controlTag 的 web.xml 的声明处是:

<?xml version="1.0" encoding="GBK"?> 
<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/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 
<?xml version="1.0" encoding="GBK"?>
<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/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

TestStruts2 的 web.xml 的声明处是:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"   
    "http://java.sun.com/dtd/web-app_2_3.dtd"> 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

看到这一差别才轰然醒悟过来,原来是在 Servlet 2.4 与 Servlet 2.3 的差别。Tomcat 5.0.28 是能支持 Servlet2.4/JSP 2.0 的,JSP 2.0 是可以用 EL 表达式,而之前版本是不行的,正式这一声明指示着容器应如何解析和编译 JSP 的,而产生未曾意料到的结果。

原本用 MyEclipse 新建的 Web 应用是采用的 Servlet 2.4 的声明,只是我有一次测试 Struts2 在 Servlet 2.3 下的表现时忘了改回来。喜或悲,着实浪费了不少时间,却也让我真实见证到了一个问题,也为在 Servlet2.3/JSP 1.2 下使用 Struts2 的企图撤了一道卡。见我的另一篇日志,一直在追寻着这个问题的答案:在仅实现到 Servlet 2.3/JSP 1.2 规范、JDK为1.4 的容器中用 Struts 2 会有什么问题?

如果硬是要在 Servlet 2.3 下使用 Struts2 运行这个 JSP 页面,那么就要把 EL 改成 OGNL 表达式,把原来 s-if.jsp 中的三个测试表达式行分别改为:

<s:if test="${age > 60}">                             改成  <s:if test="#age > 60">
<s:elseif test="${age > 35}">                       改成  <s:elseif test="#age > 35">
<s:elseif test="${age > 15}" id="wawa">      改成 <s:elseif test="#age > 15" id="wawa">

再次浏览 http://localhost:8080/TestStruts2/s-if.jsp 就保证你看到的是 "青年人" 了,因为 OGNL 是不依赖于 Servlet 容器的,就像 Struts 1.x 的 struts-el 和 JSTL 的 EL。

既然误入了藕花深处,何不来惊起一滩鸥鹭呢?我们深入对比一下,web.xml 中不同的 Servlet 版本声明时编译出的 jsp 源文件的差异。

1. 在 Servlet 2.3/JSP 1.2 (web.xml 声明使用 web-app_2_3.dtd) 下编译出的 JSP 对应源代码
    1)  EL 表达式 ${age > 60} 对应代码为:执行页面显示 少年 ×
          _jspx_th_s_if_0.setTest("${age > 60}");
    2) OGN 表达式 #age > 60 对应代码为:执行页面显示 青年人 √
          _jspx_th_s_if_0.setTest("#age > 60");

2. 在 Servlet 2.4/JSP 2.0 (web.xml 声明使用 web-app_2_4.xsd) 下编译出的 JSP 对应源代码
    1) EL 表达式 ${age > 60} 对应代码为:执行页面显示 青年人 √
          _jspx_th_s_elseif_0.setTest((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${age > 35}", java.lang.String.class, (PageContext)_jspx_page_context, null, false));
    2) OGN 表达式 #age > 60 对应代码为:执行页面显示 青年人 √
          _jspx_th_s_if_0.setTest("#age > 60");

由此对比,我们可以发现,使用 OGNL 表达式,在两种版本 web.xml 声明中编译出的 JSP 对应源代码都是一样的,其中的 OGNL 表达式后续由 ognl-2.x.x.jar 来处理。在 Servlet 2.3/JSP 1.2 下因为不支持 EL,所以这个 EL 表达式被忽略,只当是个普通字符串了,而在 Servlet 2.4/JSP 2.0 下的 EL 表达式会交给 PageContextImpl.proprietaryEvaluate 去处理。

在 JSP2.0 中有一个页面指令 <%@ page isELIgnored="true"%> ,如果加上这一指令,即使在 Servlet 2.4/JSP 2.0 中 EL 也被忽略,编译出的 JSP 对应源代码同 Servlet 2.3/JSP 1.2 也一样了。

所以要在仅支持 Servlet 2.3/JSP 1.2、JDK为1.4 的容器中应用 Struts2 的第二条准则是:EL 换成 OGNL 表达式。前面有遇到过第一条准则是解决在用 1.4 没有泛型的情况下 Action 中集合元素的类型识别问题 Unmi 的 Struts2 学习笔记(七),是要在 ActionName-conversion.properties 中用 Element_xxx 和 Key_xxx 来说明。

对于取pageContext、parameters、request、session、application 等处的属性值(假如有 name 属性)时我们用的 EL 表达式分别是:

${pageScope.name}、${param.name}、{$requestScope.name}、{$sessionScope.name}、{$applicationScope.name}

那么对应的 OGNL 的解决方案分别是:

<s:peroperty value="#attr.name"/>
<s:property value="#parameters.name"/>
<s:property value="#request.name"/>
<s:property value="#session.name"/>
<s:property value="#application.name"/>
<s:textfield name="name"  value="%{#parameters.name}"/>

说明,attr 如果可以访问到,则访问 pageContext,否则将 依次搜索 pageContext、request、session、application 相应值,所以可用来访问 pageContext 中的值,可替代 EL 的 ${pageScope.name}。

在前面提到过,Servlet 2.3/JSP 1.2 的容器中可以借助于 struts-el (针对 struts 1.x) 和 JSTL 来支持 EL 表达式,那么对于 Struts 2 是否能有所借鉴呢?其实不太可行。

struts-el 是对 struts 1.x 的全套标签重新实现,并有少部分 EL 不支持,Struts2 能这么做吗?当然是可以做到,但 Struts2 本身因为引入了 OGNL 可替代方案,所以它觉得没这个必要性,如果你愿意的话可以仿照 struts-el 自己来实现,不过会不伦不类的,大于号 ">" 要写成 "gt" 种种。

JSTL 确提供了对 EL 很好的支持,但你必须用 JSTL 的标签 <c:out value="${requestScope.name}"/>,没办法应用到 Struts2 的标签当中,所以也是徒劳无益。所以还是直接用 OGNL 表达式吧。

 

 

 

Struts2 学习笔记(九)
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 还允许通过某个规则取集合的子集
    ·? :取出所有符合条件逻辑的元素
    ·^:取出符合条件的第一个元素
    ·$:取出符合条件的最后一个元素
请将上面这三个符号与正则表达式的表示法联系起来
例了: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 实例,包含奇还是偶行,当前索引,是否第一或最后一条记录。  
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>     <!-- 迭代完成则从集合遍历的最后一个元素出栈 -->
    </s:generator>      <!-- 最后,自动出栈,generator 生成的集合出栈 -->
    汇编是基于寄存器操作的,而 Java 是基于栈的操作,Struts2 引入了存放在 request 中的 ValueStack 正好完美再现了 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 取得。其他标签类似的地方应该要注意到。
最后再来一贴,理解 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);

struts.xml 配置中 namespace 的使用
Struts2 的 struts.xml 中是分 package 配置的,可以为 package 设置 namespace 属性,如

<package namespace="/secure"   ....>
    ......
</package>

如果没有指定 namespace 属性,默认 namespace 是 ""。使用 namespace 可以方便于按不同目的规划对应用的访问规则。比如不同 namespace 下配置了不同的拦截器就可以实现权限的控制,如 "/secure" 下已登陆用户才能访问,"/public" 下可公开访问的。

配置了 namespace 直接就是反应在访问 URL 上,例如 namespace="/secure"  name="test" 的 action

 <package namespace="/secure"   ....>
       <action name="test"  ....
</package>

访问它的 URL 就是 http://ip:port/context/secure/test.action,那如果在 namespace "/secure" 下没有 test action 会出现什么情况呢?Struts 还会尝试在默认 namespace,即 "" 下找 test。

再举个例子,URL 是 http://ip:port/context/some/path/test.action 时,如果在 "/some/path" namespace 下找不到 test action,也是到 "" (default namespace) 下找 test action,但不会去 "/some" 下找的。

用标签 <s:url value="/secure/test.action"/>  对应页面源文件是 /context/secure/test.action

稍有麻的就是 <s:form action="/secure/test.action" .... 对应的源文件是 <form action="/context/secure/test.action" ...

但是后台会有警告:

警告: No configuration found for the specified action: '/secure/test.action' in namespace: ''. Form action defaulting to 'action' attribute's literal value.

Struts2 把 action 属性值当整一个 Action Name 了,但这也不影响使用,这个 URL 正好能与 (package namespace) + (action name) 合上拍。

但是对于使用了动态方法调用(struts.enable.DynamicMethodInvocation = true)就没这么幸运了。很容易想当然的
 
<s:form action="/secure/test!update.action" ....  生成的 HTML 源文件却是 action="/TestStruts2/om/test"

同时后台的警告信息是:

警告: No configuration found for the specified action: '/secure/test' in namespace: ''. Form action defaulting to 'action' attribute's literal value.

很显然对于这个 action="/TestStruts2/om/test",提交时是会得到 HTTP Status 404 - /context/secure/test  错误。

正确的用法是 <s:action...> 也有一个 namespace 属性,对了,就是

<s:form namespace="/secure" action="test!login">  生成的 HTML 源文件是:<form action="/TestStruts2/om/test!login.action" ....>

我们要的就是这个。

如果不配置 namespace 属性,我们能不能在访问 action 时也用上目录层次呢?可以,那是在 struts1 习惯的做法,配置 <action name="secure/test" ....> name 中使用斜杠,但在 Struts2 中 Action Name 中使用斜杠需要设置

struts.enable.SlashesInActionNames=true                      默认为 false

可是 Struts2 大概不赞同这种做法,力挺 namespace 的作用。

对于上面使用了斜框的 Action Name,<s:form 中的写法要用

<s:form action="secure/test">                 生成 HTML 源文件:<form action="/context/secure/test.action" .....

<s:form action="secure/test!update">            生成 HTML 源文件:<form action="/context/secure/test!login.action" .....

上面的 action 后加不加 .action 无所谓,只是要保证 <s:form>  的 action 属性一定要与 struts.xml 中的 <action> 的 name 匹配上,如果你自作多情的在前面加个斜杠,如写成了
 
<s:form action="/secure/test!update"> 、 <s:form action="/secure/test">  或者 <s:form action="/secure/test!update.action">   生成的 HTML 源文件就都成了:<form action="/context/secure/test" .....

这也是从 Struts1 带来的弊病,因为 Struts1 中 <html:form> action 属性对应的是 <action> 的 path,而 Struts2 中 <s:form> 的 action 属性对应的是 <action> 的 name;name 要完全匹配,path 可以加些层次。

 

 

 

 

 

 

 

 

 

 

 

 


 

原创粉丝点击