运行时,{0}和{1}将被 ww:param 指定的参数值替换,显示结果如下:
欢迎来自 192.168.0.2 的用户 Erica
此外,在 UI Tag 中,我们还可以通过 getText 方法读取国际化资源,如对于刚才的登录 界面,如果要求对于输入框的标题栏也实现国际化支持,则可对表单中的内容进行如下改 造:
<ww:i18n name="'messages'">
<ww:text name="'welcome'">
<ww:param>192.168.0.2</ww:param>
<ww:param>Erica</ww:param>
</ww:text>
<ww:textfield label="getText('username')"
name="'model.username'"/>
<ww:password label="getText('password')"
name="'model.password'" />
<ww:submit value="getText('submit')" align="center"/>
</ww:i18n>
同时在资源文件添加 username,password 和 submit 对应的键值配置即可。
除了页面上的国际化支持,我们注意到,另外一个可能影响到用户界面语种问题的内 容就是 Validator,我们在 Validator 中定义了校验失败时的提示信息,如:
<field name="model.username">
<field-validator type="required">
<message>Please enter Username!</message>
</field-validator>
</field>
这里的 message 节点,定义了校验失败时的提示信息,对于这样的提示信息我们必须也 为其提供国际化支持。
Validator 中国际化支持通过 message 节点的 key 属性完成:
<field name="model.username">
<field-validator type="required">
<message key= ”need_username” >
Please enter Username!
</message>
</field-validator>
</field>
这里的 key 指定了资源文件中的键名,显示提示信息时,Webwork 将首先在资源文件 中查找对应的键值,如果找不到对应的键值,才以上面配置的节点值作为提示信息。
Validator 对应的资源文件,与 Action 校验配置(这里就是 LoginAction_validation.xml)
位于同一路径,命名方式为“<ActionName><_LOCALE>.properties”,这里对应的就是
“LoginAction_zh_CN.properties”。
对应上例,只需在资源文件中配置 need_username 键值,Webwork 在运行期就会自动从 此资源文件中读取数据,以之作为校验失败提示信息。
Webwork2 in Spring
Spring MVC 在设计时,针对 Webwork 的不足提出了自己的解决方案。不过,设计者一 旦脱离实际开发,开始陷入框架本身设计的完美化时,往往容易陷入过度设计的陷阱。
请原谅这里笔者对 Rod Johnson 及其开发团队的一点善意批评。Rod Johnson 开始脱离 实际开发,上升到框架设计时,如同大多数框架开发者一样,完美化的思想充斥了设计过 程,就 Rod Johnson 针对 Struts 和 Webwork 的评论来看,其注意力过多的集中在设计上的 完善,而忽略了实际开发中另外一个重要因素:易用性。
就 Spring MVC 本身而言,设计上的亮点固然无可非议,但在设计灵活性和易用性两者 之间,Rod Johnson 的天平倒向了完美的设计。这导致 Spring MVC 使用起来感觉有点生涩,
使用者赞叹其功能强大的同时,面对复杂的开发配置过程,难免也有点抓耳挠腮。
随着角色的转变(一个应用开发者到一个框架设计者),思考角度必然产生一些微妙的 变化,希望 Rod Johnson 在 Spring MVC 后继版本的开发中,能更多站在使用者的角度出发,
针对 Spring MVC 的易用性进行进一步改良。
就笔者在实际项目开发中的感觉来看,Webwork2 虽然也并不是及易上手,但在一定程 度上,较好的兼顾了易用性。其代价是设计上不如 Spring MVC 完美,但我们开发中节省的 脑细胞,应该可以弥补这一缺陷。
就 MVC 框架设计的角度来看,Webwork2 在目前的主流实现中(Struts、Webwork、Spring MVC)恰恰处于中间位置。其设计比 Struts 更加清晰,比 Spring 更加简练。
而从使用者角度而言,其接受难度也处于中等位置,比 Struts 稍高(Webwork 中也引入 了 DI 等新的设计思想导致学习过程中需要更广泛的技术知识),比 Spring MVC 难度稍低。
而正是这一平衡点的把握,为 Webwork2 提供了与其他竞争对手一较高下的资本。
那么,Webwork 和 Spring Framework 的关系是怎样?
笔者曾经参与过一些讨论,很多开发者的观点是“如果选择了 Webwork,就不必再引 入 Spring,两种框架同时使用必将导致认识上的冲突和技术感觉的混杂。”
然而,这里,正如之前所说,Spring 是一个高度组件化的框架。如果选择了 Webwork 作为开发基础框架,那么 Spring MVC 自然不必同时使用,但 Spring Framework 中的其他 组件还是对项目开发颇有裨益。
站在 Web 应用层开发的角度而言,Spring 中最重要的组件,除了 MVC,还有另外一个 令人欣赏的部分:持久层组件。持久层封装机制也许是 Spring 中应用级开发最有价值的部 分,就笔者的视野来看,目前无论商业还是开源社区,尚无出其右者。
这里想要表达的意思就是:Webwork+Spring(Core+Persistence+Transaction Management)
也许是目前最好的 Web 开发组合。对 Web 应用最重要的技术组成部分(MVC、持久层封装、
事务管理)在这个组合中形成强大的合力,可以在很大程度上提高软件产品的质量和产出
效率。
当然,局限于笔者的知识范围,并不能保证这句话就一定正确,不同的出发点,固然 有不同的看法。崇尚简单至上的方案(JSP+JavaBean+JDBC),以及皇家正统的企业级策略
(JSP+SLSB+CMP),在不同的出发点上,也都是不错的选择。这里是笔者的看法,供大家 参考。
下面我们就 Webwork+Spring(WS)组合的应用方式进行探讨。
首先来看,WS 组合中,Webwork 和 Spring 各司何职?
对一个典型的 Web 应用而言,MVC 贯穿了整个开发过程。选用 Webwork 的同时,也 就确定了 MVC 框架的实现。
MVC 提供了纵向层面的操控。也就是说,Webwork 将纵向贯穿 Web 应用的设计,它 担负了页面请求的接收、请求数据的规格统一、逻辑分发以及处理结果的返回这些纵向流 程。
Spring 则在其中提供横向的支持。Model 层的情况比较典型,Webwork 将请求分发到 Action 之后,所能做的事情就是等待 Action 执行完毕然后返回最后的结果。至于 Action 内 部如何处理,Webwork 并不关心。而这里,正是 Spring 大展身手的地方。
场景有点类似在 Pizzahut 用餐,伺服人员(Webwork)负责接受客户定餐(请求),并将 用户口头的定餐要求转化为统一的内部数据格式(Pizzahut 订单),然后将订单递交给厨师 制作相应的餐点(执行 Action),之后再从厨房将餐点送到客户餐位。而厨师具体如何操作,
伺服并不参与。
定单传递到厨师手上之后,厨师即按照烹饪流程(业务逻辑)开始制作餐点,烹饪的 过程中,厨具必不可少,厨具可以选用乡间的柴灶、锅、碗、瓢、盆五件套,也可以选择 自动化的配套厨具。
相信大家也已经明白我的意思,Spring 就是这里的自动化配套厨具,没有它固然也可以 使用传统工具作出一份点心,但是借助这样的工具,你可以做的更好更快。
下面我们通过一个实例来演示 WS 组合的应用技术。
还是上面的用户登录示例。我们使用 Webwork 作为 MVC 框架,而在逻辑层(Action 层面),结合 Spring 提供的事务管理以及 Hibernate 封装,实现一个完整的登录逻辑。
界面部分并没有什么改变,主要的变化发生后台的执行过程。
第一个问题,Webwork 如何与 Spring 相融合?
假设 LoginAction 中调用 UserDAO.isValidUser 方法进行用户名、密码验证。最直接的 想 法, 可能就 是在 LoginAction 中 通 过代 码获取 ApplicationContext 实 例 ,然后 通 过 ApplicationContext 实例获取对应的 UserDAO Bean,再调用此实例的 isValidUser 方法。类似:
……
ApplicationContext ctx=new
FileSystemXmlApplicationContext("bean.xml");
UserDAO userDAO = (UserDAO) ctx.getBean("userDAO");
if (userDAO.isValidUser(username,password)){
……
if (userDAO.isValidUser(username,password)){
……
};
……
userDAO 自动由容器提供,而无需代码干涉。
这并不难实现,只是需要追加一个类库,以及一些配置上的修改。7
首 先 下 载
http://www.ryandaigle.com/pebble/images/webwork2-spring.jar
, 并 将 其 放 入 WEB-INF\lib。webwork2-spring.jar 中包含了 Spring 与 Webwork 融合所需的类库。
修 改 web.xml , 为 Web 应 用 增 加 相 应 的 Spring ContextLoaderListener 以 及 webwork2-spring.jar 中的 ResolverSetupServletContextListener 配置。如下:
……
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>
com.atlassian.xwork.ext.ResolverSetupServletContextListener
</listener-class>
7
</listener>
……
修改 xwork.xml,为 LoginAction 配置外部引用关系:
<xwork>
<include file="webwork-default.xml" />
<package name="default" extends="webwork-default"
externalReferenceResolver="com.atlassian.xwork.ext.SpringServl etContextReferenceResolver">
<interceptors>
<interceptor name="reference-resolver"
class="com.opensymphony.xwork.interceptor.ExternalReferencesIn terceptor" />
<interceptor-stack name="WSStack">
<interceptor-ref name="params" />
<interceptor-ref name="model-driven" />
<interceptor-ref name="reference-resolver" />
</interceptor-stack>
</interceptors>
<action name="login" class="net.xiaxin.action.LoginAction">
<external-ref name="userDAO">
userDAOProxy
</external-ref>
<result name="success" type="dispatcher">
<param name="location">/main.jsp</param>
</result>
<result name="loginfail" type="dispatcher">
<param name="location">/index2.jsp</param>
</result>
<result name="input" type="dispatcher">
<param name="location">/index2.jsp</param>
</result>
<interceptor-ref name="WSStack" />
</action>
</package>
</xwork>
可 见 , interceptors 中 增 加 了 一 个 用 于 获 得 外 部 引 用 的 拦 截 器 ExternalReferencesInterceptor。我们将其与 params、model-driven 组合为一个拦截器序列用 于简化之后 Action 中的拦截器配置。
“login”中增加了一个外部引用“userDAO”,其值为 userDAOProxy,这是 Spring 中 userDAO Transaction Proxy 实例的引用名。
通过以上配置,我们实现了 Webwork、Spring 之间的融合。Webwork 将在运行期从 Spring Context 中获取资源引用,而 Spring 则将对此资源进行管理,并提供相关的调度服务(事务 管理等)。
下面,我们在 LoginAction.java 中加入对应的 userDAO 申明:
public class LoginAction implements Action, ModelDriven {
LoginInfo loginInfo = new LoginInfo();