精华内容
下载资源
问答
  • 书的内容及连贯提出了中肯的意见。如果没有他们的帮助,本书 势必失色不少。我还要感谢 Floyd Marinescu ,他对我给予了充分的 信任,并为我提供了网络版和印刷版的写作机会。   我还要谢谢我...

    深入浅出 Struts 2

     

    Starting Struts 2

     

     

     

     

     

     

     

     

     

     

     

    作者:Ian Roughley

     

    翻译:李剑

     

     

     

     

     

     

     

    © 2008 C4Media Inc.

    版权所有

     

     

     

    C4Media InfoQ.com这一企业软件开发社区的出版商

     

    本书属于 InfoQ企业软件开发系列图书

     

    如果您打算订购InfoQ的图书,请联系 books@c4media.com  

     

    未经出版者预先的书面许可,不得以任何方式复制或者抄袭本书的任何

    部分,本书任何部分不得用于再印刷,存储于可重复使用的系统,或者

    以任何方式进行电子、机械、复印和录制等形式传播。

     

    本书提到的公司产品或者使用到的商标为产品公司所有。

     

    如果读者要了解具体的商标和注册信息,应该联系相应的公司。

     

     

     

     

     

     

     

    英文版责任编辑:Diana Plesa

    英文版封面设计:Dixie Press

    英文版美术编辑:Dixie Press

     

     

    中文版翻译:李剑

    中文站审校:张凯峰 霍泰稳

    中文版责任编辑:霍泰稳

    中文版美术编辑:吴志民

     

    欢迎共同参与InfoQ中文站的内容建设工作,包括原创投稿和翻译等,

    请联系 editors@cn.infoq.com

     

     

    10 9 8 7 6 5 3 2 1

     

     

     

     

    作者致谢

     

    如果没有 WebworkXWork Struts2 所有开发人员不懈的努

    力,这本书将永远无法面世。在我从一个开源项目的使用者变成一

    个开源项目的开发人员的过程中,Patrick Lightbody Jason Carreira

    对我的帮助将永远铭刻我心。

     

    同时,我也应该感谢这几位技术评审人员——Don Brown

    Philip Luppens Rene Gielen,是他们为本书内容的不断扩充提出了

    最终的调整方案。还有 Jim Krygowski James Walker,他们从繁忙

    的工作日程安排中特意抽出时间,站在 Struts2以外的视角上,对本

    书的内容及连贯性提出了中肯的意见。如果没有他们的帮助,本书

    势必失色不少。我还要感谢 Floyd Marinescu,他对我给予了充分的

    信任,并为我提供了网络版和印刷版的写作机会。

     

    我还要谢谢我才华横溢的妻子 LeAnn (也就是 STR Worldwide)

    她一直支持着我的工作,并且长久以来一直对书稿进行评审和非技

    术层面的分析,这是我的无价之宝。

      

      

    目 录

     

    简介...................................................................... 1

    WEB世界中,STRUTS2身处何方....................... 4

    SERVLETS...........................................................................................5

    JSPSCRIPTLET开发 ........................................................................5

    基于ACTION的框架.............................................................................6

    基于组件的框架.................................................................................6

    伟大的均衡器——AJAX......................................................................7

    核心组件............................................................... 8

    配置...................................................................................................9

    ACTIONS ...........................................................................................14

    INTERCEPTORS(拦截器)...............................................................18

    值栈与 OGNL ..................................................................................22

    结果类型..........................................................................................23

    结果和视图技术...............................................................................24

    架构目标............................................................. 29

    概念分离..........................................................................................29

    松耦合..............................................................................................30

    易测试性..........................................................................................31

    模块化..............................................................................................34

    惯例重于配置...................................................................................37

    提高效率技巧 ..................................................... 39

    重用 ACTION 的配置.........................................................................39

    在配置中使用模式匹配调配符.........................................................40

    使用替代的URI映射方法..................................................................41

    了解拦截器的功能...........................................................................44

    使用提供的拦截器栈........................................................................46

    利用返回类型...................................................................................47

    利用数据转换...................................................................................48

    利用列表数据项支持........................................................................50

    ACTION中暴露领域模型................................................................52

    尽可能使用声明式验证....................................................................53

    CRUD操作放到同一个ACTION..................................................56 在可能的地方使用注释.................................................................... 59

    视图技术选项.................................................................................. 63

    了解框架提供的标签库及其特性..................................................... 64

    自定义UI 主题................................................................................. 70

    为通用的输出创建全局结果............................................................ 71

    声明式异常处理............................................................................... 71

    国际化............................................................................................. 73

    其他技术集成......................................................77

    页面修饰和布局............................................................................... 77

    业务服务/依赖注入.......................................................................... 80

    数据库............................................................................................. 83

    安全................................................................................................. 84

    AJAX ................................................................................................ 85

    关于作者.............................................................87

    参考资料.............................................................88

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新   

     

    更多精彩内容:http://www.infoq.com/cn

    1

    InfoQ中文站 Java社区

    关注企业 Java社区的变化与创新

    http://www.infoq.com/cn/java

    简介

     

    自从 1997 年第一个 Servlet 规范发布以来,我们在用 Java 开发

    Web 应用这条路上已经走了很远很远。在过去的时间里,我们学会

    了很多,也曾经不止一次地对开发 Web 应用的方式做出过改进。

    Apache Struts 的产生就是一个伟大的跨越,它的价值远远超过了我

    们目光所能及的极限。 

     

    Apache Struts 2000 5 月由 Craig McClanahan 发起,并于

    2001 7 月发布了 1.0 版本。从技术的角度上来讲,它是在开发

    Web 程序上的一次跨越性的进步,但更重要的是,它在最恰当的时

    候出现在了人们的眼前。

     

    到那时为止,Web 开发已经度过了漫长的岁月,很多大型项目都

    已经完工进入了维护期,在这个过程中,代码的可重用性与可维护性

    也给开发人员好好的上了几课。.com 的兴起也引发了对 Web 应用开

    发更好的解决方案的诉求——当Apache Struts2000年出现的时候,

    Web 项目的数量仍然显著地增长着,而且毫无终止之势。Struts 一出

    现便大受欢迎,更成为了以后几年内web开发的实际标准。

     

    Struts21

    Struts的下一代产品。而最初提案Struts Ti所设想的发展

    方向,在Struts的现有代码的基础上是很难完成的。在发起提案的时

    候,Patrick Lightbody把多个不同的Web框架的领导者邀请到了一起,

    希望大家能够达成共识,协力完成一个通用框架。虽然最终由于各种

    原因, Patrick Lightbody 的愿望未能实现,但是 WebWork

                                                    

    1

     http://struts.apache.org/2.x 

     2 │深入浅出 STRUTS 2

     

    Struts Ti却发现了二者在技术与开发人员这两个层面上的共同之处,

    不久之后,两个项目就在WebWork的技术基础上进行了合并2

     

    当我们说起 WebWork 的时候,我们实际上说的是两个项目——

    XWork WebWorkXWork是一个通用的命令框架,它提供了很多

    核心的功能,例如 actions,验证和拦截器,它可以完全独立于执行

    上下文运行,并提供了一个内部的依赖注入机制,用来做配置和工

    厂实现的管理。

     

    WebWork 则是一个完全独立的上下文。它用 Web 应用中运

    行所需的上下文把 XWork包装起来,并提供了可以简化 Web开发的

    特定实现。

     

    Struts2 的目标很简单——使 Web 开发变得更加容易。为了达成

    这一目标,Struts2 中提供了很多新特性,比如智能的默认设置、

    annotation 的使用以及“惯例重于配置”原则的应用,而这一切都大

    大减少了 XML 配置。Struts2 中的 Action 都是 POJO,这一方面增强

    Action 本身的可测试性,另一方面也减小了框架内部的耦合度,

    HTML 表单中的输入项都被转换成了恰当的类型以供 action 使

    用。开发人员还可以通过拦截器(可以自定义拦截器或者使用 Struts2

    提供的拦截器)来对请求进行预处理和后处理,这样一来,处理请求

    就变得更加模块化,从而进一步减小耦合度。模块化是一个通用的主

    题——可以通过插件机制来对框架进行扩展;开发人员可以使用自定

    义的实现来替换掉框架的关键类,从而获得框架本身所不具备的功

    能;可以用标签来渲染多种主题(包括自定义的主题);Action 执行

    完毕以后,可以有多种结果类型——包括渲染 JSP 页面,Velocity

    Freemarker 模板,但并不仅限于这些。最后,依赖注入也成了 Struts2

    王国中的一等公民,这项功能是通过 Spring 框架的插件和 Plexus

    同提供的,与PicoContainer的结合工作还正在进行中。

     

    本书的目的,是为了帮助读者掌握 Struts2 框架,并能够对组成

    框架的功能部件和可用的配置项有深刻的理解。我在书中还将介绍

    一些可以提高生产力的方法——包括默认配置项和应当注意的实现

    特性,可用的多种配置选项和一些开发技术。本书还会就与第三方

    软件进行集成的话题展开讨论。

     

                                                    

    2

     Don Brown, Struts Ti项目的领导,他在文章中详细介绍了Struts Ti的历史,详情

    请参见http://www.oreillynet.com/onjava/blog/2006/10/my_history_of_struts_2.html.

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新   简介│3

     

    更多精彩内容:http://www.infoq.com/cn

    本书并不会对 Struts2 中所有的特性 面俱到的讲述。作为

    一个新项目,Struts2 仍然在不停地迎来变化、升级以及新的特性。

    我希望读者能够花一些时间访问一下项目的主页,从中你可以发现

    很多本书中未能囊括的选项和特性。

    进行面

    本书参考的是 Struts22.0.6版本。

     

    4

    2

    InfoQ中文站 Ruby社区

    关注面向 Web和企业开发的 Ruby

    http://www.infoq.com/cn/ruby

    Web世界中,Struts2身处何方

     

     

    今天,摆在开发人员面前的是众多的Web开发框架。它们有些

    来自于开源社区,有些来自于商业公司,还有些是由某些团队内部

    开发的,以满足当前Web开发所需。目前大约一共有 超过 40 3

    源框架,这个数目看上去挺大,但也许还有同样多(如果不是大大

    多于这个数量的话)的内部构建的框架部署在产品环境中。

     

    既然我们有这么多选择,那为什么要选择 Struts2 呢?下面列出

    的这些特性,可能会促使你把 Struts2作为你的选择:

     

    ƒ  基于 Action的框架

    ƒ  拥有由积极活跃的开发人员与用户组成的成熟社区

    ƒ  Annotation XML配置选项

    ƒ  基于 POJO并易于测试的 Action

    ƒ  SpringSiteMesh Tiles的集成

    ƒ  OGNL表达式语言的集成

    ƒ  基于主题的标签库与 Ajax标签

    ƒ  多种视图选项 (JSPFreemarkerVelocity XSLT)

    ƒ  使用插件来扩展或修改框架特性。

     

    在选择一个框架的时候,框架风格的选择是最具有争议性的。

    让我们先来了解一下 Web 应用的发展过程,然后再来看看我们是怎

    样走到今天的,以及在现在的 Web 开发场景中,Struts2 到底占据了

    什么位置。

     

     

     

                                                    

    3

     你可以访问 http://www.java-source.net/open-source/web-frameworks来获取一份

    各种web框架的列表。 STRUTS 2身处何方│5

     

    更多精彩内容:http://www.infoq.com/cn

    Servlets

     

    Servlets Java Web 应用中的开创性的尝试。在遵循 HTTP

    协议的前提下,Servlets 可以将 URL 映射到一个特定的类上,而该

    类中的方法将会被调用。

     

    人们马上就意识到,虽然这是一次大踏步式的前进,但是这种

    Java 代码中生成 HTML 代码的方式,对项目维护而言简直就是一

    场噩梦。每次当用户界面发生了变化,Java 开发人员就需要更改

    Servlet代码,重新编译,然后把应用重新部署到服务器上。

     

     

    JSPScriptlet开发

     

    这种“维护噩梦”的后果,又导致开发风格的正好颠倒。现在

    人们不是把 HTML 代码放在 Servlet 或者 Java 代码中,而是把 Java

    代码(作为 script-lets)放在 HTML 代码中——这就是 Java Server

    Pages (JSP)。每一个 JSP都同时负责处理请求和页面表现。

     

    一个问题得到了解决,而另一个问题却又出现了。在 JSP 中,

    Java 代码的使用方式和在类中一样,但这里却没有方法和类的结

    构。在早期的每一个 JSP文件中,都可以找到以下两种情况之一:

     

    剪切和粘贴的代码 —— Java 代码从一个 JSP 中复制到第

    二个,第三个,等等。这种情况会导致在原始代码中存在

    的缺陷或者错误传播开来,并且大大增加了工作量,因此

    必须要对此做出改变。

    调用通用的 Java 格式化对象——通用的格式化代码或者是

    逻辑代码被组织到一个可重用的对象中,然后每一个 JSP

    都会使用这个通用的对象。

     

    基于这些情况,一种最佳实践,或者可以称之为一种模式,就

    应时而生了——在 JSP中使用 Java对象。

     

    随着 JSP 规范的进一步完善,标签开始被引入进来对可重用的

    Java 对象进行封装。标签提供了用以访问底层代码的表层代码,它

    HTML 很相像,设计人员(而不是开发人员)和 IDE 可以通过它与

    动态内容交互,组装出页面布局。像<jsp:useBean.. />

     6 │深入浅出 STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    <jsp:getProperty.. />就是 JSP 所提供的标签。JSP 在提供了一系

    列标签库的同时,还可以支持开发人员创建自定义的标签库。

     

     

    基于Action的框架

     

    基于 Action的框架把 Servlet JSP的概念合并到了一起。它的

    想法是把对当前用户所见的页面请求的处理动作,分拆成处理逻辑

    和表现逻辑,让它们各司其职。这种实现方式使用了源自于

    Smalltalk 的一个模式,名为模型-视图-控制器——最近的叫法是前端

    控制器,而 Sun则给它起名为 Model 2 

     

    在这个模式中,Servlet 是控制器,集中处理所有的客户端页面

    请求。它把所请求的 URL与被称为 Action的工作单元映射到一起。

    Action 的工作就是通过访问 HTTP 会话、HTTP 请求和表单参数等调

    用业务逻辑,最后把响应映射到以 POJOplain old java object)形式

    存在的模型上,来完成特定的功能。最后,Action 返回的结果会通

    过配置文件映射到 JSP页面上,JSP会渲染视图并显示给用户。

     

    Struts2是一个基于 Action MVC Web框架。

     

     

    基于组件的框架

     

    Web 应用变得更加复杂的时候,人们便意识到一个页面已经

    不再是一个独立的逻辑了——一个页面上会存在多个表单,有内容

    更新的链接,还有其他很多自定义的 Widget——而这些都需要进行

    逻辑处理来完成各自的任务。

     

    出于解决这种复杂度的需要,基于组件的框架开始流行起来。它们

    在用户界面组件和表示这些组件的类之间提供了一层紧密的连接,

    它们是事件驱动型的,并且比起基于 Action 的框架而言,更具有面

    向对象的特征。一个组件可以是一个 HTML输入框,一个 HTML

    单,框架所提供的或是开发人员创建的 Widget。像提交表单或者是

    点击链接这样的事件,都与代表组件的类的方法或者是特定的监听

    类,有着一对一的映射关系。基于组件的框架还有一个好处,那就

    是你可以在多个 Web 应用之间重用组件。JSFWicket Tapestry

    等都是基于组件的框架。 STRUTS 2身处何方│7

     

    更多精彩内容:http://www.infoq.com/cn

     

    伟大的均衡器——Ajax

     

    2005年初,Web应用又增异彩。按照 Jesse James Garrett的说

    法, Ajax的全称是 “Asynchronous JavaScript and XML”。 相对说

    来,这些技术没有一样是新的。实际上,早在 6年以前(从 Internet

    Explorer 5开始),可进行异步调用的主要 Web浏览器组件——

    XMLHttpRequest对象就已经提供了支持。 

     

    真正推陈出新的是这些技术的应用。Google地图第一个完全利

    用了这项技术所带来的好处,在 Google地图中,网页是活动的,你

    可以通过各种窗体组件与它进行交互。你可以用鼠标来拖动屏幕上

    的地图;当你输入一个地址时,相关信息还会在地图的对应位置显

    示出来;最后,当它们都与 Web程序完美结合以后,Web应用终于

    攀上了新的巅峰。这些操作都不会带来页面的刷新!

     

    当用户界面与 Ajax结合以后,Web浏览器就可以只在必需的时

    候,才会向服务器发起请求,获得少量的信息。服务器返回的结果

    都是被格式化或者处理过的,页面会直接把结果显示出来,然后用

    户就可以在浏览器中看到变化。因为只有发生变化的那一块区域会

    被重新渲染,而不是整个页面进行刷新,所以对于用户来说,响应

    速度就变得更快了。

     

    UI发出的请求和事件很相似——它们是不连续的,所传递的

    只是一个单独的组件或者功能的信息。现在的操作已经再也不需要

    获取整个页面的信息了,它们变得更加精细,跨应用的可重用性也

    变得更高。其结果就是,当一个 Ajax用户界面调用基于 Action的框

    架时,这个 Action框架的反应机制就和基于组件的框架非常相似。

    实际上,这二者的结合为我们带来了耦合度更低、可重用性更高的

    系统。同样的操作,可以为 Ajax组件提供 JSONXML或者 HTML

    片段视图,又可以和其他操作组合,为非 Ajax的用户界面提供

    HTML视图。

     

      8

    3

    InfoQ中文站 SOA社区

    关注大中型企业内 SOA的一切

    http://www.infoq.com/cn/soa

    核心组件

     

    从全局的角度来看 ,Struts2是一个 pull(拉)类型的 MVC(或

    MVC2)框架,它与传统类型的 MVC 框架的不同之处就在于在

    Struts2 中,Action 担任的是模型的角色,而非控制器的角色,虽然

    它的角色仍然有些重叠。“pull”的动作由视图发起,它直接从

    Action 里拉取所需的数据,而不是另外还需要一个单独的模型对象

    存在。

     

    我们刚刚从概念上讲述了一下 Struts2里面的 MVC,那么从实现

    的层面上来看又是什么样子呢?在 Struts2 中,模型-视图-控制器模

    式通过五个核心组件来实现——Action、拦截器?值栈/OGNL?结果

    类型和结果/视图技术。

     

     

     

     

    1:  MVC / Struts2架构 核心组件 | 9

     

    更多精彩内容:http://www.infoq.com/cn

    1 描述了 Struts2 架构中的模型、视图和控制器。控制器通过

    Struts2 分发 Servlet 过滤器(也就是人们常说的拦截器)来实现,模

    型通过 Action 实现,视图则通过结果类型和结果组合实现。值栈和

    OGNL提供了公共的线程和链接,并使得不同组件可以相互集成。

     

    当我们在本章讨论通用组件时,其中有大量的信息是与配置相

    关的,也就是对 Action、拦截器、结果等等的配置。 在阅读时要注

    意一点,这里的讲解只是帮助读者掌握一些背景知识,并没有采用

    最有效率的配置方式。在后续的章节中,我们会介绍一些更加简单

    有效的方式,诸如“惯例重于配置”、注解和零配置插件。

     

    在我们对核心组件进行深入探讨之前,首先让我们来看看全局

    性的配置。

     

     

    配置

     

    在配置 Struts 2之前,你首先要把发行版下载下来,或者在

    Maven2的“pom.xml”文件中声明如下的依赖: 

     

    <dependency>

    <groupId>org.apache.struts</groupId>

    <artifactId>struts2-core</artifactId>

    <version>2.0.6</version>

    </dependency>

     

    Mave2是一种管理项目整体构建过程的工具——包括编译,运

    行测试,生成报告以及管理构建的制品,其中对开发人员最有吸引

    力的一项就是对构建制品(artifact)进行管理。

     

    应用程序的依赖库只需要在项目的“pom.xml”文件中通过

    groupIdartifactId version进行定义即可。在使用一个制

    品之前,Maven会从本地和远程的库中进行查询,查询的范围包括

    本地的缓存、网上其他组织提供的库以及 ibiblio.com这个标准的

    库。如果在远程的库中找到了所需的制品,Maven就会把它下载到

    本地的缓存中,以供项目使用。当请求所需要的制品时,这个制品

    相关联的依赖也会同样被下载到本地来(假设在该制品的

    pom.xml”文件中对所有依赖都依次进行了声明)。

     10 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    Struts2是用Maven2来构建的,在 pom文件中,它定义了所有

    依赖的配置项。您可以访问 http://maven.apache.org来获得关于

    Maven2的更多信息。

     

    Struts2的配置可以分成三个单独的文件,如图 2所示。

     

     

     

    2: 框架元素的配置文件

     

    FilterDispatcher 是一个 servlet过滤器,它是整个 Web应用

    的配置项,需要在“web.xml”中进行配置:

     

    <filter>

    <filter-name>action2</filter-name>

    <filter-

    class>org.apache.struts2.dispatcher.FilterDispatcher</fil

    ter-class>

    </filter>

     

    <filter-mapping>

    <filter-name>action2</filter-name>

    <url-pattern>/*</url-pattern>

    </filter-mapping>

     

    如果是配置一个最基本的 Web 应用的话,这样就足够了。剩下的就

    是自定义一些 Web 应用的执行环境和配置应用中的组件。其中前者

    主要通过“struts.properties”来完成,而后者是在“struts.xml”中进

    行配置的。我们下面来看一下这两个配置文件的细节。 核心组件 | 11

     

    更多精彩内容:http://www.infoq.com/cn

     

    struts.properties文件

    这个文件提供了一种更改框架默认行为方式的机制。在一般情

    况下,如果不是打算让调试更加方便的话,你根本无须更改这个文

    件。在“struts.properties”文件中定义的属性都可以在“web.xml

    文件的“init-param”标签中进行配置,或者通过“struts.xml”文件

    中的“constant” 标签来修改(我们在下一章中会继续讨论这个标

    签)。

     

    我们可以更改其中的一些属性来改变Freemarker的选项——改变

    Action映射类,判断是否要重新载入XML配置文件,以及默认的UI

    主题等等。在Struts2 wiki上有这些属性的最新信息,地址为

    http://struts.apache.org/2.x/docs/strutsproperties.html.  

     

    Struts2-Core jar发行版中,有一个默认的属性文件,名为

    default.properties”。如果要对属性进行修改的话,只需要在项目

    的源代码目录下,创建一个叫做“struts.properties”的文件,然后把

    想要修改的属性添加到文件中,新的属性就会把默认的覆盖掉了。

     

    在开发环境中,以下几个属性是可能会被修改的:

     

    »  struts.i18n.reload = true——激活重新载入国际化文件的功能

    »  struts.devMode = true ——激活开发模式,以提供更全面的

    调试功能。 

    »  struts.configuration.xml.reload = true——激活重新载入

    XML配置文件的功能(这是为 Action准备的),当文件被

    修改以后,就不需要重新载入 Servlet容器中的整个 Web

    用了。

    »  struts.url.http.port = 8080——配置服务器运行的端口号(所

    有生成的 URL都会被正确创建)

    »  

     

    struts.xml文件

    struts.xml”文件中包含的是开发 Action时所需要修改的配置

    信息。在本章接下来的内容中,会针对特定的元素进行详细讲解,

    但现在让我们先来看一下文件的固定结构。

     

    有的时候你甚至可以把整个“struts.xml”文件从应用中移走,这完

    全取决于应用程序的功能需求。我们在本章中所讨论的配置信息,

     12 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    都可以被其他方式所代替,比如注解,“web.xml”启动参数和和可

    替换的 URL映射方案。

     

    必须要在“struts.xml”中进行配置的信息只有全局结果、异常处理

    和自定义的拦截器堆栈。

     

     

    因为这是一个 XML文件,所以最开始的元素就是 XML版本和

    编码信息。接下来则是 XML的文档类型定义(DTD)。DTD提供

    XML文件中各个元素所应使用结构信息,而这些最终会被 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="struts2" 

    extends="struts-default" 

    namespace="/struts2">

     

       …

     

    </package>

     

    </struts>

     

    我们现在看到了<struts>标签,它位于 Struts2 配置的最外层,其他标

    签都是包含在它里面的。

     

    Include标签:

     

    <include /> <struts>标签的一个子标签,它可以把其他

    配置文件导入进来,从而实现 Struts2的模块化。它的“file”属性定

    义了要导入的文件的名称——该文件要和“struts.xml”一样有着相

    同的结构。比如说,如果要把一个记账系统的配置分解的话,你可

    能会把记账、管理和报表的功能各自组织到不同的文件中:

     

    <struts>

     

    <include file="billing-config.xml" /> 核心组件 | 13

     

    更多精彩内容:http://www.infoq.com/cn

    <include file="admin-config.xml" />

    <include file="reports-config.xml" />

     

     …

     

    </struts>

     

    当我们导入文件时,一定要注意导入的顺序。因为从文件被导

    入的那个点开始,该文件中的信息才能被访问到,也就是说,如果

    要使用另外一个文件中所定义的标签,那么该文件就必须要在被引

    用之前就配置好。

     

    有些文件需要显式导入,有些则会被自动导入。“struts-

    default.xml”和“struts-plugin.xml”就属于后者。它们都包括有结果

    类型、拦截器、拦截器堆栈、包(package)以及 Web 应用执行环境

    (也可以在“struts.properties”中配置)的配置信息。二者的区别在

    于,“struts-default.xml”提供的是 Struts2 的核心配置信息,而

    struts-plugin.xml”则描述了特定插件的配置信息。每个插件的

    JAR 中都要有一个“struts-plugin.xml”文件,该文件会在系统启动

    时被装载。

     

     

    The Package Tag:

     

    <package />标签是用来把那些需要共享的通用信息——例

    如拦截器堆栈或 URL命名空间——的配置组织在一起的。它通常由

    Action的配置组成,但也可以包括任何类型的配置信息。它还可以

    用来组织管理各自独立的功能——它们也可以被进一步拆分到不同

    的配置文件中。

     

    这个标签的属性包括有:

    z  name ——开发人员为这个 Package指定的唯一的名字。

    z  extends —— 当前这个 Package所继承的 Package的名字,

    被继承的 Package中所有的配置信息(包括 Action的配

    置)都可以在新的命名空间下,新的 Package里面被使

    用。

    z  namespace ——命名空间提供了从 URL Package的映

    射。也就是说,如果两个不同的 Package,其命名空间分别

    为“package1”和“package2”,那么URL差不多就是

     14 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    /myWebApp/package1/my.action” 和

    /myWebApp/package2/my.action”这样的形式。

    z  abstract ——如果这个属性的值为“true”,那么这个

    Package就只是一个配置信息的组合,也就无法通过

    Package的名字来访问其中配置的 Action 

     

    只有继承了正确的父 Package,那么你才能用到所需的预先配置

    好的特性。在大多数情况下,我们都应该继承“struts-default.xml

    配置文件中的“strust-defaultPackage,但是如果你想要使用插件的

    话,那就另当别论了。你必须要参考插件的说明文档来确认所应当

    继承的父 Package

     

    后面我们会继续讲解 Package标签的详细配置信息。

     

    还有两个标签也可以和<struts>标签一起使用,它们是

    <bean /><constant />。这两个标签为重新配置框架提

    供了更高级的方式,我们在下一章讲述插件的时候会介绍它们的使

    用方法和配置细节。

     

     

    Actions

      

    在大多数的 Web应用框架中,Action都是一个最基本的概念,

    也是可以与用户发出的 HTTP请求相关联的最小的工作单元。

     

    Struts2中,Action可以以几种不同的方式来工作。

     

    单个结果

    Action最常用也是最基本的用法就是执行操作后返回单个结

    果。这种 Action看上去就是这样的:

     

    class MyAction {

     

     public String execute() throws Exception {

       return "success";

     }

     

    }

     

     核心组件 | 15

     

    更多精彩内容:http://www.infoq.com/cn

    这样简单的几行代码当然说明不了什么。但首先,这个 Action

    类不需要继承其它类,也不需要实现其他接口。这样的类就是一个

    简单的 POJO

     

    其次,在这个类中有一个名为“execute”的方法。这个方法名

    是依照惯例命名的,如果你想用其他名字的话,那么只需要在

    Action的配置文件中做出更改。无论方法名是什么,它们都被认为

    会返回一个 String类型的值。Action的配置文件会将该 Action的返

    回代码与要呈现给用户的结果进行匹配。另外,该方法还可以在需

    要的时候抛出异常。

     

    下面是最简单的配置信息:

     

    <action name="my" class="com.fdar.infoq.MyAction" >

    <result>view.jsp</result>

    </action>

     

    name”属性提供了执行Action所对应的URL地址,在这里就是

    my.action”。“.action”的扩展名是在“struts.properties  4

    文件中

    配置的。“class”属性指定了要执行的action所对应的类的全限定

    名。

     

    多个结果

    现在情况稍微复杂了一些,Action需要根据逻辑运算的结果,

    来生成多个结果。下面的代码和刚才那个类看上去很像:

     

    class MyAction {

     

     public String execute() throws Exception {

       if( myLogicWorked() ) {

    return "success";

       } else {

       return "error";

       }

     }

     

    }

     

    因为这里有两个结果,所以就为每一种不同的情况来配置要呈

    现给用户的结果。配置文件就变成了如下的样子:

     

                                                    

    4

     只需要更改 “struts.action.extension”这个属性的值。

     16 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    <action name="my" class="com.fdar.infoq.MyAction" >

    <result>view.jsp</result>

    <result name="error">error.jsp</result>

    </action>

     

    我们可以看到在 result节点中多了“name”属性,实际上这个

    属性是一直都存在的,如果开发人员没有显式指定它的值,那么它

    的默认值就是“success”(第一个 result的配置就是如此)。

     

    前面我们已经看到了定义 Action结果的最通用的方式。而实际

    上我们还有另外四种方式:

    1. Action方法返回一个字符串——这个返回的字符串与

    struts.xml”的一个 action配置相匹配。例子中已经演示这一

    种方式。

    2.  使用Code behind插件 ——当使用这个插件的时候,它会将

    Action的名字和Action返回的结果字符串进行连接来得到视图模

    板。比如说,如果URL是“/adduser.action”,而Action返回了

    success”,那么要渲染的页面就是“/adduser-success.jsp” 。

    更多信息请参见http://struts.apache.org/2.x/docs/codebehind-

    plugin.html

    3.  使用 @Result注解——  action类可以用@Results @Result

    解来标注多个不同的结果。Action所返回的字符串需要与所注

    解的结果之一相匹配。

    4.  方法返回一个 Result类的实例——Action不必一定要返回一个

    字符串,它可以返回一个 Result类的实例,该实例应当是已经

    配置好可使用的。

     

    结果类型

    Action生成并返回给用户的结果可能会有多个值,而且也可能

    是不同的类型。“success”的结果可能会渲染一个 JSP页面,而

    error”的结果可能需要向浏览器发送一个 HTTP头。 

     

    结果类型(本章中稍后会详细讨论)是通过 result 节点的“type”属

    性来定义的。和“name”属性一样,这个属性有一个默认值——

    dispatcher”——用来渲染 JSP。大多数情况下,你只需要使用默

    认的结果类型就可以了,但是你可以提供自定义的实现。

     

    请求和表单类型 核心组件 | 17

     

    更多精彩内容:http://www.infoq.com/cn

    Action为了执行操作,并为数据库持久化对象提供数据,就必

    须要访问请求字符串和表单中的数据。

     

    Struts2采用了 JavaBean的风格——要访问数据的话,就给字段

    提供一个 getter setter,要访问请求字符串和表单也是一样的道

    理。每一个请求字符串和表单的值都是一个简单的名/值对,所以要

    设定一个特定名称的值的话,就要为它提供一个 setter。比如,如果

    一个 JSP调用了“/home.action?framework=struts&version=2”这样一

    个请求,那么 action就应该提供如下两个 setter

    setFramework( String frameworkName )”和“setVersion( int

    version )”。

     

    我们可以看到,例子中的 setter并不是只接受 String类型的参数。在

    默认情况下,Struts2可以把 String类型的值转换成 action所需要的类

    型,这条规则对于所有的 primitive类型和基本对象类型的值都适

    用,当然你也可以对其进行配置,让它也适用于你所创建的类。

    Struts2还可以在更加复杂的对象图中进行定位后赋值 ,比如说如果

    一个表单元素的名字是“person.address.home.postcode”,其值为

    2”,那么Struts2就会调用

    getPerson().getAddress().getHome().setPostcode(2)”这个方法。

     

    访问业务服务

    到现在为止,我们已经讨论了很多有关 Action配置的问题,以

    及如何根据不同的结果代码来控制返回给用户的结果。这是 Action

    的一个很重要的功能,但是,它必须要完成一些操作之后,才能返

    回结果。因此它们需要访问多种类型的对象——业务对象,数据访

    问对象或者其他资源。 

     

    Struts2使用了名为依赖注入5

    ——又名控制反转——的技术来降

    低系统的耦合性。依赖注入可以通过构造器注入,接口注入和setter

    注入来实现。Struts2中用的是setter注入。这就是说,你只需要提供

    一个setter,对应的对象就可以被Action使用了。Struts2推荐的依赖

    注入框架是Spring框架,并通过插件对它进行配置。你还可以使用

    Plexus,或者是提供自定义的实现。

     

                                                    

    5

     Martin Fowler写过一篇文章,对依赖注入进行了完整的描述:

    http://www.martinfowler.com/articles/injection.html

     18 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    还有些对象是不受 Spring框架管理的,例如

    HttpServletRequest。这些对象是通过 setter注入和接口注入混合处理

    的。对于每一个非业务的对象而言,都有一个对应的接口(也就是

    aware”接口),需要 action对其进行实现。 

     

    在最开始的时候,WebWork有它自己独有的依赖注入框架。但

    自从 2.2版本以后,该框架就被 Spring取代了。原先的那种组件框架

    是基于接口的,所以每一个组件都需要有一个接口和一个对应的实

    现类。 

     

    另外,每一个对象都有一个“Aware”接口,为组件提供了

    setter方法。对于 UserDAO接口而言,对应的 aware接口按照惯例就

    会被命名为“UserDAOAware”,其中有一个 setter方法——“void

    setUserDAO( UserDAO dao );”。

     

    当必需的接口和 setter齐备以后,拦截器就会对对象的注入进行

    管理了。

     

    Action中访问数据

    在有些情况下我们需要查看被 Action修改过的对象。有好几种

    技术可以帮助我们做到这一点。 

     

    对于开发人员而言,最常用的方法就是把所需要访问的对象放

    HttpServletRequest或者 HttpSession里面。这可以通过实现

    aware”接口(让依赖注入来工作),并设置为可以通过要求的名

    称来访问的方式来达到。

     

    如果你打算使用内建的标签库或者是 JSTL 支持的话,访问数据就会

    更简单了。这两种方法都可以通过值栈来访问 Action。开发人员唯

    一要另外做的就是在 Action 里面为所要访问的对象提供 getter

    法。在下个关于值栈的小节中我们会进行更深入的介绍。

     

     

    Interceptors(拦截器)

     

    Struts2中提供的很多特性都是通过拦截器实现的,例如异常处

    理,文件上传,生命周期回调与验证。拦截器从概念上来讲和

    Servlet过滤器或者 JDK Proxy类是一样的。它提供了一种对核心组件 | 19

     

    更多精彩内容:http://www.infoq.com/cn

    Action进行预处理和事后处理的功能。和 Servlet 过滤器一样,拦截

    器可以被分层和排序。它还可以访问所执行的 Action和所有的环境

    变量与执行属性。

     

    让我们从依赖注入来开始对拦截器的介绍。正如我们已经看到

    的一样,依赖注入可以多种不同的实现方式。下面就是对应于不同

    实现方式的拦截器:

    ♦  Spring 框架——ActionAutowiringInterceptor 拦截器

    ♦  请求字符串和表单值—— ParametersInterceptor 拦截器

    ♦  基于 Servlet的对象——ServletConfigInterceptor 拦截

     

    前两种拦截器可以独立工作,不需要 Action的帮助,但是最后

    一种不同,它是在以下几种接口的辅助下工作的:

    ♦  SessionAware ——通过 Map来提供对所有 session属性的访

    ♦  ServletRequestAware——提供对 HttpServletRequest

    对象的访问

    ♦  RequestAware ——通过 Map来提供对所有 request属性的访

    ♦  ApplicationAware ——通过 Map来提供对所有 applicatin

    性的访问

    ♦  ServletResponseAware ——提供对

    HttpServletResponse 对象的访问

    ♦  ParameterAware ——通过 Map来提供对所有 request string

    表单数据的访问

    ♦  PrincipalAware ——提供对 PrincipleProxy 对象的访

    问;该对象实现了 HttpServletRequest 对象的有关

    principle role的方法,但是它提供了一个 Proxy,因此所有的

    实现都是独立于 Action的。

    ♦  ServletContextAware ——提供对 ServletContext 对象

    的访问

     

    如果要把数据注入到 Action 中去,那么对应的数据就需要实现

    必需的接口。 

     

    配置

    如果要在 Action中激活依赖注入功能(或其他任何由拦截器提

    供的功能),就必须要对 Action进行配置。和其他元素一样,许多

     20 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    拦截器都已经提供了默认的配置项。你只需要确认一下 Action所在

    Package继承了“struts-defaultpackage

     

    在配置一个新的拦截器之前,首先要对它进行定义。

    <interceptors /><interceptor />标签都要直接放

    <package>标签里面。像我们上面提到的那些拦截器,它们的配

    置项就是这样的:

     

    <interceptors>

     …

    <interceptor name="autowiring" 

    class="…xwork2.spring.interceptor.ActionAutowiringInte

    rceptor"/>

    </interceptors>

     

    我们同时还要确保 Action中应用了所需的拦截器。这可以通过

    两种方式来实现。第一种是把拦截器独立的分配给每一个 Action 

     

    <action name="my" class="com.fdar.infoq.MyAction" >

    <result>view.jsp</result>

    <interceptor-ref name="autowiring"/>

    </action>

     

    在这种情况下,Action所应用的拦截器是没有数量限制的。但

    是拦截器的配置顺序必须要和执行的顺序一样。

     

    第二种方式是在当前的 Package下面配置一个默认的拦截器:

     

    <default-interceptor-ref name="autowiring"/>

     

    这个声明也是直接放在<package /> 标签里面,但是只能

    有一个拦截器被配置为默认值。

     

    现在拦截器已经被配置好了,每一次 Action所映射的 URL接到

    请求时,这个拦截器就会被执行。但是这个功能还是很有限的,因

    为在大多数情况下,一个 Action都要对应有多个拦截器。实际上,

    由于 Struts2的很多功能都是基于拦截器完成的,所以一个 Action

    应有 78个拦截器也并不稀奇。可以想象的到,如果要为每一个

    Action都逐一配置各个拦截器的话,那么我们很快就会变得焦头烂

    额。因此一般我们都用拦截器栈(interceptor stack)来管理拦截器。

    下面是 struts-default.xml文件中的一个例子:

     

    <interceptor-stack name="basicStack"> 核心组件 | 21

     

    更多精彩内容:http://www.infoq.com/cn

    <interceptor-ref name="exception"/>

    <interceptor-ref name="servlet-config"/>

    <interceptor-ref name="prepare"/>

    <interceptor-ref name="checkbox"/>

    <interceptor-ref name="params"/>

    <interceptor-ref name="conversionError"/>

    </interceptor-stack>

     

    这个配置节点是放在<package /> 节点中的。每一个

    <interceptor-ref />标签都引用了在此之前配置的拦截器或

    者是拦截器栈。

     

    我们已经看到了如何在 Action中应用拦截器,而拦截器栈的用

    法也是一模一样的,而且还是同一个标签:

     

    <action name="my" class="com.fdar.infoq.MyAction" >

    <result>view.jsp</result>

    <interceptor-ref name="basicStack"/>

    </action>

     

    默认拦截器的情况也是一样的——只需要把单个拦截器的名字

    换成拦截器栈的名字就可以了。

     

    <default-interceptor-ref name="basicStack"/>

     

    由上面的种种情况可以得出,当配置初始的拦截器与拦截器栈时,

    必须要确保它们的名字是唯一的。

     

    实现拦截器

    在应用程序中使用自定义的拦截器是一种优雅的提供跨应用特

    性的方式。我们只需要实现 XWork框架中一个简单的接口,它只有

    三个方法:

     

    public interface Interceptor extends Serializable {

     

    void destroy();

     

    void init();

     

    String intercept(ActionInvocation invocation) throws

    Exception;

    }

     

     22 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    如果我们不需要执行其他初始化或清理动作的话,还可以直接

    继承 AbstractInterceptor。这个类对“destroy”和“init”方

    法进行了重写,但在方法中没有执行任何操作。

     

    ActionInvocation 对象可以用来访问运行时环境,以及

    Action本身;上下文(包括了 Web应用的请求参数,session参数,

    用户 Local等等);Action的执行结果;还有那些调用 Action的方法

    并判断 Action是否已被调用。

     

    在上一小节中我们知道了配置拦截器和配置拦截器栈的方式是相同

    的,如果你需要创建自己的拦截器的话,那么也应当考虑一下创建

    自定义的拦截器栈。这样才能确保新的拦截器在应用的时候可以贯

    穿所有需要该拦截器的 Action

     

     

    值栈与 OGNL

     

    本节所要介绍的两部分内容是密切相关的。值栈的含义正如它

    的名字所表示的那样——对象所组成的栈。OGNL的全称是 Object

    Graph Navigational Language(对象图导航语言),提供了访问值栈

    中对象的统一方式。

     

    值栈中的对象构成及其排列顺序如下所示:

     

    1.  临时对象——在执行过程中,临时对象被创建出来并放到

    了值栈中。举个例子来说,像 JSP标签所遍历的对象容器

    中,当前访问到的值就是临时对象 

    2.  模型对象——如果模型对象正在使用,那么会放在值栈中

    action的上面

    3.  Action对象——正在被执行的 action

    4.  固定名称的对象(Named Objects)——这些对象包括有

    #application #session #request #attr

    #parameters,以及相应的 servlet作用域

     

    访问值栈可以有很多方法,其中最常用的一种就是使用 JSP

    Velocity或者 Freemarker提供的标签。还有就是使用 HTML标签访

    问值栈中对象的属性;结合表达式使用控制标签(例如 ifelseif

    iterator);使用 data标签(set push)来控制值栈本身。

     核心组件 | 23

     

    更多精彩内容:http://www.infoq.com/cn

    在使用值栈时,我们无须关心目标对象的作用域。如果要使用

    名为“name”的属性,直接从值栈中进行查询就可以了。值栈中的

    每一个元素,都会按照排列顺序依次检查是否拥有该属性。如果有

    的话,那么就返回对应的值,查询结束。如果没有的话,那么下一

    个元素就会被访问……直到到达值栈的末尾。这个功能非常强大,

    我们根本不需要知道所需要的值在什么地方——存在于 Action,模

    型或是 HTTP请求中——只要这个值存在,它就会被返回。

     

    但它也有个缺点。如果所请求的是很常见的属性(例如

    id”),而你想要从某个特定的对象中(例如 action)获取该属性

    的值,这时候值栈中第一个满足条件的对象返回的属性值就可能不

    是所想要的结果了。返回结果的确是“id”属性的值,但它可能来自

    JSP标签,临时对象或者模型对象。这时候就需要用到 OGNL来增

    强值栈的功能了。OSGL并不仅限于访问对象属性,如果我们知道某

    action在值栈中的深度,那么就可以用“[2].id”来替换掉

    id”。

     

    实际上OGNL是一套完整的表达式语言。在OGNL里面,可以用

    .”来遍历对象图(比如说,使用“person.address”而不是

    getPerson().getAddress()”),它还提供了类型转换,方法调用,

    集合的操作与生成,集合间的映射,表达式运算和lambda表达式。

    OGNL的网站上提供了一套完整的指南,地址为

    http://www.ognl.org/2.6.9/Documentation/html/LanguageGuide/index.ht

    ml

     

     

    结果类型

     

    在前面我们已经演示过如何配置 Action来向用户返回一个

    JSP。但这只是 Action的结果之一。Struts 2里面提供了多种结果类

    型 ,有些是可见的,有些只是与运行环境之间的交互。

     

    type”属性被用来配置 Action的结果类型,如果没有配置该

    属性的话,那么默认类型就是“dispatcher”,它将会渲染一个 JSP

    结果并返回给用户。下面就是这样的 Action配置:

     

    <action name="my" class="com.fdar.infoq.MyAction" >

    <result type="dispatcher">view.jsp</result>

    </action>

     

     24 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    配置

    结果类型的配置项在<package />标签内部,和拦截器的配

    置看上去很相似。“name”属性提供了结果类型的统一标识,

    class”属性描述了实现类的全限定名。它多出了第三个属性:

    default”——通过这个属性可以修改默认的结果类型。如果一个

    Web应用是基于 Velocity而不是 JSP的话,那么把默认结果类型修

    改一下,就可以节省很多配置的时间。

     

    <result-types>

    <result-type name="dispatcher" default="true" 

    class="org.apache.struts2.dispatcher.ServletDis

    patcherResult"/>

    <result-type name="redirect" 

    class="org.apache.struts2.dispatcher.ServletRed

    irectResult"/>

    </result-types>

     

    实现结果类型

    和拦截器一样,你也可以创建自己的结果类型并在 Web应用中

    加以配置。Struts 2中已经有了很多常用的结果类型,所以在创建自

    己的结果类型之前,你应该检查一下你想要的类型是否已经存在。

     

    创建一个新的结果类型需要实现 Result接口。

     

    public interface Result extends Serializable {

     

    public void execute(ActionInvocation invocation) 

    throws Exception;

    }

     

    ActionInvocation 对象可以用来访问运行时环境,新的结果类

    型还可以通过它来访问刚刚执行的 Action 以及Action 执行的上下

    文。在上下文中包括有 HttpServletRequest 对象,可以用来访

    问当前请求的输入流。

     

     

    结果和视图技术

     

    在上面的所有示例中,我们假设了所使用的视图技术都是对 JSP

    进行渲染,虽然这是最常用的方式,但并不是渲染结果的唯一方

    式。 核心组件 | 25

     

    更多精彩内容:http://www.infoq.com/cn

     

    结果类型是与所使用的视图技术联系在一起的。我们在前面的

    小节中看到过,如果没有“type”属性或者该属性的值是

    dispatcher”的话,那么最后就会渲染 JSP页面。在 Struts  2应用

    中,有其他三种技术可以用来替代 JSP

     

    ƒ  Velocity Templates

    ƒ  Freemarker Templates

    ƒ  XSLT Transformations

     

    另外,你还可以为任何一种已有的视图技术实现一种新的结果

    类型,这样就可以得到另外的结果了。

     

    抛开各自的语法差异不谈,Freemarker Velocity还是和 JSP

    相似的。Action的所有属性都可以被模板访问(通过 getter方法),

    就像使用 JSP标签库和在标签库中使用 OGNL一样。在 action的配

    置文件中,只需要把 JSP模板的名字换成 Velocity或者 Freemarker

    模板的名字就可以了。下面的配置文件中,用 Freemarker代替了 JSP

    的返回结果:

     

    <action name="my" class="com.fdar.infoq.MyAction" >

    <result type="freemarker">view.ftl</result>

    </action>

     

    XSLT结果和其他的有所不同。它不是用 stylesheet名代替模板

    名,而是使用了另外的参数。“stylesheetLocation”参数提供了用于

    渲染 XML文件的 stylesheet。如果没有为该参数赋值的话,那么返

    回给用户的就是未经转换过的 XML文件。

     

    exposedValue”属性用来提供将要作为 XML来展现的Action

    属性或是 OGNL表达式。如果该参数没有赋值的话,那么Action

    身就会被作为 XML显示出来。 

     

    <result type="xslt">

     <param

    name="stylesheetLocation">render.xslt</param>

     <param name="exposedValue">model.address</param>

    </result>

     

    在使用 XSLT 作为结果的时候,还可以使用“struts.properties”来进

    行属性配置。该属性为“struts.xslt.nocache”,它决定了 stylesheet

    否会被缓存。在开发的过程中,你可能需要移除所有的缓存,以提

     26 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    高开发速度。但是当应用被部署到产品环境以后,被缓存的

    stylesheet就会提高渲染性能。

     

    标签库

    标签库常用来定义一些提供了可重用性的 JSP专属特性。在

    Freemarker Velocity中虽然没有同样的概念,但却提供了渲染引擎

    的模型或者上下文,以及可以访问这些对象的被渲染的模板。当我

    们讨论 Struts 2中的标签库时,我们实际上讨论的是与 JSP标签库一

    样提供了相同功能的对象,它们可以被所有的视图技术——JSP

    Velocity Freemarker——访问。

     

    在这里定义标签库时,步骤要更多一些,但是内在的功能是相

    同的——为对象内部的方法进行访问。通过这项功能,逻辑代码得

    到了更好的封装,同时也避免了到处的剪切和粘贴代码,从而提高

    了程序的可维护性。

     

    JSP的标签库中有另外一个今天看上去已经过时的特性——把要

    显示的文本放在标签库自己的 Java代码中。Struts 2中又重新拾起了

    这种思想,为标签专门创建了一种第二等的 MVC模式。在该模式

    中,由 Java类处理逻辑,但是页面渲染的工作放在了 Freemarker

    板中(默认情况下)。整体架构如下图所示: 

     

     

     

     

    这个架构的核心是一组组件对象。组件对象以最基本的形式来

    表示每一个标签,并且提供了所有必需的逻辑,例如管理和渲染模

    板。每一个不同的结果/视图技术都对组件进行了封装,在封装中把

    原始页面中的标签进行了转换,以满足特定的需要。 核心组件 | 27

     

    更多精彩内容:http://www.infoq.com/cn

     

    在结合 Freemarker模板渲染使用标签的时候,还需要进行额外

    的配置。我们要在“web.xml”文件中配置一个 servlet,这样

    Freemarker才能得到渲染所需的信息:

     

    <servlet>

     <servlet-name>jspSupportServlet</serlet-name>

     <servlet-class>

     org.apache.struts.action2.views.JspSupportServlet

    .JspSupportServlet

     </servlet-class>

     <load-on-startup>10</load-on-startup>

    </servlet>

     

    每一个组件都有与其相关联的模板。如果原始的标签中还包含

    有其它标签(例如 form标签),那么就会对应有一个开放的模板和

    封闭的模板。如果原始标签是自包含的(例如 checkbox标签),那

    么就对应的就只有一个封闭的模板。就像把 UI架构中的文本与逻辑

    分离一样,为标签提供模板也会带来额外的好处——开发人员可以

    为同样的标签来组合和匹配不同的模板,这种特性叫做“主题

    themes)”

     

    目前共有 3个主题6

    :“simple”,“xhtml”和“css_xhtml”。

    simple”主题只是简单输出标签,不做任何的格式调整。

    xhtml”主题作出了一些格式处理;比如对于HTML的表单标签,

    这种主题使用HTML表格形式提供了两列的格式(即名称标签和输

    入域两列)。对CSS的信仰者来说,这里为他们提供了

    css_xhtml”主题。与“xhtml”主题相似的是,这种主题也是提供

    了格式转换的功能,但是它用的不是HTML表格,而是CSS DIV。这

    种方式不会带来HTML的混乱。

     

    xhtml”和“css_xhtml”主题为开发者自己动手实践提供了很

    好的范例——通过实现一个主题来为 HTML提供特定的格式。在同

    一个页面上,可以组合和匹配多种主题,当前标签所使用的主题是

    通过“theme”属性来定义的。如果在整个应用中,你只需要使用一

    个主题的话,那么就可以在“struts.properties”文件中,将

    struts.ui.theme”属性的值配置为要使用的主题。

     

                                                    

    6

     单纯从技术角度来讲,应该有4种主题——第4种是“ajax”主题。Struts2

    经决定在下个版本中把Ajax的功能从核心框架里移走,放到插件里面。所以在

    本节中没有对其进行介绍。

     28 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    每一种标签(control 标签,data标签,form标签和 non-form标签)

    都可以使用主题;但是如果要创建一个新主题的话,那么它只有对

    可视化的标签才能起到作用。

    29

    4

    InfoQ中文站.NET社区

    .NET相关企业软件开发解决方案

    http://www.infoq.com/cn/dotnet

    架构目标

     

    对于一个特定的代码库而言,要谈它的架构目标是很困难的。

    通常在开发之前,其最终目标就写进了文档中;但这是理想化的状

    态,当开始开发之后,代码往往就会向着另外的方向发展。然后就

    有了代码库的典型特征;它们很难被发现,在不同的 package或者特

    性之间有可能表现出不一致,并且更像是脱离计划之外的演变产

    物。

     

    在本章中,我们将要讲述 Struts 2代码库中五种这样的特征。从

    2002年的代码库演变起始——从最开始的 WebWork,到 WebWork

    分离成为 WebWork2 XWork,再到最后的 Struts 2,这些架构思想

    一直延续到了今天。

     

     

    概念分离

     

    以下这些不同层次的功能都是 Web开发人员应当了解的:

     

    ♦  在请求/响应周期中,所完成的核心任务是执行特定于每个

    Action的逻辑功能。

    ♦  Action要完成逻辑功能或者访问资源的话,必须要访问或

    者持有业务对象。

    ♦  在把 HTML中基于字符串的值转换成原始数据类型或其他

    对象类型,以及把视图对象转换成业务对象或者数据表表

    示的过程中,这期间需要完成多种转化,映射和变换。

    ♦  有一些横切(cross-cutting)的目的是为成组的 action,或

    者应用中的所有 action提供功能的。 

     30 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    Struts 2的架构中,以上的每一种概念都是被单独分离的。功

    能和逻辑不再是 Action独享的。让我们看一下上面提到的概念是如

    何被分离的:

     

    ♦  每个 Action的逻辑(Per-Action Logic ——这是最简单

    的概念;每一个 Action都要为它所提供的逻辑或功能负

    责。

    ♦  访问/持有业务对象(Accessing/Obtaining Business

    Objects )—— Struts 2 使用了依赖注入机制来把 Action

    需的对象注入到 Action中去。

    ♦  转化/映射/变换(Translation/Mapping/Conversions )—

    —这三个概念两两之间都有着细微的区别,但它们都为

    Action核心逻辑提供了辅助功能。类型的转化和变换是由

    框架本身完成的。在 Action开始进行处理之前,HTML

    的字符串值就被转化成了基本类型,然后注入进 Action

    面——所需的东西就齐备了。映射是通过特定的拦截器完

    成的。我们需要通过某种方式对 Action进行配置,让它拥

    有一个领域模型,并正确指定 HTML中相应的字段,这样

    框架就会把 UI与领域模型进行映射。它甚至还可以贯穿一

    个对象图。

    ♦  横切的概念(Cross-cutting Concerns )——横切功能主要

    是由拦截器提供的。开发人员可以实现拦截器,然后让它

    们横切所有的 Action,横切特定 Package中的 Action,或

    者还可以选择所要横切的 Action。 另外一个横切的概念是

    UI布局。在这里 Struts 2提供了被称作“主题”的支持标

    签。不同的主题可以提供不同的布局选项,并可以应用到

    每一个独立的标签中,或是整个应用程序(把该主题设置

    为默认值)。

     

     

    松耦合

     

    WebWork早期的目标之一就是提供一个松耦合的框架。

    WebWork2.0版本更强化了这一点,把代码拆分成了两个项目:

    XWork——一个通用的命令框架;WebWork——XWork的针对 Web

    的接口。WebWork在架构上做出的根本性变化创造了一种共生的关

    系。我们现在所知的“WebWork”实际上是 WebWork XWork

    合并。 架构目标 | 31

     

    http://www.infoq.com/cn

     

    XWork作为一个独立的项目,可以作为其他项目的一部分加以

    利用——事实也是如此。Swingwork7

    就是这样一个项目。它是基于

    SwingMVC框架,在底层使用了XWork。另外一个例子是一个JMS

    前端,它在Web UI中执行或共享XWorkaction。这些都是高度松耦

    合的精彩示例。Struts 2也是XWork的一个受益者。

     

    松耦合的思想在 Struts 2中得到了更好的传播,它贯穿了整个框

    架的始终——从最开始 Action的处理过程到最后一步结束。实际

    上,在 Struts 2中几乎没有什么是不可以被配置的——我相信这是

    Struts 2中最强有力的地方之一,但也是最虚弱的地方之一。

     

    松耦合的配置有一些常见示例:

    ♦  URL映射到 Action

    ♦  把不同的 Action结果映射到被渲染的页面

    ♦  把处理过程中发生的异常映射到被渲染的异常页面

     

    稍微少见一些的 Struts 2特有的示例有:

    ♦  在不想用 Spring的情况下配置业务对象工厂

    ♦  改变 URL Action类的映射方式

    ♦  添加新的 Action结果类型

    ♦  为新的框架功能添加插件

    ♦  通过拦截器配置框架层的功能

     

    松耦合系统的好处是广为人知的——增加易测试性,更容易扩展框

    架功能,等等。但是这里还有一点坏处。强大的配置管理功能,尤

    其是拦截器在其中至关重要的作用,使得开发人员已经很难理解某

    Action 的执行过程。这一点在调试时尤为突出。一个不了解内部

    流程的开发者很难提高调试的速度或效率,因为他根本就无法理解

    这中间发生了什么。这种问题可能简单如拦截器的配置错误,甚或

    是拦截器的配置顺序不对。如果我们能够充分理解处理过程中的每

    一个细节,那么很快就能找到解决方案了。

     

     

    易测试性

     

                                                    

    7

     Swingwork 的地址为 https://swingwork.dev.java.net/。这个项目已经停止开发

    了。

     32 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    在过去的几年里,单元测试已经成为了软件开发领域内的事实

    标准。测试不仅能够保证逻辑的正确性,而且在开发所要测试的类

    的时候(如果在此之前则更好),通过编写单元测试还可以得到更

    简洁强壮的设计。

     

    Struts2的前身——WebWork,就是在这种环境下开发的。在与

    框架元素保持松耦合的情况下,测试变得更加简单。Action、拦截

    器、结果、对象工厂以及在 Web应用开发过程中开发出的其他组

    件,都可以不依赖于框架进行测试。

     

    因为 Action 和拦截器是最常用的,所以下面我们来看一下怎么样对

    它们进行测试。

     

    Actions

    Actions在框架中一般都是通过“execute()”方法被调用的,在

    更改配置以后,任何返回 String类型值的方法都可以用来调用

    Action。站在易测试性的角度来看,这已经无法更简单了。

     

    我们先来看一个例子。下面这个 action被用来增加一个数值:

     

    public class MyAction {

     

     private int number;

     public int getnumber() { return number; }

     public void setNumber( int n ) { number = n; }

     

     public String execute() {

       number += 10;

       return “success”;

     }

    }

     

    因为 Action都是 POJO,所以单元测试只需要实例化一个

    Action,调用其中的方法,然后确保返回的结果与所期待的一致。

    Action中的数据资源都是通过 setter方法来提供的。所以 Action中所

    需的数据都可以被直接赋值。

     

    在我们的例子中需要两条断言——一个用来判断“execute”方

    法的输出,一个用来验证我们所期望的 Action的状态。下面是单元

    测试代码:

     

    public class myActionTest extends TestCase {

     架构目标 | 33

     

    http://www.infoq.com/cn

     …

     

     public void testExecute() {

     

       MyAction action = new MyAction();

       Action.setNumber(5);

              assertEquals("success", action.execute());

              assertEquals(15,action.getNumber());  

    }

    }

     

    对于资源来说情况就复杂一些了。我们可以用类似于JMock8

    第三方库来提供资源的mock实现,然后测试Action与资源之间的交

    互是否正确。

     

    虽然例子是用 JUnit来写的,但是我们同样可以使用 TestNG

    者其他的测试框架。

     

    拦截器

    在构建拦截器时,测试会稍稍复杂一些。但是在代码框架中也

    为其提供了额外的帮助。下面是两个使用拦截器的场景。

     

    第一个是拦截器在使用时与 ActionInvocation对象交互。在执行

    完以后,你可以通过断言拦截器本身的状态来对逻辑处理进行验

    证。在这个场景中,拦截器的测试方式和 action一模一样。实例化

    拦截器;创建一个 ActionInvocation mock实现,用它来测试拦截

    器;调用 intercept方法;然后断言所期望发生的变化。所断言的可

    能是拦截器本身,也可能是方法调用的结果,抑或是可能会抛出的

    异常。

     

    第二个场景是拦截器与运行环境或者是与拦截器栈中的其他拦

    截器交互。这种情况下,测试需要通过 ActionProxy类与 Action

    互,同时断言也需要访问拦截器本身没有权限访问的其他环境对

    象。

     

    XWork库为 JUnit测试提供了 XWorkTestCase类,为 TestNG

    试提供了 TestNGStrutsTestCase TestNGXWorkTestCase,用以帮助

    测试拦截器。它们都为 ConfigurationManagerConfiguration

    ContainerActionProxyFactory类的实例提供了测试实现。XWork

                                                    

    8

     请参见http://www.jmock.org以获得更多信息。

     34 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    同时还提供了其他一些类,比如 XWorkTestCaseHelper

    MockConfiguration

    现在我们已经有了建立测试环境的底层架构,测试本身就变得

    容易了——只需要按照第一个场景中的步骤执行就可以了。唯一的

    区别就是在调用拦截器的 intercept()方法时,同时还需要调用

    ActionProxy execute方法,例如下面的代码:

     

    ActionProxy proxy = 

    actionProxyFactory.createActionProxy(NAMESPACE,

    NAME,null);

     

    assertEquals("success", proxy.execute());

     

    在这个场景中,测试代码可以断言的期望值包括:Action的结

    果,Action的值或是值栈中的值。下面的方法可以在 Action的执行

    前或者执行后获取这个 Action对象:

     

    MyAction action =

    (MyAction)proxy.getInvocation().getAction();

     

    下面的方法可以用来得到值栈:

     

    proxy.getInvocation().getStack()

     

     

     

    模块化

     

    当应用的规模变大以后,把程序分拆成各个模块的重要性就不

    言而喻了。这样一来,我们就可以把一个项目中开发的功能或者新

    的框架特性打包,并且在其他项目中重用。Struts 2已经把模块化作

    为了体系架构中的基本思想,开发人员可以独立工作,然后在其他

    人的工作基础上进行构建。

     

    下面是将应用程序模块化的几种方法:

     

    ♦  将配置信息拆分成多个文件——它本身不会对应用程序的

    分包造成影响,但是配置信息按照功能的界限进行拆分以

    后,管理起来就容易了很多,也减小了开发的难度

    ♦  把自包含的应用模块创建为插件——所有用来提供某一种

    特性的功能组成——包括Action、拦截器、拦截器栈、视

    图模板(除了JSP以外)等等——都可以被打包在一起,并架构目标 | 35

     

    http://www.infoq.com/cn

    作为独立的插件发布。例如配置浏览器插件9

    ,该插件提供

    了一个完整的模块,该模块可以为所在的应用程序添加一

    个用来查看配置信息的Web接口。

    ♦  创建新的框架特性——与特定应用无关的新功能可以组织

    成插件,应用到多个应用中。

     

    从技术角度来讲,这几种模块化的方式其实是一样的——它们

    都有同样的配置元素(除了名字可能不同以外,在使用插件时,

    struts-plugin.xml”文件会作为配置文件被自动加载),同样的目

    录结构,并且可以包括同样的框架和应用元素。

     

    上面的两种插件类型的唯一区别在于你从怎么概念上去理解它们,

    在发行包中放入的是哪些元素和配置文件。

     

    额外的配置元素

    因为插件可以为内部的框架功能提供替代的实现方式,所以需

    要做一些额外的配置。这些配置元素可以在“struts.xml”配置文件

    中使用,也用在了“struts-default.xml”文件中,但是它们更常用的

    情况是用来配置插件。

     

    在插件中,替代的实现方式是分两步来配置的:

    1.  使用 <bean />标签来定义替换的接口实现,并用一个

    唯一的 key来标识

    2.  使用<constant />标签来在多种可能的接口实现中进

    行选择

     

    下面我们来看看每一步的具体细节。

     

    <bean />标签可以用来为扩展点提供实现信息。下面的例

    子是“struts-default.xml”配置文件中一个对象工厂的配置:

     

    <bean name="struts" 

    type="com.opensymphony.xwork2.ObjectFactory" 

    class="org.apache.struts2.impl.StrutsObjectFactory" />

     

    在配置项的属性中包含了用来在 Struts 2里创建和使用替代对象

    实现(alternate object implementation)的所有信息。这些属性包括:

    ♦  class ——类的全名

                                                    

    9

     config browser插件的文档地址为http://struts.apache.org/2.x/docs/config-browser-

    plugin.html

     36 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    ♦  type ——类所实现的接口

    ♦  name ——每个 type的唯一简称

    ♦  static ——是否把静态对象方法注入到类实例中

    ♦  scope ——实例的作用域,可以是“default”

    request”,“session”,“singleton”或“thread

    ♦  optional—— 当值为“true”时,即使创建类实例的过程中

    发生错误,也会继续装载。

     

    然后,开发人员可以用<constant />标签来选择使用哪一

    个配置。这里只有两个属性——“name”属性提供了扩展点的名

    字,这个扩展点是在新的实现中会被更改的,“value”属性的值是

    <bean />标签中配置的唯一标识。

     

    <constant name="struts.objectFactory" value="plexus"

    />

     

    <constant />标签是把一个新值赋给已知属性的一种方

    式,但并不是唯一的方式。新值还可以在“web.xml”文件的“init-

    param”中修改,或者作为一个名-值对写入“struts.properties”配置

    文件。

     

    如果你不是在开发插件,而是在普通的“struts.xml”文件中使

    用这些技术,那么还有捷径可走。把一般来说会放在<bean />

    标签中的类名放进在<constant />标签中——这样就避免了使

    <bean />标签。

     

    下表列出了可配置的扩展点对应的接口与属性名。

     

    接口  属性名  Scope  Description

    com.opensymphony.

    xwork2.ObjectFactory

    struts.objectFactory  singleton 创建在框架中

    用到的对象—

    Action,结

    果,拦截器,

    业务对象等

    等。

    com.opensymphony.

    xwork2.

    ActionProxyFactory

    struts.

    actionProxyFactory

    singleton 创建

    ActionProxy

    com.opensymphony.

    xwork2.util.

    struts.

    objectTypeDeterminer

    singleton 判断一个 map

    或者 collection架构目标 | 37

     

    http://www.infoq.com/cn

    ObjectTypeDeterminer 中的 key和元

    素是什么

    org.apache.struts2.

    dispatcher.mapper.

    ActionMapper

    struts.mapper.class  singleton 从请求中得到

    ActionMapping

    并从

    ActionMapping

    中得到 URI

    org.apache.struts2.

    dispatcher.multipart.

    MultiPartRequest

    struts.multipart.

    parser

    per

    request

    解析 多部件 请

    求(文件上

    传)

    org.apache.struts2.

    views.freemarker.

    FreemarkerManager

    struts.freemarker.

    manager.classname

    singleton 载入和处理

    Freemarker

    org.apache.struts2.

    views.velocity.

    VelocityManager

    struts.velocity.

    manager.classname

    singleton 载入和处理

    Velocity模板

     

    <constant /> 标签和 “web.xml”文件中的“init-param”不仅

    仅可以用来定义扩展点属性。“struts.properties”文件中的所有属性

    都可以通过二者进行修改。

     

     

    惯例重于配置

     

    惯例重于配置是 Rails带入主流应用开发中的概念。它不是提供

    那些对于各个应用而言都很相似的配置文件,而是假定在绝大多数

    情况下,开发人员都会遵守特定的模式。这种模式具有足够的通用

    性,所以可以被认为是一种开发惯例,框架会默认使用这种模式,

    而不是为每一个新的应用都提供配置。在默认情况下,开发人员就

    不必再管理种种配置信息了。如果有的需求与惯例的配置信息不

    同,那么还可以根据需求进行修改,把默认模式覆盖掉。

     

    Struts 2采用了这个概念。松耦合在给 Struts 2带来高度灵活性的

    同时,也带来了配置上的高度复杂性。惯例在这二者之间做出了平

    衡,为我们提供了简洁而高效的开发者体验。

     

    Struts 2中“惯例重于配置”的应用可以通过以下几个例子来说

    明:

     38 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    ♦  隐式的配置文件加载——不需要显式配置“struts-

    default.xml”和“struts-plugin.xml”(对每一个插件而言)

    文件,它们会被自动加载。

    ♦  Code Behind插件——在使用 code behind 插件时,它会混

    合使用 action名和结果字符串在结果模板中进行自动搜

    索,所以“/user/add.action” 这个 action的“success”结果

    会返回“/user/add-success.jsp”结果模板,“error”结果会

    返回“/user/add-error.jsp”结果模板。

    ♦  默认的结果和结果类型—— 在配置Action的时候,如果使

    用默认的“success”结果和 JSP结果类型的话,就不需要

    对它们进行配置

    ♦  织入(WiringSpring业务服务——在安装了 Spring框架

    插件以后,就不必为每个 Action所需的 Spring提供的业务

    服务进行配置,这些 业务服务会被自动织入到 Action里。

     

    在前面的章节里,我们看到了很多默认的设置,包括如何通过

    配置来重写属性值和提供新的默认设置。在下面一章中,我们将要

    介绍的是如何通过更多的配置项和惯例来提高生产力。

     

    39

    5

    InfoQ中文站 Agile社区

    关注敏捷软件开发和项目管理

    http://www.infoq.com/cn/agile

    提高效率技巧

     

    本章所要介绍的是一些在使用 Struts 2开发 web应用时,能够提

    高生产效率的技巧、技术与特性,这些都是开发人员应当牢记的。

    这些 tip范围很广,包括从列出默认值,到说明应当实现的接口,以

    及如何创建自定义的声明式验证。 

     

    本章中描述的信息只是一个简单的介绍——如果你对哪一个tip

    感兴趣,就不妨深入研究一下。你可以参考Struts2的文档

    http://struts.apache.org/2.x/docs/guides.html,或者是通过搜索来看看其

    他开发人员的想法,以及他们是如何进行应用的。 

     

    最后,当你阅读每一个小节的时候,都应该想一下当前的 tip

    该如何与其他 tip配合使用,它与其他 tip的区别是什么,你又该怎

    样把它应用到 web开发中去。这样你对它的理解才能更上一个台

    阶。

     

    Struts2 的插件体系结构保证了它的可持续发展。你需要常常访问

    Struts2 的插件注册页面以获得最新的开发进展。这个页面中包括了

    所有的插件相关的声明,地址为

    http://cwiki.apache.org/S2PLUGINS/home.html。它上面已经有了很多

    第三方的插件,例如JSONGWTSpring WebFlow

     

     

    重用 Action 的配置

     

    我们曾经讲过把 action package的组织形式来配置管理,并且

    描述了 package如何继承其它的 package——但是这样做所带来的好

    处还没有说清楚。下面我们来看一个完整的例子。这个应用程序用

    来向游客提供动物园信息。每个洲都会对应有一个 portal页面,提供

    动物,地图等等数据。 40 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

     

    完成这项功能的一种方式是让用户可以调用类似

    www.myzoo.com/home.action?continent=asia”这样的URL。这样强

    化了应用程序的逻辑性,并且可以很容易判断现在所请求的是哪个

    洲的信息。但是,当我们依赖于要渲染给用户的信息的定义方式

    时,灵活性就丧失了,因为硬编码的路径信息被添加到了action或者

    视图中。

     

    灵活一点的方式是提供类似

    www.myzoo.com/asia/home.action ”这样的URL。在使用这种方案

    时,我们可以提供一个action基类,并配置到默认的package下面。每

    一个继承的package都可以访问这个action基类。所以如果不进行额外

    配置的话,“www.myzoo.com/home.action”实际上调用的就是

    www.myzoo.com/asia/home.action”。

     

    更进一步来说,视图也可以不用任何额外的配置来进行自定

    义。如果默认的 package下面的 action是这样的:

     

    <action name="home" class="com.fdar.infoq.HomeAction"

    >

    <result>portal.jsp</result>

    </action>

     

    那么Struts2所渲染的JSP就会依赖于URL所提供的,用户调用的命名

    空间。于是如果URL为“www.myzoo.com/home.action”,那么

    /portal.jsp”就会被渲染,如果URL

    www.myzoo.com/asia/home.action”,那么被渲染的页面就是

    /asia/portal.jsp”。强调一下,这个功能并没有进行多余的配置—

    —因为配置信息提供的是一个JSP相对路径而不是特定的路径名。

     

     

    在配置中使用模式匹配调配符

     

    有的时候,action的配置文件大小会以令人难以置信的速度增

    加。为了避免这种现象,我们需要使用模式匹配。它的工作方式是

    定义一个或多个模式,而 URL会与这些模式保持一致。

     

    举个例子会更容易说明这一点。假设你的应用中 URL的模式是

    这样的:“/{module}/{entity}/{action}.action”。这是很常见的模提高效率技巧 | 41

     

    http://www.infoq.com/cn

    式,对应的 URL可能是:“/admin/User/edit.action”,

    /admin/User/list.action”和“/admin/User/add.action”。

     

    在类的配置中,会有一个 Struts2action类,名字为

    {entity}Action”,而每一个{Action}都是 action类里面的一个方

    法。所有对 action中方法的调用要么返回一个被显示的实体的更新

    页面,要么返回所有被显示的实体的一个列表。

     

    在我们的例子中,“struts.xml”配置就是这样的:

     

    <action name=”*/*/*” method=”{3}” 

    class=”com.infoq.actions.{1}.{2}Action”>

     <result name=”view”>/{1}/update{2}.jsp</result>

     <result name=”list”>/{1}/list.jsp</result>

    </action>

     

    action的名字中,每一个星号都是一个通配符。在这个例子

    中,我们全都用了星号——其实也不必如此。比如说,如果你想要

    把所有对实体的 view动作映射到一起,那么类似于

    name=/*/View*”这样的配置就能完成要求。形如{1}{2}等等的

    标识符用来获取通配符所对应的值(数字表示了所要获取的值对应

    的通配符的位置,顺序是从左到右)。

     

    在“struts.properties”文件中(或者使用“struts.xml”的

    constant标签),你需要确保正确配置了下面的属性:

     

    struts.enable.SlashesInActionNames = true

     

    这个属性设为 true以后,action的名字中就可以使用斜杠了。

    Struts2的默认设置是不允许 action的名字中出现斜杠的,需要用

    package来分割命名空间。

     

    最后,如果你是提供验证和转换的属性文件而不是注解的话,那么

    就没有什么捷径了。我们会在下面的部分谈到这一点。每一项都要

    包括 action的全名和所需的扩展:例如,前面的例子就要包括“edit-

    validation.xml”和“edit-conversion.xml”,以及

    com.infoq.actions.admin”这个 package

     

     

    使用替代的URI映射方法

     

     42 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    我们除了在配置中使用通配符以外,还可以使用自定义的映射

    方案,改变从 URI action和被调用方法的映射关系。用这种方法

    可以减少配置信息,并且可以在整个应用中保持一致。你可以把

    URI模式与 session信息或其他任何你能想到的信息合并,来决定要

    调用哪一个 action

     

    这种方法需要实现 ActionMapper接口,在该接口中有两个要实

    现的方法:一个是 getMapping(),它把 URI转换成已知的配置;另

    一个是 getUriFromActionMapping(),它把一个 action配置转换成

    URI。如下所示:

     

     

    public interface ActionMapper {

     

    ActionMapping getMapping(

    HttpServletRequest request, 

    ConfigurationManager configManager);

     

    String getUriFromActionMapping(ActionMapping mapping);

    }

     

    ActionMapping 类中提供了要调用的 action的命名空间,名

    称,方法,结果和参数。ConfigurationManager 可以用来访问

    配置信息的提供者(然后就可以进行进一步的自定义)。

     

    Action mapper需要在“struts.xml”中进行配置后才能被使用。

    Name type属性的值是不变的——只需要把 value属性的值改成自

    定义的 ActionMapper的实现类。

     

    <constant name="struts.mapper.class"

     value="com.fdar.infoq.MyActionMapper" />

     

    值得高兴的是我们不需要在所有用到的地方都去实现

    ActionMapper这个接口,Struts2已经有了几种不同类型的实现。

     

    Restful2ActionMapper类提供了一个ReST型接口的实现,它借

    鉴了Ruby on Rails里易于使用的URI的设计思路。要注意的是,在

    Struts2的文档中,这个实现还只是一个试验品。

     

    Restful2ActionMapper 首先做的事情是判断要使用的命名

    空间和 action,与你所想的一样——URL的最后一个元素是 action

    名,之前的都是命名空间。但是也有例外,因为属性也会通过 URI提高效率技巧 | 43

     

    http://www.infoq.com/cn

    传入。把 action名,属性名和属性值映射到 URI中的模式是这样

    的:

     

    http://HOST/PACKAGE/ACTION/PARAM_NAME1/PARAM_VALUE1/PARAM

    _NAME2/PARAM_VALUE2

     

    PARAM_NAME/PARAM_VALUE”这样的名/值对的数量是没有

    限制的,如果PARAM_NAME1 是“id”的话,那么URI可以简化

    为:

     

    http://HOST/PACKAGE/ACTION/PARAM_VALUE1/PARAM_NAME2/PARAM

    _VALUE2

     

    action被确定下来以后,下一步就是要找到需要调用的方

    法。这可以使用 HTTP方法来做出决定。因为 HTML不支持 PUT

    DELETE方法,所以要有另外的请求属性“__http_method”来提供

    方法信息。

     

    下面是 HTTP方法和 URL合并以后的结果:

     

    ♦  GET: “/user” —— action被单独使用时,“index”方法

    会被调用

    ♦  GET: “/user/23” ——action与参数名/值对一起使用时,

    view”方法会被调用,在这里“id”属性的值被设为

    23”

    ♦  POST: “/user/23” ——这里 HTTP方法是 POST而非 GET

    于是“create”方法将会被调用,“id”或者其他用于标识

    的值会被包含在 URL中,而包含更新信息的名-值对会放

    POST数据中

    ♦  PUT: “/user” ——“update”方法会被调用,与 POST场景

    类似,包含信息的名-值对会放在 POST数据中而不是 URL

    里面

    ♦  DELTE: “/user/23” ——“remove”方法会被调用,在 URL

    里面提供了唯一的标识符(在这里是“id”,其值为

    23”)

    ♦  GET: “/user/23!edit”——“!”被用来描述方法名,所以这

    里会被调用的是“edit”方法 

    ♦  GET: “/user/new” ——“new”后缀表示“editNew”方法

    会被调用

     

     44 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    这里还有一个 CompositeActionMapper类。这个实现可以帮助你

    把多个独立的 ActionMapper实现类串连起来。这个串连序列中的每

    一个类都会被检查,看它是否能够解析 URI。如果 URI被解析成

    功,那么就返回结果,如果没有,序列中的下一个类就会被继续检

    查;如果最终也没有找到匹配的结果,就会返回一个 null

     

    和一般的 ActionMapper配置一样,CompositeActionMapper配置

    中包含有 constant节点,它列出了要被串连在一起的 ActionMapper

    的实现类的名字。

     

    <bean name="struts"

     type="org.apache.struts2.dispatcher.mapper.Action

    Mapper" 

     class="org.apache.struts2.dispatcher.mapper.Compo

    siteActionMapper" />

     

    <constant name="struts.mapper.composite" 

     value="org.apache.struts2.dispatcher.mapper.Defau

    ltActionMapper,

     

     org.apache.struts2.dispatcher.mapper.RestfulActio

    nMapper" />

     

     

    了解拦截器的功能

     

    拦截器在 Struts2框架中起到了至关重要的作用。通过掌握已有

    的拦截器,你就可以理解在 action处理过程中的每一个步骤了。

     

    另外一个好处会在调试 action的时候呈现出来。有时候 action

    没有那些应有的数据。这常常都是由于拦截器没有被正确应用,或

    者是拦截器应用的顺序有问题导致的。通过掌握每一个拦截器的作

    用,问题的定位和解决就容易了很多。

     

    下面是一些可以立刻使用的拦截器,并提供了对应的功能描

    述。

     

    拦截器名  描述

    alias  将同一个参数在请求之间进行命名转换。

    chain  使上一个 action的属性对当前 action可用。常

    <result type="chain">结合使用(在

    上一个 action中使用)。 提高效率技巧 | 45

     

    http://www.infoq.com/cn

    conversionError  ActionContext的转换错误添加到 Action

    字段错误中。

    createSession  自动创建一个 HttpSession,当某一个拦截

    器需要 HttpSession协同工作时(如

    TokenInterceptor)是很有用的。

    debugging  提供多个不同的调试场景,用来查看页面背后

    的数据结构信息。

    execAndWait  在后台执行 action,并发送给用户一个等候页

    面。

    exception  把异常映射到对应的结果。

    fileUpload  为便捷的文件上传提供支持。

    I18n  记忆用户 session所使用的 locale

    logger  输出 action名。

    model-driven  如果 action实现了 ModelDriven,该拦截器会

    getModel方法的结果放到值栈上面。

    scoped-model-driven 如果 action实现了 ScopedModelDriven

    该拦截器会从 scope中获取并存储模型,然后

    通过 setModel方法将其赋给 action

    params  把请求参数赋给 action

    static-params  把“struts.xml”定义的参数传给 action,它们

    <action />标签下的<param />子标签。

    scope  提供了将 action状态存储在 session

    application scope中的简单机制。

    servlet-config  提供了对表示 HttpServletRequest

    HttpServletResponse Map的访问。

    timer  输出 action的执行时间(包括内嵌的拦截器和

    视图)。

    token  验证 action中的有效 token,避免提交重复的

    表单。

    token-session  token拦截器一行,但是在处理无效的 token

    时,将提交的信息保存在 session中。

    validation  使用在 action-validation.xml中定义的验证器来

    进行验证。

    workflow  调用 action中的 validate方法。如果有异常的

    话,就会返回 INPUT视图。

    store  session中获取和保存实现了

    ValidationAware action的消息/异常/字段异

    常。

     46 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    checkbox  添加 checkbox的自动处理代码,可以检测没有

    被选中的 checkbox,并给它一个默认值(通常

    false),然后作为参数添加。它使用了一个

    特定名称的隐藏字段来检查未提交的

    checkbox。未选中的默认值可以被那些不是

    boolean值的checkbox重写。

    profiling  激活对参数的性能监测

    roles  只有当用户具有正确的 JAAS角色时才能够执

    action

    prepare   如果 action实现了 Preparable,那么就调用它

    prepare()方法。

     

     

    使用提供的拦截器栈

     

    拦截器栈可以将那些应用于各类 action的拦截器进行功能分

    组。拦截器栈构建用于 CRUD操作,验证 action输入,或是其他任

    何你所需的功能。但是在创建自己的栈以前,最好先来看一下

    Struts2提供的拦截器栈。很多标准配置都已经配置完成并可以即刻

    使用了。另外,每一个插件都可以提供自己的拦截器栈,如果要使

    用插件所提供的功能的话就要使用插件的拦截器栈。

     

    有两种方法可以使用框架提供的拦截器栈——或者把 action

    到提供了拦截器栈(通过零配置的注解或者“struts.properties”中的

    constant)的package下面,或者让定义的新 package(其中包括了你

    创建的 action)继承提供了拦截器栈的 package

     

    <package name="mypackage" 

    extends="struts-default" namespace="/mypackage">

     

     …

    </package>

     

    我们曾经说过,在把应用程序部署为产品之前,你需要检查一

    下拦截器栈,看看你是否需要其中的某/每一个拦截器。

    paramsPrepareParamsStack”和“defaultStack”里面包含有

    chain”,“il8n”,“fileUpload”,“profiling”和“debugging

    这几个拦截器。它们都是不怎么常用的,我们可以把它们移走,减

    少不必要的处理工作。

     提高效率技巧 | 47

     

    http://www.infoq.com/cn

     

      Description

    basicStack  Struts2提供的最常用的栈。提供了异

    常处理,将 HTTP对象和请求/表单参

    数注入 action,和处理转换错误的功

    能。

    validationWorkflowStack  basic栈中添加验证和工作流的功

    能。 

    fileUploadStack  basic栈中添加对自动文件上传的支

    持。

    modelDrivenStack  basic栈中添加对模型驱动 action

    支持。  

    chainStack  basic栈中添加对 action串连的支

    持。 

    i18nStack  basic栈中添加国际化的功能。 

    paramsPrepareParamsStack  这是 Struts2提供的最复杂的一个栈。

    它的应用场合是当 action prepare()

    法被调用时,用传入的请求参数载入

    数据(或者执行其他任务),然后重

    新用请求参数来重写一些载入的数

    据。它的一个典型应用是更新对象。

    id来从数据库中读取对象,然后用

    从请求中传入的参数重写一些读取的

    数据。

    defaultStack  这是默认的栈。它为绝大多数的应用

    场景提供了所有的功能。实际上,它

    包括了核心发布版中几乎所有的可用

    拦截器。

    completeStack  这个栈提供了“defaultStack”的别

    名,用来向后兼容 WebWork的应用。 

    executeAndWaitStack  basic栈中添加异步支持 action的功

    能。 

     

     

    利用返回类型

     

     48 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    返回类型使得开发者可以对要渲染给用户的元素进行组合和匹

    配。实际上,一个 action可以有多个结果,而且每个结果都可以有

    不同的结果类型。

    另外一点也需要注意:结果可以是可见的,也可以是不可见

    的。比如说,我们可以返回 HTTP头。

     

    下面是一些已经预配置好的结果类型和一些简要说明。如果你

    action放到“struts-defaultpackage下面,或者是继承这个

    package,这些结果类型就可以使用了:

     

    名称  描述

    chain  将一个 action的执行与另外一个配置好的

    action串连起来。用第一个 action getter

    方法和第二个 action setter方法来完成

    action之间属性的复制。

    dispatcher  渲染 JSP。这是默认的结果类型,如果在

    action配置中没有配置其他的结果类型,它

    就会被使用。

    freemarker  渲染 Freemarker模板。

    httpheader  返回 HTTP头和用户定义的值。

    redirect  重定向到任意的 URL

    redirect-action  重定向到配置好的 action。可以用来提供

    post完成以后的重定向功能。

    stream  将数据流返回给浏览器。可以用来把数据

    注入 PDFMicrosoft Work,图像或其他数

    据中。

    velocity  渲染 Velocity模板。

    xslt  使用 XSLT来转换已执行完毕的 action

    属性格式。

     

     

    利用数据转换

     

    Web开发的一个常见任务就是把基于字符串的表单数据转换成

    适当的类型,交给模型或者业务服务方法使用。通常这是繁重的人

    工处理过程。Struts2提供了数据转化功能,将这个过程做了简化。

    内置的转换机制可以将 String转化成下面任意一种格式:

     提高效率技巧 | 49

     

    http://www.infoq.com/cn

    ƒ  String

    ƒ  Boolean boolean

    ƒ  Character char

    ƒ  Integer int

    ƒ  Float float

    ƒ  Long long

    ƒ  Double double

    ƒ  Date ——使用与当前请求相关联的 locale

     

    这样 action中的 setter方法就可以从“setId(String id)”改成

    setId(int id)”了。我们再也无须为每一个值进行类型转换,因为它

    们会以正确的类型传给 action,我们只需简单使用即可。

     

    要进行自定义的类型转换时,你需要实现 StrutsTypeConverter

    这个类。里面有两个需要实现的方法;一个是把字符串转换成新类

    型的类,一个是把新的类型转换成字符串。

     

    public class MyTypeConverter extends

    StrutsTypeConverter {

     

    public Object convertFromString(

    Map context, String[] values,

    Class toClass) {

       …

     }

     

    public String convertToString(Map context, Object o) {

     …

    }

    }

     

    如果在类型转换时出现问题,那么应该抛出一个

    TypeConversonException,告知框架转换无法完成。

     

    action中使用新的转换器之前,需要对它进行配置。这里可

    以使用注解(下一节会进行解释)或是使用一个单独的“*-

    conversion.properties”文件。如果正在使用转换功能的 action叫做

    MyAction”,那么你需要在同一个 package下面创建一个名为

    MyAction-conversion.properties”的文件。文件内容为:

     

    typeVal = MyTypeConverter

     

     50 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    等式左边的“typeVal”是请求名或是需要转换的表单值,等式

    右边是转换类的全限定名。

     

    如果你在多个 action中都使用转换功能的话,那么就可以使用

    一个全局的配置文件“xwork-conversion.properties”。这个文件放在

    应用程序的 classpath根目录下。其内容为:

     

    MyType = MyTypeConverter

     

    等式左右是要转换的类型和转换器所对应的类的全限定名。注

    意这里用的是要转换的类型的类名,而不是请求名或表单值。所以

    action setter就是“setTypeValue( MyType type)”。

     

     

    利用列表数据项支持

     

    Struts2可以通过 list来把列表数据在 HTML用户界面和 action

    之间进行便捷的传递。让我们看一个例子。这是一个 Person类,每

    一个属性都有对应的 getter setter(这里省略掉了)::

     

    public class Person {

     int id;

    String name;

     int age;

     float height; 

    }

     

    我们的 action需要有一个 person列表:

     

    public class MyAction {

     

     public List getPeopleList() { … }

     public void setPeopleList( List peopleList ) { … }

     

     …

    }

     

    在把 Person类作为 MyAction中的一个元素使用之前,我们需要

    添加一些配置信息。在 MyAction的同一个 package下面创建一个名

    为“MyAction-conversion.properties”的文件。该文件名和验证文件

    的命名方式一样,在 action名字后面跟着“*-conversion.properties

    后缀。文件内容为:

     提高效率技巧 | 51

     

    http://www.infoq.com/cn

    Element_peopleList=Person

     

    Element_”前缀是一个常量,它后面的“peopleList”是 action

    类中的 list属性名。等式右边是要放到 list里面的类的全限定名。

     

    最后,我们还需要把这个 list渲染给用户:

     

    <ww:iterator value="peopleList" status="stat">

     <s:property value="peopleList[#stat.index].id" />

     <s:property value="peopleList[#stat.index].name"

    />

     <s:property value="peopleList[#stat.index].age"

    />

     <s:property

    value="peopleList[#stat.index].height" />

    </ww:iterator>

     

    因为 list是有索引的,所以我们使用了迭代器状态对象的索引属

    性来引用被显示的对象。对当前的例子而言,这不是最有效率的办

    法;因为<s:property />标签的 value还可以简化成“id”,

    name”,“age”和“height”。这里只是想让你明白,要得到可

    编辑的表单需要哪些东西。

     

     

    对于一个使用了相同元素的列表型可编辑的表单,其 JSP为:

     

    <s:form action="update" method="post" >

     

    <s:iterator value="peopleList" status="stat">

     

    <s:hidden 

    name="peopleList[%{#stat.index}].id" 

    value="%{peopleList[#stat.index].id}"/> 

    <s:textfield label="Name" 

    name="peopleList[%{#stat.index}].name" 

    value="%{peopleList[#stat.index].name}"

    /> 

    <s:textfield label="Age" 

    name="peopleList[%{#stat.index}].age" 

    value="%{peopleList[#stat.index].age}" /> 

    <s:textfield label="Height" 

    name="peopleList[%{#stat.index}].height" 

    value="%{peopleList[#stat.index].height}"

    /> <br/>

     

    </s:iterator>

     

     52 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    <s:submit value="Update"/>

     

    </s:form>

     

    注意这段代码中的“name”和“value”属性和上面的“value

    属性很相似。区别在于,在“name”属性中,我们需要用正确的

    token把“#stat.index”包起来,从而得到实际的索引值,再用它来获

    取真正的值。而在“value”中,整个表达式都被包了起来。Struts2

    用这段代码生成一个 ArrayList,里面都是计算后的 People对象,然

    后用 setPeopleList()方法把这个 ArrayList赋值给 action

     

    为了允许 Struts2可以在这个 list中创建新的对象(可能你是在

    用户界面中动态创建对象的),把下面这行代码加入“MyAction-

    conversion.properties”文件中:

     

    CreateIfNull_peopleList = true

     

     

    Action中暴露领域模型

     

    介绍过数据转换和容器后,有一扇门已经向你打开了。我们用

    不着非要在 HTML表单字段中使用 String值,使用正确的类型就

    行。我们还看到通过正确的 setter方法,请求属性和表单字段值都可

    以直接赋给 action

     

    我们还可以更进一步。Struts2中有一种名为“模型驱动 action

    的概念,可以减少在调用业务服务时,用来把用户界面的字段映射

    为领域对象或者值对象的代码。

     

    模型驱动的 action需要实现 ModelDriven接口。这个接口只有一

    个方法,用来把 action被指派的对象作为模型返回。

     

    public interface ModelDriven  {

        

        java.lang.Object getModel();

    }

     

    被用来处理 action的拦截器栈也需要包含“模型驱动”的拦截

    器。包括这样一个拦截器的栈有以下几种:“modelDriven”,

    defaultStack”,“paramsPrepareParamsStack”和

    completeStack”。这个拦截器会从 action中得到模型对象,把它放提高效率技巧 | 53

     

    http://www.infoq.com/cn

    到值栈中相应的 action上面的位置,这样请求或者表单值就会直接

    赋值给模型而不是 action。同样,反过来讲,在 JSP使用和显示的值

    也就会从模型中获取。

     

    这不是那种“要么全是,要么全非”的场景,如果有的请求或

    者表单值在模型中找不到对应的 setter,那么它们就会被传递给

    action。同时,如果 JSP在模型中找不到所需的值,它也会在值栈中

    向下查找,然后从 action得到想要的结果。

     

    通过综合运用这几种技术,你可以在模型或者 action 中进行赋值,

    或者读取相应的值,而不必显式指定要操作的目标。

     

     

    尽可能使用声明式验证

     

    Struts2应用中提供验证的方式有两种——编程式和声明式。

     

    要提供编程式验证的话,action就要实现 Validateable接口。该

    接口只有一个方法,在方法中需要执行验证操作:

     

    void validate();

     

    为了将验证中出现的问题反馈给用户,action还需要实现

    ValidationAware 接口。这个接口更为复杂一些,它里面的方法

    可以用来添加验证错误,判断当前是否有验证错误,等等。

     

    如果可能的话,你的 action可以继承ActionSupport类,在该类

    中提供了以上这些接口的缺省实现。只有当验证操作极其复杂的时

    候,我们才该使用编程式验证。更好的解决方案是采用声明式验

    证。

     

    每一个需要声明式验证的 action都需要在类中进行注解(后面

    对此会进行讨论),或者要有一个相应的 XML文件。对于

    MyAction这个类,其 XML文件就是“MyAction-validation.xml”,

    并会和它放在同一个 package下面。处理这个 action的拦截器栈需要

    包含有“validation”(用来进行验证)和“workflow”(当验证失

    败时用来进行重定向,以返回到“input”结果)拦截器。这样的拦

    截器栈有以下几个:“validationWorkflowStack”,

     54 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    paramsPrepareParamsStack”,“defaultStack”和

    completeStack”。

     

    下面是验证文件的一个例子:

     

    <!DOCTYPE validators 

    PUBLIC "-//OpenSymphony Group//XWork Validator

    1.0.2//EN" 

    "http://www.opensymphony.com/xwork/xwork-validator-

    1.0.2.dtd">

    <validators>

    <field name="count">

    <field-validator type="int" short-

    circuit="true">

    <param name="min">1</param>

    <param name="max">100</param>

    <message key="invalid.count">

    Value must be between ${min} and

    ${max} 

    </message>

    </field-validator>

    </field>

    <field name="name">

    <field-validator type="requiredstring">

    <message>You must enter a name.</message>

    </field-validator>

    </field>

    <validator type="expression" short-circuit="true">

    <param

    name="expression">email.equals(email2)</param>

    <message>Email not the same as email2</message>

    </validator>

    </validators>

     

     

    在这个例子中,有些东西是应该注意的:

    ƒ  每一个字段都可以含有一个或多个“field-validator”

    节点

    ƒ  每一个字段的 validator是按照定义顺序执行的

    ƒ  每一个字段的 validator都有一个“short-circuit”

    性;如果其值为 true而验证失败,那么剩下的所有验证都会

    被跳过,该字段就会直接返回一个失败的结果

    ƒ  Message节点可以包含一个“key”属性,用来从

    message bundle中查找需要显示给用户的消息;如果找不到

    对应的 message bundle key的话,那么就返回节点的 value

    性的值 提高效率技巧 | 55

     

    http://www.infoq.com/cn

    ƒ  Validator的配置信息(例如 min max)和值栈中的

    值一样,都可以通过把值放在“${”和“}”之间,来显示

    到验证消息中。

    ƒ  Validators 的作用域可以是“field”或者

    expression”;具有“expression”作用域的 validator可以

    同时作用于多个字段

     

    下面是关于validator类型和相应描述的一个完整列表。在

    http://struts.apache.org/2.x/docs/validation.html可以得到包括配置相关

    的更多信息。

     

    名称  描述

    required  确保该属性不是 null

    requiredstring  确保一个 String类型的属性不是 null,并且非空

    stringlength  检查 String的长度范围是否与所期望的一致

    int  检查 int类型的数字是否超出所期望的大小范围

    double  检查 double类型的数字是否超出所期望的大小范

    date  检查 date类型的属性是否超出所期望的范围

    expression  使用值栈来估算一个 ONGL表达式(必须要返回

    boolean值)

    fieldexpression  使用 OGNL表达式来验证字段

    email  保证该属性是一个有效的 email地址

    url  保证该属性是一个有效的 URL

    conversion  检查该属性是否有转换错误

    regex  检查该属性的值是否与某个正则表达式相匹配。

    visitor  Visitor validator可以把对字段的验证动作推迟到这

    个字段所属的类的特有的另一个验证文件中执行。 

     

    比如说,你在使用模型驱动的 action,在模型中

    有一个对应于 Person类的“person”属性。如果

    该模型正在被多个 Action使用,那么你大概就会

    想要把验证信息抽取出来进行重用。Visitor 验证

    提供了这样的功能

     

    另外,你还可以创建自己的 validator。你自定义的 validator需要

    实现 Validator接口(用于表达式验证)或 FieldValidator接口(用于

    字段验证).

     

     56 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    新的 validator需要在“validators.xml”中注册,该文件存在于

    classpath的根目录下。通常这个文件可以从发行的 JAR包中访问

    到,但是如果人为提供了这个文件的话,那么发行包中的文件就会

    被忽略。所以在添加新的 validator之前,你需要从 Struts2 JAR包中

    把“validators.xml”这个文件拷贝出来,放到项目 classpath的根目

    录下,然后再把新的 validator添加进去。这样当前已有的 validator

    才会被包含到应用中来。和其他配置一样,validator的配置信息也很

    简单,只需要一个统一标识符和 validator的类名。

     

    <validators>

    <validator name="postcode" 

    class="com.validators.PostCodeValidator"/>

    </validators>

     

     

    CRUD操作放到同一个Action

     

    把一个模型驱动的 Action和“preparable”拦截器/接口,Action

    配置中的通配符,还有验证与工作流结合以后,你就可以把 CRUD

    操作放到一个 Action里面去。这种方式与

    Restful2ActionMapper 的功能很相似。

     

    我们使用的 URL模式为“/{model}/{method}.action”。例如,

    调用 UserAction类中的“add”方法,那么 URL就是

    /User/add.action”。我们同样还需要确保有对应的结果映射——

    success”(缺省情况),“input”(验证出现问题的情况)和

    home”或是一个默认页面。每一个页面都是模型所特有的。

    success”会按照 redirect-after-post模式,重定向到 Action

     

    下面是相应的“struts.xml”文件:

     

    <action name=”*/*” method=”{2}” 

    class=”com.infoq.actions.{1}Action”>

     <result type=”redirect”>/{1}/view.action</result>

    <result name=”view”>/{1}/view.jsp</result>

     <result name=”input”>/{1}/edit.jsp</result>

     <result name=”home”>/{1}/home.jsp</result>

    </action>

     

    这个 Action需要继承 ActionSupport类(提供了验证和错误消息

    处理的实现),并实现 ModelDriven Preparable接口。与这两个接提高效率技巧 | 57

     

    http://www.infoq.com/cn

    口协同工作的拦截器栈可以帮助我们简化对接口的实现,下面来看

    一下具体细节。

     

    ModelDriven接口中有一个方法,getModel(),它与“model-

    driven”拦截器相结合,把 Action中的模型放到值栈中,位于 Action

    之上。当请求参数被传入时,它们将会被赋给模型而不是 Action

    这就是我们想要看到的——对模型赋值而非 Action——然后我们只

    需要更新 Action。但是如果模型里面已经有了数值,而我们并不想

    重写它们呢?

     

    这时就需要“paramsPrepareParamsStack”拦截器栈发挥作用

    了。下面的步骤就是我们想要做,而栈中的拦截器帮我们做了的工

    作:

     

    1.  设置 Action中的 id—— “params” 拦截器

    2.  Action执行某些逻辑,或是创建一个新模型,或是从

    Service上,从数据库中获取一个已有的模型——

    prepare” 拦截器调用 Preparable接口中的 prepare()

    方法

    3.  当模型已经存在后,把请求参数赋值给模型——先是

    model-driven”拦截器,然后又是“params”拦截器。

    4.  查看模型是否存在验证错误,在必要的情况下重定向回

    input页面—— “validation”和“workflow”拦截器

    5.  执行被调用的方法中的逻辑——普通的 action处理

     

    通过遵守这些约定,你的 Web应用中所有的模型或者实体对象

    都可以通过上面的“struts.xml”文件来管理。只是 Action的实现需

    要根据情况不同而发生变化。在我们的例子中,UserAction的代码

    为:

     

    public class UserAction 

    extends ActionSupport 

    implements ModelDriven, Preparable{

     

     private User user;

     private int id;

    private UserService service;  // user 业务服务

     

     …

     

    public void setId(int id) {

    this.id = id;

     58 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    }

     

    /** 如果user 不存在的话,就创建一个新的,否则就根据特定

    id 来载入user */

     

    public void prepare() throws Exception {

    if( id==0 ) {

    user = new User();

    } else {

    user = service.findUserById(id);

    }

    }

     

    public Object getModel() {

    return user;

    }

     

    /** 创建或更新user,然后查看该user */

     public String update() {

    if( id==0 ) {

    service.create(user);

    } else {

    service.update(user);

    }

    return “redirect”;

     }

     

    /** 删除该user,然后跳转到默认的home 页面 */

     public String delete() {

       service.deleteById(id);

    return “home”;

     }

     

    /** 显示页面,让用户可以看到已存在的数据*/

     public String view() {

    return “view”;

     }

     

    /**显示页面,让用户可以看到已存在的数据并对其进行修改*/

     public String edit() {

    return “input”;

     }

    }

     

    当用户想创建一个新的 user或是修改一个已存在的 user时,

    edit()方法就会被调用。这个方法很简单,返回“input”结果,然后

    就会给用户一个 HTML表单页面。该表单的 Action指向一个映射到

    update()方法上的 URLupdate()方法实际上还可以被拆分成两个独

    立的方法,但是这会让 HTML表单变得复杂,而且因为通过唯一的提高效率技巧 | 59

     

    http://www.infoq.com/cn

    key字段可以很容易的判断一个对象是否存在,所以这样做的意义也

    不大。

     

    最后,view()方法是一个简单的 pass-through方法,它将页面跳

    转到一个显示 user的页面上去。delete()方法会根据特定的 id来删除

    user,然后返回到用户默认的 home页面。

     

    所有这些方法基本上都没有什么逻辑实现,所以很容易就会被

    误解为什么什么都没做。实际上,这里还是有功能实现的,但是它

    是一个横切的关注点,并且被重构到了 prepare()方法中。对于

    edit()update() view()这几个方法而言,如果有模型存在的话,那

    么它就要获取到该模型;如果没有的话,那么就要创建一个模型。

     

    这种 Action实际上还是很简单的,而且很容易参数化,以用于

    不同的模型类和 service。有了这样的微架构,那么在开发 CRUD

    应用时,最复杂的事情就变成创建页面模板了。

     

     

    在可能的地方使用注释

     

    Struts2是在JDK5上面开发的,所以可以使用注解来代替配置。

    注解可以用多种方式来进行自我阐述。在Strut2网站上有关于注解的

    更多信息,也包括代码示例。网址为

    http://struts.apache.org/2.x/docs/annotations.html 

     

    零配置

    “零配置”通过使用注解来避免对“Action”进行 XML配置,

    如果你一直都是继承既有的 package的话,那么“struts.xml”文件也

    可以省略掉。它包括以下四种类级别的注解,分别为:

    注解  描述

    Namespace  所期望的命名空间(在“struts.xml”文件中也有

    定义)的字符串值

    ParentPackage  所期望的父 package的字符串值

    Results  Result”注解列表

    Result  提供了 Action结果的映射,它有四个属性:

    ƒ  name ——action方法的结果名

    ƒ  type—— 结果类型

    ƒ  value——任意的结果值。可以是 rediect

     60 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    结果类型对应的 action名,也可以是

    dispatcher结果类型对应的 JSP

    ƒ  parameters ——字符串参数组成的数组

    在使用这些注解的时候,还需要进行额外的一些配置。在

    web.xml filter配置中,需要指定哪些 package是使用了注解的。配

    置如下所示,其中参数名必须为“actionPackages”,参数的值就是

    package的名称列表。

     

    <filter>

    <filter-name>struts</filter-name>

    <filter-class>

    org.apache.struts2.dispatcher.FilterDispatcher

    </filter-class>

    <init-param>

    <param-name>actionPackages</param-name>

    <param-value>user.actions,other.actions</param-

    value>

    </init-param>

    </filter>

     

    被配置过的每一个 package和它的子package都会被扫描到,看

    其中哪些类实现了 Action或者类名以“Action”结尾,然后注解就

    会被加入到运行时配置中去。如果没有使用 namespace注解的话,

    那么命名空间就会由 package名来生成。把“actionPackages”配置

    值中使用的 package名称截掉,就得到了命名空间。也就是说,如果

    某个被配置好的 action的名字是“actions.admin.user.AddAction”,

    而“actionPackages”的值为“actions”,那么这个 action的命名空

    间就是“/admin/user”。

     

    使用这些注解并不能完全避免 XML的使用——但这是一个良好

    的开始。例如默认拦截器,package层次结构之类的 package信息仍

    然需要进行配置。

     

     

    生命周期回调

    关于方法级的生命周期回调的注解一共有三种,每一种都是在

    Action处理过程中的特定时刻被调用的。生命周期回调与拦截器及

    Action代理不同,它们特定于被调用的 Action类存在的,并不是那

    种可以在多个 Action中使用的单个的类。

     

    注解  描述 提高效率技巧 | 61

     

    http://www.infoq.com/cn

    Before  被注解的方法会在 Action的逻辑执行前被调

    用。

    BeforeResult  被注解的方法的调用时机是 Action的逻辑执

    行之后,但执行结果还未被调用之前。

    After  被注解的方法的调用时机是 Action的结果被

    调用之后,但尚未被返回给用户之前

     

     

    验证 

    任意一个 XML配置的 validator都有一个相应的注解。每一个注

    解的属性都和 XML的配置很相似。还有些注解可以用来把一个类定

    义为使用基于注解的验证,或是配置自定义的 validator,还可以把一

    个属性或类的验证进行分组。

     

    注解  相应的 XML  描述

    RequiredFieldValidator  required  确保该属性不是

    null

    RequiredStringValidator  requiredstring  确保一个 String

    型的属性不是

    null,并且非空

    StringLengthFieldValidator  stringlength  检查 String的长度

    范围是否与所期望

    的一致

    IntRangeFieldValidator  int  检查 int类型的数字

    是否超出所期望的

    大小范围

    DoubleRangeFieldValidator  double  检查 double类型的

    数字是否超出所期

    望的大小范围

    DateRangeFieldValidator  date  检查 date类型的属

    性是否超出所期望

    的范围

    ExpressionValidator  expression  使用值栈来估算一

    ONGL表达式

    (必须要返回

    boolean值)

    FieldExpressionValidator  fieldexpression 使用 OGNL表达式

    来验证字段

     62 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    EmailValidator  email  保证该属性是一个

    有效的 email地址

    UrlValidator  url  保证该属性是一个

    有效的 URL

    ConversionErrorFieldValidator conversion  检查该属性是否有

    转换错误

    RegexFieldValidator  regex  检查该属性的值是

    否与某个正则表达

    式相匹配。

    VisitorFieldValidator

     

    visitor  把对字段的验证动

    作推迟到这个字段

    所属的类的特有的

    另一个验证文件中

    执行。

    StringRegexValidator  n/a  检查字符串是否与

    正则表达式匹配

    CustomValidator  n/a  表示使用了一个自

    定义的 validator

    ValidationParameter  n/a  作为

    CustomValidator

    解的一个参数

    Validation  n/a  表示该类使用了基

    于注解的验证——

    这个注解可以与接

    口或类一起使用

    Validations  n/a  用来对一个属性或

    类组合使用多种验

     提高效率技巧 | 63

     

    http://www.infoq.com/cn

    转换和容器

    与“验证”类注解类似,“转换和容器”类注解为每一个使用

    了“*- conversion.properties”配置文件的对象都提供了相应的注解。

     

    注解  描述

    KeyProperty  用于指定作为 key 的属性

    Key  用作 map key的类型

    Element  用于容器 list map中的值或元素的类型

    CreateIfNull  如果 list map中不存在元素的话,是否创建

    一个新的元素

    Conversion  表示该类使用了基于注解的转换——这个注

    解可以与接口或类一起使用

    TypeConversion  定义要使用的转换类。如果用在容器上,那

    PROPERTY MAP KEY

    KEY_PROPERTY 或者 ELEMENT组合的规

    则会被用来指定哪些元素会被进行转换

     

     

    视图技术选项

     

    我们在讨论框架的核心元素时提到过,标签库既可以在 JSP

    访问,又可以在 Velocity Freemarker模板中使用,还能被扩展用

    于其他视图技术中。由于 Velocity Freemarker是这个框架中的一

    等公民,框架对它们提供了完整的标签库支持,所以开发人员可以

    选择最适用于当前项目的视图技术来使用。

     

    让我们通过文本框标签在每一种视图技术中的渲染方式,来看

    看这几种技术的相似点和区别。文本框标签是很常见的表单标签—

    —用来生成 HTML表单元素,让用户可以输入文本。这个元素有很

    多属性,不过这里我们只用其中两个——name属性和 label属性,前

    者表示元素名,用来在 HTML action属性中引用这个元素;后者

    用来在输入框之前显示一段文字标记。

     

    JSP中,标签库必须在使用之前被声明。声明只需要在每一

    JSP页面的起始处出现一次即可,然后就可以用 prefix(在这里是

    s”)来引用标签库了。

     

    <%@taglib prefix="s" uri="/struts-tags" %>

     

     64 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    <s:textfield label="Name" name="person.name" />

     

    Freemarker Velocity 里没有 JSP那么复杂的结构,也没有

    标签库的概念。任何被添加到模板上下文中的对象都可以在页面模

    板中访问。JSP标签库也是这样的对象。HTTP Servlet请求与响应,

    值栈和刚刚执行完的 Action也都被放在模板上下文中使用。

     

    JSP相似的是,Freemarker模板中也用了“s”前缀来引用

    JSP标签库。模板中的其他属性和 HTML很相似——用“<”开头,

    用“/>”结尾,属性名则是一样的。其中一个区别是,在第一个尖

    括号后面紧跟着一个“@”符号。

     

    <@s.textfield label="First Name" name="person.name"/>

     

    http://freemarker.sourceforge.net/上面有关于Freemarker的更多

    信息。

     

    Velocity HTML语法的相似性就比 Freemarker少得多了。它

    在每一个 JSP标签名前面都有一个“#s”前缀,名-值对都在圆括号

    内部被双引号引了起来。

     

    #stextfield ("label=Name" "name=person.name")

     

    http://velocity.apache.org/上有关于Velocity的更多信息。

     

    JSPVelocity Freemarker 一样,XML 也可以作为 Action 的结

    果直接呈现给用户,或者是使用 XSLT 结果类型,通过 XSL 转换后

    显示给用户。现在 Struts2 中正在进行一件很有意思的工作——接受

    JSON 格式的表单或者请求,并且用 JSON 格式输出结果——这使得

    Struts2 具有更高的灵活性,可以容纳所有的视图技术,甚至还可以

    在同一个项目同一个 Action中混合使用多种视图技术。

     

     

    了解框架提供的标签库及其特性

     

    Struts2中提供了各种可以在 JSPVelocity Freemarker视图中

    使用的标签库,用来提供从 UI Action的集成和对这种信息的操

    作。

     提高效率技巧 | 65

     

    http://www.infoq.com/cn

    在默认情况下,表单标签接受 OGNL表达式作为属性值,还有

    很多标签的属性也可以这样。如果你要使用的属性没有这个默认

    值——例如“label”属性——那么基本上你都可以用“%{”和“}

    把属性值包起来,整体作为一个表达式来执行。

     

    能够执行 OGNL表达式是一个很强大的特性,尤其是标签库还

    可以访问值栈,这样一来你就可以做到以下几点:

     

    ƒ  访问 applicationsessionrequest作用域的 HTTP

    名的对象,以及 request的属性和参数

    ƒ  访问刚刚执行过的 Action

    ƒ  访问刚刚执行过的 Action中的模型

    ƒ  访问临时对象——例如表示迭代中当前遍历的对

    象,或是被放到值栈中的对象

    ƒ  存储临时变量

    ƒ  获取最后一个执行的 Action中的验证问题

    ƒ  获取提供的 key所对应的国际化文本

    ƒ  调用静态对象中的方法,获取静态属性的值

     

    http://struts.apache.org/2.x/docs/tag-reference.html上有标签库的

    最新信息。为了帮助你有一些初步的认识,在下面列出了四类标

    签,并辅以说明。每一个标签的属性和更详尽的信息都可以从上面

    的链接中得到。 

     

    下面的标签中,带有星号(*)标记的标签要么是基于 Ajax 的标

    签,要么是在 Ajax模式与非 Ajax模式下都能工作的标签。

     

     

    Control 标签

     

    名称  描述

    if, elseif & else  这三个标签提供了页面的流程控制逻辑。“if

    和“elseif”都有一个“test”属性,它对应的值

    必须是一个 OGNL表达式,执行后返回一个

    boolean

    append  用来将多个迭代器附加到一个迭代器上。每一

    个迭代器都通过内置的“param”标签来标识。

    每一个迭代器中的所有元素都会按照被指定的

    顺序添加到最终的迭代器中 

     66 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    generator  在给定的字符串列表的基础上生成一个迭代

    器。我们既可以指定要解析的列表,又可以指

    定自定义的转换器

    iterator  对容器进行遍历。我们可以访问迭代状态对象

    (提供当前循环的位置信息),也可以指定当

    前循环的对象 id

    merge  用来将多个迭代器合并到一个迭代器上。每一

    个迭代器都通过内置的“param”标签来标识。

    最后的迭代器会依次包括所有迭代器的第一个

    元素,所有迭代器的第二个元素,等等。

    sort  使用指定的 comparator来对容器排序。排序后

    的容器可以在这个标签中遍历

    subset  选择当前容器的子集。可以提供一个选择范

    围,也可以提供自定义的选择对象。选择结果

    可以在这个标签中遍历

     

    Data标签

     

    名称  描述

    a*  渲染一个 HTML链接

    action  在页面中调用 Action,经过配置后,还可以渲

    染结果。这个标签可以作为动态的 include使

    用,在不渲染结果的情况下调用通用的 Action

    来获取数据,或者是在页面模板之前调用

    Action

    bean  根据特定的类来实例化一个 Bean,然后把它放

    到值栈中。使用它内置的“param”标签可以给

    新创建的对象设置属性值

    date  格式化一个 Date对象

    debug  生成值栈信息以供调试使用。

    i18n  把额外的 Resource Bundles 放到值栈中, 用来

    将文本国际化。

    include  进行方法调用,并把结果动态的引入到当前页

    面中。用“param”标签来指定请求参数。

    param  是一个用来指定名/值的的通用标签,其中的值

    可以是静态文本,或者是从值栈中获取的表达

    式。这个标签不能单独使用,只能作为其他标

    签的子标签。 提高效率技巧 | 67

     

    http://www.infoq.com/cn

    push  把一个值放到值栈中。

    set  把一个值栈中的值设置为特定 HTTP 作用域的

    属性 (application, session, request, page action).

    text  根据特定的 key,从 Resource Bundle中取一个

    文本值。可以用“param”标签来指定结果文本

    中的变量值。

    url  生成一个有效的 URL(包括 Servlet上下文或

    Portlet信息),并把它赋值给值栈中的一个

    id

    property  从值栈中获取一个属性的值。它(属性名)可

    以是一个复杂路径。如果没有对其赋值的话,

    那么就会被指定一个默认的值。

     

    Form 标签

      

    名称  描述

    checkbox  生成一个简单的 HTML checkbox元素

    checkboxlist  把输入数据作为一个对象列表,由此生成一组

    HTML checkbox元素。

    combobox  HTML input HTML select组合生成 combo-

    box的功能。用户既可以从下拉列表中选择,又

    可以输入一个新值。

    datetimepicker  生成 HTML 下拉框,让用户可以选择日期/

    间。

    doubleselect  生成两个关联的 HTML select元素。第一个列表

    的选择可以影响第二个列表中显示的内容

    head  生成 HTML header信息,尤其是可以用来引入

    主题中使用的 CSS JavaScript

    file  生成 HTML file input 元素。

    form*  生成 HTML 表单元素。

    hidden  生成 HTML hidden 元素。

    label  生成 HTML label,在 UI中可以保持一致的格

    式。

    optiontransferselect 生成两个关联的 HTML select元素,并在它们中

    间生成 HTML按钮。这些按钮可以用来在列表

    中间转移数据。

    optgroup  在一个 HTML select元素中生成一个可选项组。

    password  生成 HTML password 元素

     68 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    reset  生成一个 reset按钮,可以是 HTML button

    型,也可以是 HTML input类型。

    select  生成 HTML select元素

    submit*  生成 HTML submit按钮或链接——可以是一个

    HTML input,一个 image或一个按钮。

     

    在使用默认的ActionMapper的时候,它有四种特

    殊的“name”属性前缀,submit标签可以用它们

    来修改一般情况下表单所用的URLaction或者

    method。它们是:“method:”,“action:”,

    redirect:”和“redirect-action:”。要调用的

    URL,方法或者action要放在冒号的后面。更多

    信息请参见

    http://struts.apache.org/2.x/docs/actionmapper.html.

    textarea  生成 HTML text area元素

    textfield  生成 HTML text field 元素

    token  向用户提醒来防止用户进行二次提交。它是和

    token” 或者“token-session”拦截器配合工

    作的。

    updownselect  生成 HTML select元素和用来在列表中上下移动

    数据的 HTML按钮。当表单提交时,列表中的

    元素顺序会和排列顺序一样。

     

     

    Non-Form UI 标签

     

    名称  描述

    checkbox  生成一个简单的 HTML checkbox元素

    checkboxlist  把输入数据作为一个对象列表,由此生成一组

    HTML checkbox元素。

    combobox  HTML input HTML select组合生成 combo-

    box的功能。用户既可以从下拉列表中选择,又

    可以输入一个新值。

    datetimepicker  生成 HTML 下拉框,让用户可以选择日期/

    间。

    doubleselect  生成两个关联的 HTML select元素。第一个列表

    的选择可以影响第二个列表中显示的内容

    head  生成 HTML header信息,尤其是可以用来引入提高效率技巧 | 69

     

    http://www.infoq.com/cn

    主题中使用的 CSS JavaScript

    file  生成 HTML file input 元素。

    form*  生成 HTML 表单元素。

    hidden  生成 HTML hidden 元素。

    label  生成 HTML label,在 UI中可以保持一致的格

    式。

    optiontransferselect 生成两个关联的 HTML select元素,并在它们中

    间生成 HTML按钮。这些按钮可以用来在列表

    中间转移数据。

    optgroup  在一个 HTML select元素中生成一个可选项组。

    password  生成 HTML password 元素

    reset  生成一个 reset按钮,可以是 HTML button

    型,也可以是 HTML input类型。

    select  生成 HTML select元素

    submit*  生成 HTML submit按钮或链接——可以是一个

    HTML input,一个 image或一个按钮。

     

    在使用默认的ActionMapper的时候,它有四种特

    殊的“name”属性前缀,submit标签可以用它们

    来修改一般情况下表单所用的URLaction或者

    method。它们是:“method:”,“action:”,

    redirect:”和“redirect-action:”。要调用的

    URL,方法或者action要放在冒号的后面。更多

    信息请参见

    http://struts.apache.org/2.x/docs/actionmapper.html. 

    textarea  生成 HTML text area元素

    textfield  生成 HTML text field 元素

    token  向用户提醒来防止用户进行二次提交。它是和

    token” 或者“token-session”拦截器配合工

    作的。

    updownselect  生成 HTML select元素和用来在列表中上下移动

    数据的 HTML按钮。当表单提交时,列表中的

    元素顺序会和排列顺序一样。

     

     

     

     

     

     70 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    自定义 UI 主题

     

    作为标签库架构的一部分,每一个标签都有对应的类来管理模

    型和逻辑,以及可以控制显示在 HTML中元素的一个或多个

    Freemarker模板。模板可以以实用为主,只提供必需的 HTML

    素;或者在表单和非表单的 UI标签中,用它提供丰富的排版功能,

    包括将消息和错误信息提供给表单元素。

     

    对于最基本的用户界面来说,默认的布局就足够了。但是一般

    情况下,用户需求都要复杂的多。在新的布局下你有两个选择——

    把要修改的 HTML应用到程序的所有页面中,或者是修改默认的主

    题模板,来为要做出的变化提供新的主题。就像把视觉格式信息抽

    象到 CSS文件中一样,创建新的主题可以给后续的维护带来极大的

    便利(尤其是当应用程序中的页面数量增加的时候)。

     

    主题的创建和修改都很简单。在应用程序的根目录下创建一个

    名为“template”的文件夹,然后就可以进行操作了。

     

    如果要创建一个新主题的话,那么就在“template”目录下创建

    一个与新主题同名的目录——我们在这里给它命名为“modified”。

    然后你可以一步步创建模板,或者从 Struts2发行版中拷贝一个模板

    出来,在它的基础上进行修改。如果你想在标签中使用“modified

    主题而不是默认的“xhtml”主题,那么就需要在每一个标签中修改

    theme属性,指向“modified”主题。

     

    <s:textfield label="Name" name="person.name"

    theme=”modified” />

     

    如果你只是想修改某一个模板,那么就只需要对它进行重写。

    在上面创建的“template”目录下,创建一个名为“xhtml”(这是

    Struts2的默认主题)的目录。然后创建新的模板或是修改 Struts2

    的已有模板,但要注意的是,这个模板需要和 Struts2中的模板名保

    持一致。只有模板名一致的情况下,它们才是同一个主题。而 web

    应用中的“template”目录会在 Struts2 JAR包之前被检索——然

    后被修改的模板就会被直接应用,无需我们作做他配置。

     

    如果你想要完全替换掉一个主题,就去修改“struts.properties

    文件。在这个例子中,我们需要把“struts.ui.theme”属性指向新的提高效率技巧 | 71

     

    http://www.infoq.com/cn

    modified”主题。在这个文件中,还可以修改主题模板所存放的目

    录。

     

    struts.ui.theme=modified

    struts.ui.templateDir=template

     

     

    为通用的输出创建全局结果

     

    结果的作用域可以是针对特定 Action的,也可以是全局的。我

    们可以对通用的 Action结果——例如“error”和“logon”——进行

    重构,这样每一个 Action就可以只处理和自身特有逻辑相关的结果

    了。

     

    在全局结果中使用的<result />标签和普通的 Action结果

    标签的形式基本相同——有一个唯一的“name”属性,还有一个

    type”属性,描述了不同的渲染选项。它们的不同点在于,全局的

    结果标签是放在<package />根标签下的<global-results />标签里

    面的。

     

    <package … >

     

     <global-results>

       <result name=”logon”>/logon.jsp</result>

       <result name=”error”>/error.jsp</result>

       …

     <global-results>

     

     …

     

    </package>

     

    全局结果在“struts.xml”定义了以后,它就可以被应用程序中的所

    Action使用了。

     

     

    声明式异常处理

     

    在开发 web应用时,需要处理不同种类的异常。有些异常是特

    定于服务或者正在调用的业务对象的——这些无法进行声明式处

    理,只能通过编程来处理它们。

     

     72 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    但是还有另外一些异常:

     

    ƒ  无法处理,需要把用户重定向到一个错误页面,直

    到问题解决为止。这些常常是系统级别或者资源级别的问

    题,和 Web应用的逻辑无关。因网络问题而导致的数据库连

    接失败就是这样一个例子。

    ƒ  与逻辑无关,但是需要对用户重定向到执行额外操

    作的页面。比如说,如果用户在未登录的情况下来访问一个

    web页面,就可能因为安全问题而抛出异常。当用户登录以

    后,他们就可以继续操作了。

    ƒ  与逻辑相关,可以通过修改用户的工作流程解决。

    这种问题常常是与资源相关的,包括唯一约束冲突的异常,

    对数据并发修改或是资源锁问题等等。

     

    这些异常都可以进行声明式管理,无需修改 Action

     

    当一个异常可能会被应用中的所有 Action抛出时,它应该被声

    明为全局异常。全局异常在“struts.xml”文件中进行声明,它位于

    <package >标签下的<global-exception-mappings />标签里面。

     

    <global-exception-mappings>

    <exception-mapping result="sqlException" 

    exception="java.sql.JDBCConnectionException"/>

    <exception-mapping result="unknownException" 

    exception="java.lang.Exception"/>

    </global-exception-mappings>

     

    <global-exception-mappings />中,<exception-mapping />

    标签的数量是没有限制的。标签中的每一个映射都包含两个属性—

    —“exception”属性定义了异常类的全限定名,“result”标签定义

    了重定向的结果。

     

    每一个异常映射都会按照被配置的顺序来进行检索。当检索到

    一个匹配的异常(或它的子类)时,处理过程就会终止,页面请求

    就会被转发给先前映射的结果。否则就会按照配置顺序向下继续检

    索能够匹配的异常。

     

    如果一个异常的作用域只是单个的 action,那么就在<action

    />标签内进行同样的<exception-mapping />标签配置。

     

    <action name="my" class="com.fdar.infoq.MyAction" >

    <result>view.jsp</result> 提高效率技巧 | 73

     

    http://www.infoq.com/cn

    <interceptor-ref name="basiActioncStack"/>

    <exception-mapping result="exists"

       exception="ConstraintViolationException"

    />

    </action>

     

    这里的属性和全局异常的属性相同。如果在 action级别上没有

    找到匹配的异常映射,那么就会从全局异常的定义中检索相应的异

    常。

     

    同时,你还应该保证拦截器栈中要有“exception”拦截器,并

    对需要进行声明式异常处理的 Action进行配置。在默认情况下,

    Struts2提供的所有拦截器栈都包含有“exception”拦截器。

     

    在对抛出异常时的结果进行修改的同时,“exception”拦截器

    也在值栈中添加了两个元素,用以提供异常信息。

     

    名称  描述

    exception  所抛出的异常对象

    exceptionStack  stack trace的字符串值

     

    这些值可以用来向用户显示异常的堆栈信息,或是显示一些友好的

    用户提示信息,或者是重新组织页面布局,显示额外的数据项,并

    可以再次提交表单。

     

     

    国际化

     

    Struts2通过Resource Bundle,拦截器和标签库提供了可扩展的

    国际化支持。它的核心功能是通过 Resource Bundle提供的,我们的

    讲述也将从这里开始。

     

    Resource Bundles

    Struts2使用Resource Bundle来向用户提供多语言和多区域的选

    项。我们不需要在一个庞大的文件中提供整个应用中的所有文本

    (虽然这个选项是可以通过只使用一个属性文件来支持的)。相

    反,应用中的属性文件可以被分拆成易于管理的大小。

     

    属性文件是根据 Action类(包括基类和接口)和 package作用

    域,或是任意的文件名来命名的。如果需要一个 key所对应的值,

     74 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    那么系统就会按照下面的顺序来检索属性文件,直到找到该 key

    止:

     

    1.  Action类的属性文件——例如 MyAction.properties

    2.  Action层次结构中的每一个基类的属性文件,直到

    Object.properties文件为止。

    3. 每一个接口和子接口的属性文件。 

    4. 如果一个 Action是模型驱动的,那么就有对应于模型对象

    (以及模型对象的每一个基类,接口和子接口)的属性文件

    5. 从每一个 Action package到根 package,都有一个命名

    为“package.properties”的属性文件。

    6. 在“struts.properties”文件中,

    struts.custom.i18n.resources”配置项中定义的属性文件。

     

    这样提供了高度灵活性。

     

    拦截器与 Locale选择

    在默认情况下,Struts2会在 HttpServletRequest对象的 Session

    设置用户 Locale。它是直接从 Web浏览器中得来的,基于 Accept-

    Language HTTP header

     

    Web应用需要与 Web浏览器 Locale无关的多语言内容时,

    我们可以使用“i18n”拦截器。这个拦截器对名为“request_locale

    的请求参数进行检查,并且把它保存到用户 Session中。在它被请求

    参数再次修改之前,这个特定的 Locale就会一直作为用户 Session

    其他部分的 Locale 

     

    标签库

    标签库是最后一处需要说明的地方。 所有的标签都是通过当前

    起作用的 Locale来支持国际化的。比如说,“date”标签使用用户

    Locale来决定正确的日期格式;“actionerror”、 “fielderror”和

    actionmessage”标签都是通过声明式验证配置中所提供的 key来获

    取要渲染的文本。还有一些其他的标签很值得关注。

     

    有两种方式可以编程式获取一个页面所需的国际化文本,它们

    都需要 Action继承 ActionSupport这个类,这样才可以使用必需的国

    际化方法。第一种是使用“text”标签,这个标签通过“name”属性

    所提供的 key来检索文本:

     提高效率技巧 | 75

     

    http://www.infoq.com/cn

    <s:text name="label.greeting"/>

     

    <s:text name="label.greeting2">

       <s:param >Mr Smith</s:param>

    </s:text>

     

    额外的信息可以在 Resource Bundle通过花括号来提供。上面的

    例子对应的属性文件内容的形式是这样的:

     

    label.greeting=Hello there!

    label.greeting2=Hello there {0}!

     

    第二种方式是通过OGNL方法和“property”标签来获取文本

    值。这种技术和上面介绍的那中的不同在于开发者对风格的选择。 10

    。在方法调用时,OGNL表达式可以被用在任何进行表达式运算的

    标签中。在第二种方式下,上面的页面就变成了:

     

    <s:property value="getText(‘label.greeting’)"/>

     

    <s:property value="getText(‘label.greeting2’)">

       <s:param >Mr Smith</s:param>

    </s:text>

     

    由于“label”属性在默认情况下不是 OGNL表达式,所以我们

    需要使用“%{”和“}”符号来强制 Struts2把它作为 OGNL表达式

    解析。

     

    <s:textfield label="%{getText(‘label.greeting’)}"/>

     

    在需要大段文字的时候,OGNL表达式还可以和“include”标

    签一起使用,用来指定语言目录。在这种情况下,我们要确保每一

    Action都继承了一个基类,同时在基类中向外暴露一个决定

    Locale目录的方法。在这里我们有一个 getLocaleDirectory()方法。对

    应的页面为:

     

    <s:include

    value="/include/%{localeDirectory}/copyright.html" />

     

    i18n”标签提供了一种从被渲染页面的值栈中获取额外的

    Resouce Bundle的方式,它的“name”属性描述了 Resouce Bundle

                                                    

    10

     这种说法没错,但是如果使用property标签,开发人员就可以访问值

    栈,并使用在开发时还不知道的key名来获取国际化文本。

     76 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    的名称。“i18n”标签中的任何标签都可以访问新的 Resouce Bundle

    文本。

     

    <s:i18n name="myCustomBundle">

      The value for abc in myCustomBundle 

      is <s:text name="abc"/>

    </s:i18n> 

    77

    6

    其他技术集成

     

    在先前的章节中,我们已经介绍了 Struts2中用于与其他技术集

    成的一些技术。下面我们再来复习一下,它们包括:

     

    ƒ  拦截器——可以更改用户的工作流程,修改结果,

    把对象注入 Action

    ƒ  结果类型 ——允许进行事后处理,额外的基于结果

    的处理,或是对 Action返回的信息进行渲染.

    ƒ  插件包 ——新的拦截器,结果类型,结果和 Action

    可以被打包到插件里面,以便在多个项目中重用。

    ƒ  插件扩展点——Struts2中, 可以用新的核心框架类

    的实现来进行替换,从而改变框架的行为。

     

    本章的目的不是对每一个集成点的细节进行面面俱到的描述,

    而是进行大致的概述,让读者了解到在 Struts2中有哪些类型的集

    成,如何进行集成,同时还提供了一些基本的配置信息,并说明怎

    样获取更多的配置信息。本章也并不旨在说明如何使用那些用于集

    成的类库,但是假定读者已经有了相关的基础知识。

     

    Struts2中所有集成(Apache和第三方库)的最新信息都可以在

    Struts2 wiki上找到,地址为

    http://cwiki.apache.org/S2PLUGINS/home.html。新的项目正在不断的

    添加进来。如果你没有从中找到想要的信息,那么可以检查一下几

    个月前的更新,也许它已经被添加进来了。如果你想要在自己的

    Web应用中添加新的集成,你可以考虑一下把它实现为一个插件,

    然后和其他人共享。

     

     

    页面修饰和布局

     

    InfoQ中文站 Architecture社区

    关注设计和技术趋势及架构师领域

    http://www.infoq.com/cn/architecture78 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

                                                   

    开发Web应用通常都意味着要有一个标准的页面布局,并被应

    用到整个应用中,并且不同的模块,页面和向导可能还要使用额外

    的一些布局。根据个人偏爱不同——自己指定布局或是让URL指定

    布局——你很可能会选择Struts Tiles

    11

     或是SiteMesh12

    Struts2中提

    供了对这两种布局技术的集成。

     

    SiteMesh

    SiteMesh可以通过两种方式来安装。一种是把插件13

    放到应用程

    序下的“/WEB-INF/lib”目录中,一种是在Maven2 pom.xml”构

    建文件中添加依赖关系:

     

    <dependency>

    <groupId>org.apache.struts</groupId>

    <artifactId>struts2-sitemesh-plugin</artifactId>

    <version>2.0.6</version>

    </dependency>

     

    在安装完以后,需要配置 Servlet过滤器。这个过滤器允许

    SiteMesh 修饰器访问值栈,并且确保在修饰器完成工作之后(而不

    是完成之前),会清空 ActionContext

     

    <filter>

     <filter-name>struts-cleanup</filter-name>

     <filter-class>

     

     org.apache.struts2.dispatcher.ActionContextCleanU

    p

     </filter-class>

    </filter>

     

    如果你使用了 Freemarker或者 Velocity来渲染页面,那么就需

    要添加下面两种过滤器之一:

     

    <filter>

     <filter-name>sitemesh</filter-name>

     <filter-class>

     

     org.apache.struts2.sitemesh.FreeMarkerPageFilter

     </filter-class>

    </filter>

    <filter>

     

    11

     http://tiles.apache.org

    12

     http://www.opensymphony.com/sitemesh

    13

     http://cwiki.apache.org/S2PLUGINS/sitemesh-plugin.html 其他技术集成 | 79

     

    http://www.infoq.com/cn

     <filter-name>sitemesh</filter-name>

     <filter-class>

     

     org.apache.struts2.sitemesh.VelocityPageFilter

     </filter-class>

    </filter>

     

    过滤器映射的顺序也非常重要。“struts-cleanup”和

    sitemesh”(如果已经使用)过滤器都要在“struts

    FilterDispatcher)过滤器之前进行配置:

     

    <filter-mapping>

     <filter-name>struts-cleanup</filter-name>

     <url-pattern>/*</url-pattern>

    </filter-mapping>

    <filter-mapping>

     <filter-name>sitemesh</filter-name>

     <url-pattern>/*</url-pattern>

    </filter-mapping>

    <filter-mapping>

     <filter-name>struts</filter-name>

     <url-pattern>/*</url-pattern>

    </filter-mapping>

     

    完成这些以后,就可以对特定的修饰器文件进行开发了,然后

    对其进行配置,以匹配特定的 URL模式,或者是“decorators.xml

    中的元数据。

     

    Tiles

    Apache TilesSiteMesh一样,都可以把插件14

    放到Web应用的

    /WEB-INF/lib”目录下,或是在Mave2 pom.xml”文件中加入依

    赖:

     

    <dependency>

    <groupId>org.apache.struts</groupId>

    <artifactId>struts2-tiles-plugin</artifactId>

    <version>2.0.6</version>

    </dependency>

     

    我们需要配置 Servlet Listener才能载入 tiles配置信息:

     

    <listener>

     <listener-class>

                                                    

    14

     http://cwiki.apache.org/S2PLUGINS/tiles-plugin.html

     80 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

     

     org.apache.struts2.tiles.StrutsTilesListener

     </listener-class>

    </listener>

     

    Listener会从“WEB-INF”目录下载入“tiles.xml”配置文件,

    该文件中定义了应用中的每一个 tile。与 SiteMesh不一样的是,Tiles

    是作为一种新的结果类型实现的。要使用 Tiles布局的 Action结果都

    需要把“type”属性设置为“tiles”(或者是把 Tiles结果设置为默

    认值),并指定要使用的 tile名称。Tile名称需要在“tiles.xml”文

    件中定义。

     

    <action name="my" class="com.fdar.infoq.MyAction" >

    <result type="tiles">myaction.layout</result>

    </action>

     

    Struts2中使用了 Tiles的第二版。这个版本还没有稳定的发布

    版,还会有更多的变化。所以 Struts2中对 Tiles的支持被标记为“试

    验性”。

     

     

    业务服务/依赖注入

     

    Spring Framework插件是Struts2推荐使用的依赖注入(DI)或

    控制反转(IoC)容器,它可以为 Action提供完全配置好的业务服务

    实例。

     

    这里还有其他几种选择,每一种都具有不同层次上的稳定性:

    ƒ  Plexus

    15

     插件是刚刚加入代码库的,目前还是被标记

    为“试验性”。当我们在“struts.xml”配置文件的每一项中

    使用Plexus id而不是类名时,Plexus可以创建类的实例,并向

    其中注入所有它所知的依赖。关于该插件的详细信息可以在

    这里找到:http://cwiki.apache.org/S2PLUGINS/plexus-

    plugin.html

    ƒ  PicoContainer

    16

     是另外一个Ioc容器,不过虽然

    WebWork对它提供了支持,但现在还没有对应的Struts2

    件。

                                                    

    15

     http://plexus.codehaus.org

    16

     http://picocontainer.codehaus.org 其他技术集成 | 81

     

    http://www.infoq.com/cn

    ƒ  EJB3,它虽然不是Ioc容器,但仍可以用来为Action

    提供业务服务。现在Strut2中现在还没有提供插件来提供对

    EJB3的支持,虽然实现起来并没有什么难度。在Struts2

    使用EJB3有三种方式——实现一个自定义的ObjectFactory

    Action获取EJB引用,然后在“struts.properties”配置文件

    的“struts.properties”属性中加以定义,把这个新的工厂安

    装到应用程序中;创建一个新的拦截器,用来检查每一个

    Action,并把所需的EJB应用注入其中;或者还可以使用

    Spring插件来访问JPA或是EJB

    http://cwiki.apache.org/S2WIKI/struts-2-spring-jpa-ajax.html

    个链接提供了相关的指南。

     

    因为 Spring 框架是推荐使用的库,所以下面我们将重点介绍它的使

    用。

     

    Spring Framework 

    安装Spring支持的时候,需要下载Spring插件17

    ,并把它拷贝到

    Web应用中的“/WEB-INF/lib”目录下,或者是在Maven2

    pom.xml” 构建文件中添加对Spring插件的依赖:

     

    <dependency>

    <groupId>org.apache.struts</groupId>

    <artifactId>struts2-spring-plugin</artifactId>

    <version>2.0.6</version>

    </dependency>

     

    在“web.xml”配置文件中,你需要添加两部分代码。第一部分

    用于注册一个 listener,来激活应用程序对象与 Spring的集成::

     

    <listener>

    <listener-class>

    org.springframework.web.context.ContextLoaderLi

    stener

    </listener-class>

    </listener>

     

    然后是要指定 Spring配置文件所存放的位置。在下面这种情况

    下,任何以“applicationContext”开头的 XML文件都会被装载:

     

    <context-param>

     <param-name>contextConfigLocation</param-name>

                                                    

    17

     http://cwiki.apache.org/S2PLUGINS/spring-plugin.html

     82 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

     <param-

    value>classpath*:applicationContext*.xml</param-value>

    </context-param>

     

    现在你就可以在 Spring的支持下进行开发了。所有需要创建的

    对象都会由 Spring对象工厂代理创建。如果它知道如何创建对象实

    例的话,那它就会进行创建,如果它不知道的话,就会把创建工作

    交回给框架完成。

     

    无论是由 Spring对象工厂还是由 Struts2创建的对象,框架都会

    判断是否有任何所依赖的对象被 Spring管理着。作为默认的 DI

    器,Spring会获取所有依赖对象的实例,并在所需的时候传给目标

    对象。这对 Action而言尤为重要,因为虽然 Action本身绝大多数情

    况下都由 Struts2来创建,但是它需要由 Spring来注入所需的业务服

    务。

     

    使用 Spring时还有一点要注意的是如何编织依赖关系。对下面

    这个类而言,Spring是应该注入 id值为“service”的 bean呢,还是

    类型为“MyService”的 bean呢?

     

    public class MyAction {

     

     private MyService myService;

     

     public void setService( MyService service ) {

       myService = service;

    }

     

     public String execute() {

       …

     }

    }

     

    答案是值为“service”的 id,但这也是可以配置的。在

    struts.properties”修改“struts.objectFactory.spring.autoWire”属性

    就可以配置编织依赖关系的方式。这个属性的默认值是“name”,

    它可以是以下四个值之一:

     

      描述

    name  Springbean的定义中,使用名/id 值来自动织

    bean

    type  Springbean的定义中,使用类名来自动织入

    bean。 其他技术集成 | 83

     

    http://www.infoq.com/cn

    auto  Spring 自己决定织入bean的最佳方法。

    constructor  Spring 会通过bean的构造器自动织入bean

     

    还有一种方法可以完全由 Spring来管理 Action,不过配置起来

    就复杂多了。有兴趣的读者可以参考一下 Spring插件的文档。

     

     

    数据库

     

    Struts2中集成数据库并没有什么特别的,不过这里仍然有多

    种不同的方法来访问数据库:

    ♦  通过标签库 ——既然你使用的是基于 Action的框架,所以

    这并不是最佳的选择。不过它依然可行;数据可以直接从

    JSP中通过标签库来访问(JSTL或者自定义的标签),然

    后将信息格式化。

    ♦  通过依赖注入使用自定义的 DAO——如果你使用了依赖注

    入,那么就可以将 Action中所需的自定义的 DAO注入到

    Action里面;只要 Action中存在有对 DAO的引用,那么

    它就可以直接调用 DAO的方法,就如同它自己创建了

    DAO的实例一样。

    ♦  通过依赖注入使用 DAO / ORM ——如果你在使用高级的

    DAO或者 ORM库(例如 iBatis Hibernate),那么你就

    该考虑一下使用一个像 Spring一样功能齐备的依赖注入框

    架;Spring提供了配置和初始化大多数 DAO ORM库的

    所有功能,几乎不需要 Action做什么事情;当 Action需要

    执行业务逻辑时,所需的数据访问对象的实例就都已经就

    绪了。

    ♦  通过业务服务来间接存取——不是直接使用数据访问对

    象,而是通过业务服务来间接调用;和上面其他方式一

    样,业务服务也是通过依赖注入框架注入 Action的。

     

    附带说一下,如果你打算在项目中使用 Hibernate 作为 ORM 技术的

    话,那么需要研究一下“OpenSessionInView”过滤器或拦截器。它

    可以将 Hibernate Session一直保持连接的状态,直到 JSP渲染完毕以

    后才关闭。这样 Hibernate 就可以成功的完成延迟加载。否则的话,

    Action或者业务服务或者 DAO就必须在 JSP显示数据之前,预先把

    所有需要的数据全都取出来。

     

     

     84 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    安全

     

    和数据库集成一样,在 Struts2中提供安全集成也没有什么特别

    的地方。要在微架构或是应用程序的哪一层来实现授权和验证,是

    由系统架构师或者开发人员决定的。

     

    进行验证的地方:

    ♦  应用程序之外——单点登录(SSO)服务器或者应用服务

    器提供的验证模块就是很好的例子

    ♦  应用程序之内——通过HTML表单或者另外一种

    challenge-response机制

     

    进行授权的地方:

    ♦  URI级别——每一个被请求的 URI都需要和发起请求的用

    户帐户进行匹配来验证用户是否有权限访问

    ♦  子页面级别 ——页面中的某些部分可能会需要具有特定的

    访问级别才能查看,修改或者执行操作

    ♦  Action级别 ——每一个 Action在执行业务操作之前,都需

    要判断访问权限

    ♦  业务服务级别——业务服务中的每一个方法在执行逻辑之

    前都需要判断访问权限 

    ♦  领域对象或者数据库级别——对一个用于获取数据或者领

    域对象的通用方法调用而言,调用者的权限不同,返回结

    果也会不同

     

    Struts2 Web应用的视图中,需要对用户是否有权访问 URL

    进行验证。在用户有权访问应用程序的前提下,还可以分为如下的

    几种情况。

     

    第一种是外部的解决方案,通过 HttpServletRequest来提供用户

    帐户信息。这之后用户名和用户的角色信息就可以在 Action中访问

    (通过实现 PrincipalAware接口和在拦截器栈中配置“servlet-

    config”),并通过 Action暴露给被渲染的页面。现有的 Struts2

    签都可以用来进行基于角色的访问限制。

     

    下一种情况是用户访问 HttpServletRequest的帐户信息尚未提供

    的时候。这时就需要写一个拦截器,用它来进行验证,获取所需要

    的角色信息,并组装信息。这个过程可以复杂如编写登录表单,登其他技术集成 | 85

     

    http://www.infoq.com/cn

    录和注销代码,验证逻辑等等;也可以简单如从暴露的 API中获取

    信息并传递给 Action

     

    最后一种情况是由第三方类库来做所有的事情,比如 Acegi

    Acegi提供了用于保护 Web应用安全的所有组件——Servlet过滤

    器,自定义的标签库,与 Spring相集成来保护业务对象和领域对

    象——这些都是在 Struts2 Web应用的外部来完成。

    当然,只有当Action处理过程中需要授权信息的时候,才需要这

    种集成。在这种情况下,需要有一个拦截器和一个action接口相配

    合,向Action提供Authz接口的实例。更多细节请参见

    http://struts.apache.org/2.x/docs/can-we-use-acegi-security-with-the-

    framework.html. 

     

    这里的话题与第三方安全类库的集成无关。在使用 Struts2时的

    一个与安全相关的概念就是任何 Action都可以访问值栈中的所有对

    象。但这种假设并不总是正确的。我们可以通过配置和利用

    com.opensymphony.xwork2.interceptor.ParameterFilt

    erInterceptor 拦截器(这个拦截器并没有像先前提到的那些拦

    截器一样有默认的配置)来使得特定的值栈对象对某个 Action可用

    或是不可用。更多配置信息请参见拦截器的 JavaDoc

     

     

    Ajax

     

    Struts2中对Ajax的支持启动的有些晚。目前很多工作仍在进行

    中,待到尘埃落定还有很长的路要走。

     

    从根本上说,任何一个 Action都可以充当数据服务器。向 URI

    发出的请求可以得到一个 HTML片段(直接在 DIV中显示),一个

    XML文档(通过 XSLT结果类型)或者是一个 JSON文档(将 JSP

    转化为 JSON而不是 HTML),之后就在 web浏览器端由 JavaScript

    进行处理。这是到目前为止,从实现的观点看最为稳妥的做法,而

    且也代表着高效和质量。

     

    按照同样的理念,下面是三个需要一直关注的项目。它们都是

    很新的项目,需要时间才能发展成熟。

    ♦  使用JSON插件项目来自动完成提供JSON结果的工作,这

    是个第三方的插件,可以在下面的网址上找到相关信息:

     86 | 深入浅出STRUTS 2

     

    InfoQ中文站:时刻关注企业软件开发领域的变化与创新  

    http://cwiki.apache.org/S2PLUGINS/2007/01/11/json-

    plugin.html

    ♦  作为后单数据源来提供Google Web Toolkit (GWT)

    Struts2之间的互操作性,这是GWT 插件项目 –

    http://cwiki.apache.org/S2PLUGINS/2007/01/10/gwt-

    plugin.html

    ♦  DWR已经增加通过使用自己的框架对远程WebWork2

    Action进行调用的支持;WebWork2Struts2的先去,所

    以所有的技术都是直接可用的;更多的信息可以在

    http://getahead.ltd.uk/dwr/server/webwork找到。

     

    Struts2也提供了实现 Ajax功能的标签库,它们是:

    ♦  a / link tag ——使用 Ajax向服务器发起远程调用

    ♦  form ——提供对表单域的基于 Ajax的验证(使用

    DWR),并可以通过 Ajax远程提交表单

    ♦  submit ——结合基于 Ajax的表单提交一起使用

    ♦  div ——通过Ajax远程调用来获取 DIV的内容

    ♦  tabbedpanel ——通过 Ajax远程调用来获取标签面板中每

    一个面板的内容

     

    这些标签都是使用 dojo库实现的,有些标签正在从 JavaScript

    dojo的结合向纯粹的 dojo实现进行迁移。在不久的将来,Ajax

    签将会被移植到插件中。这样就可以基于不同的 Ajax库来提供不同

    的实现,而现在已经有了这方面的需求了。

     

    基于以上种种,Ajax标签库现在还是被标记为试验阶段。

     

     

     

     

    87  

     

    关于作者

     

    Ian Roughley是一位技术演讲人、作家及独立咨询顾问,住在马

    萨诸塞州的波士顿。他具有十多年提供架构设计、开发、过程改进

    以及指导等方面服务的经验,客户范围小至创业公司,大到财富 500

    强前 10名的公司。他曾经在金融、保险、制药、零售、e-learning

    hospitality和供应链等多个行业中工作过。

     

    他专注于具有实效性且以结果为目标的方法,是开源及以敏捷

    开发为基础的过程和质量改进的支持者。Ian参与了 WebWork项目

    的开发,也是 Apache Struts PMC的成员之一,同时还是 No Fluff

    Just Stuff座谈会的演讲人。他同时还是 Sun认证 Java程序员和 J2EE

    企业架构师,以及 IBM认证解决方案架构师。 

    88

     

     

     

     

     

     

     

    参考资料

    i http://struts.apache.org/2.x

    ii. Don Brown, Struts Ti项目的领导,他在文章中详细介绍了Struts Ti

    的历史,详情请参见

    http://www.oreillynet.com/onjava/blog/2006/10/my_history_of_struts_2.h

    tml

    iii. 你可以访问 http://www.java-source.net/open-source/web-frameworks

    来获取一份各种web框架的列表。

    iv. 只需要更改 “struts.action.extension”这个属性的值。

    v. Martin Fowler写过一篇文章,对依赖注入进行了完整的描述:

    http://www.martinfowler.com/articles/injection.html

    vi. 单纯从技术角度来讲,应该有 4 种主题——第 4 种是“ajax”主

    题。Struts2 已经决定在下个版本中把 Ajax 的功能从核心框架里移

    走,放到插件里面。所以在本节中没有对其进行介绍。

    vii. Swingwork 的地址为  https://swingwork.dev.java.net/。这个项目已

    经停止开发了。

    viii. 请参见http://www.jmock.org以获得更多信息。

    ix. config browser 插件的文档地址为

    http://struts.apache.org/2.x/docs/config-browser-plugin.html

    x. 这种说法没错,但是如果使用 property 标签,开发人员就可以访

    问值栈,并使用在开发时还不知道的 key名来获取国际化文本。

    xi.   http://tiles.apache.org

    xii.  http://www.opensymphony.com/sitemesh

    xiii. http://cwiki.apache.org/S2PLUGINS/sitemesh-plugin.html

    xiv. http://cwiki.apache.org/S2PLUGINS/tiles-plugin.html

    xv.   http://plexus.codehaus.org

    xvi.  http://picocontainer.codehaus.org

    xvii. http://cwiki.apache.org/S2PLUGINS/spring-plugin.html

    展开全文
  • 新技术促使CRM经营策略重新整合

    千次阅读 2004-10-29 17:28:00
    新技术促使CRM经营策略重新整合 作者: techupdate.zdnet.comFriday, May 21 2004 11:20 AM根据META Trend的研究:2004-2005年期间,由于缺乏充分的认识而导致出现的技术过度投资,驱使组织主动地将其CRM计划与他们...

    新技术促使CRM经营策略重新整合

    作者: techupdate.zdnet.com
    Friday, May 21 2004 11:20 AM

    根据META Trend的研究:2004-2005年期间,由于缺乏充分的认识而导致出现的技术过度投资,驱使组织主动地将其CRM计划与他们的经营策略进行重新整合。同时,由于原先被继承的应用面临版本停产(End Of Life),激发组织对其下一代CRM体系进行升级;到2006年,CRM改革将成为主流企业的一项战略性发展趋势,并得到行业定制(industry-specific)产品、面向服务的体系结构、集成框架,以及相应的价值体系的支持。与此同时,Global 2000的CRM系统将有15%临近结束状态,并已成功地将用户产品周期管理渗透到他们的商业过程中。

     

    7年前,单块集成电路的两层客户/服务器应用(two-tier client/server)是当时的标准规范;目前,面向Web的、多层(n-tier)体系结构已经变得普通。随着大多数供应商已逐步形成面向服务的体系结构,促使整个行业普遍置身于新的进化发展阶段。对于从事CRM的企业来说,这次体系结构的进化发展将直接影响其CRM升级。

    围绕CRM的部署方法一直存在许多言过其实的宣传。争论的焦点集中于究竟是内建(on-premises),还是采用租用方式(hosted CRM)?从根本上来说,无论何种类型或规模的企业都希望了解hosted CRM的解决方案是否适用于自己的组织。此外,大量新兴技术开始对CRM技术产生直接影响,包括:门户技术(portal)、业务流程管理(BPM)、以及客户数据集成(CDI,Customer Data Integration)等等。一旦技术领域的工作趋于成熟,同时企业也将不遗余力地将其更多的努力集中于跨功能和跨系统集成方面,可以预见,创新的解决方案势必在CRM策略中被探索发现。

    CRM升级:最有效的途径。
    2004年将抛起一场CRM升级的潮流,这对于CRM供应商和系统集成商来说无疑是个好消息,同时也受到用户的期待。驱使CRM升级的主要因素有两个:首先,针对大多数供应商产品被抑制的需求最近得到释放,这被描述为体系结构设计的一次巨大突破。2002-2003年期间,由于IT资源限制的原因,大多数用户不得不推迟他们的升级计划。事实上,对下一代体系结构进行升级可能会面临更多的困难,并且可实现的投资回报也将低于预期希望。然而,随着大多数供应商第一代Web体系结构第二或第三版本的发布,用户购买力的增强,以及用户意识到他们的许多版本已不再适用(或者处于不好的状态)等因素的影响,将促使用户考虑在今年内对CRM系统进行升级,或者至少促使他们计划在未来12个内进行系统升级。

    驱使CRM升级的第二个主要因素是由于存在一些难以处理问题。除了用户承诺不进行升级,一些供应商(如:Siebel)计划在今年为其客户或旧版本实施显著的增强型维护。另外一些供应商则针对旧版本系统,采用减少技术支持人员的方式,或完全取消相应的技术支持。

    正如俗话所说的那样“躲得过初一,躲不过十五”。现在决定实施系统升级的用户只需为升级支付费用。然而,那些推迟其升级计划的用户,将面临产品不受技术支持,版本不再适用于企业需求的诸多风险。同时,他们还可能支付更多的维护费用。当然,还是有一些选择可供考虑,如:选择其他的应用供应商。

    CRM部署:对CRM业务进行综合管理。
    例如应用的最佳配合、发展和迁移、IT成熟、IT限制条件、以及所有权成本等关键性问题,必须在制订CRM部署决定之前得到慎重考虑。无论是hosted CRM应用或是on-premises CRM应用,都能为企业提供显著的价值交付(尽管企业并不需要过多的考虑交付模型)。进一步说,当涉及到IT限制和商业限制问题时,调整好的CRM应用将更加有效地支持现在以及将来的业务和技术要求。从而促使CRM计划的成功取得最大的可能性。

    Hosted CRM供应商,尤其是salesforce.com,大力宣传其CRM产品并引起市场的广泛关注。META Group的客户期望hosted CRM供应商交付的应用能够满足他们的企业发展要求;总的来说,采用hosted CRM解决方案的用户对他们的投资感到满意。即便他们的投资范围受到一定程度的局限(如:接触管理、渠道报告),但hosted CRM解决方案更快捷、更廉价、更佳的功能性,仍然受到大多数企业的青睐。此外,在许多情形中,CRM业务转换的长期策略开始演变为一种短期策略。解决方案的迅速实施以及价值创造的进程成为用户关注的焦点。这种策略性转变是从CRM的早期发展(1997-2001年期间)中探索而来。

    企业必须接受的现实是,他们将最终执行on-premises应用,选择使用hosted解决方案,并且将组织CRM业务的全部功能和系统(如:呼叫中心)承包给专业的outsource呼叫中心。目前来说,由于这个问题还没有找到最有效的解决途径,因此无法就所有情形给出一个具体的解决方案。世界经济新秩序下的部署选择,其关键性问题在于如何对各种类型的CRM业务进行组合和管理,以及如何促使组合业务最终支持企业的CRM策略。

    新技术的发展以及他们对CRM的影响。
    一体化进程无疑是CRM面临的最大挑战并且是最耗费资源的行动。概括而言,公司每年在CRM许可上需要支付1美元,针对CRM许可的咨询和执行服务则需花费3美元。60%的CRM服务成本花费在系统和数据的集成方面。同时,CRM一体化进程还受到大量工具、方法和技术的直接影响。在此情况下,为满足综合解决方案的创新要求,新技术和新市场迅速应运而生。综上所述,需要对以下技术进行密切关注:

     

    • 门户(Portal):尽管门户不是一个新市场,但是他们正越来越密切的与CRM业务相联系。一个门户机制是集合应用、内容、商业逻辑或规则、数据以及统一标准的用户界面。在为终端用户特定业务流程的相关经验提供内容交付时,门户框架能够针对特定的终端用户、任务、或职能,实现其个性化信息的交付。

       

    • 业务流程管理(BPM):很多企业试图忽视部门的工作流程,直接对整个企业实行业务流程管理。由于目前的业务流程管理还不够规范,并且相关的解决方案要求大量组件实现集成(例如:建模、规则引擎、协调、集成服务器、监视、最优化,等等),因此BPM的发展目标强调实现业务流程全部功能性的明晰化、自动化、和最优化。对于实施CRM策略的企业来说BPM尤为重要,因为销售、服务、业务处理等流程不仅与企业内部的诸多职能密切联系(如:订单管理、广告宣传、生产),同时还将直接影响其外部(如:渠道、合作伙伴)。

       

    • 客户数据集成(CDI):CDI是通过潜在的数据集成、合并、以及互用性,为全面实施用户操作而设计的一种策略。针对单个或团队用户运用价值计量学方法(如:用户收益率),使客户关系管理策略得到有效利用和实施,并随着时间推移不断得到改进。全面的用户CDI策略要求对现有的新生价值和未来的CRM投资有具体的认识,并且具备实施和操作的独特计划。根本上来说,由于CDI承诺能够为用户创建一个贯穿CRM全过程的单一记录,因此它被视为CRM策略的一个长期努力目标。

      如果缺乏合理的CRM技术计划,可能导致CRM长期计划混乱无序。企业计划对CRM系统进行升级前,应该对其部署选择进行认真评估;同时,需要对可能成为CRM关键技术的新兴综合技术有所了解和认识。(责任编辑:刘燕之

    展开全文
  • 整合资源,创造价值,是目前企业界、营销界普遍认同的价值观,“借力”、“共享”、“双赢”也是很多企业老总经常挂在嘴边的顺口溜,“借力共赢”是企业整合资源的另一种诠释,资本与市场是企业发展的原动力!...

    整合资源,创造价值,是目前企业界、营销界普遍认同的价值观,“借力”、“共享”、“双赢”也是很多企业老总经常挂在嘴边的顺口溜,“借力共赢”是企业整合资源的另一种诠释,资本与市场是企业发展的原动力!
      
      企业要借力,要实现共赢,应该注重以下两个层次:一是选择正确的战略伙伴,选择合适的外脑资源,把握最佳的借力时间;二是让专业的人做专业的事,让资源最大限度地合理利用。这样,才能借助资本与市场两个轮子,实现品牌腾飞。
      
      7月19日,桑迪营销咨询机构全程策划的首届医药产业融投资高端论坛(长三角峰会)在上海成功举行。论坛针对中国医药产业的融资投资问题进行了多角度、深层次的探讨,为医药企业和投资企业搭建了互动沟通的平台,为有投资、融资意向的企业提供了直接对话的机会。论坛上,笔者就如何整合资源,如何借力,资本与市场如何借力策划、实现共赢等问题发表了精彩报告,本文由演讲内容整理而成。
      
      一、老总的困惑
      
      在企业的发展过程中,总会有这样或那样的瓶颈:
      
      单品如何过亿?
      
      品牌过亿之后如何第二次腾飞?
      
      如何打造第二个核心赢利品种?
      
      如何从区域市场走向全国市场?
      
      老品种如何创新?
      
      如何借助外脑四两拨千斤?
      
      如何赢得风险投资的青睐?
      
      ……
      
      这是摆在企业老总面前的现实问题!我们如何客观去评估?
      
      二、企业需不需要借力——要有正确的借力心态
      
      有的企业老总自豪地说:
      
      我们从来不向银行贷款!
      
      我们的产品从来不愁销路!
      
      我们的队伍执行力非常强!
      
      我们从来不找外脑做咨询策划!
      
      1、正确认识自己,分析自身长处与不足
      
      企业自身最擅长什么?我们的优势有哪些?我们的不足有哪些?比如,队伍很忠诚、执行力很强,但策划力与创新力不足;或者网络资源有限,总是裹步不前;或者自己资金很充足,但不知道如何去花。
      
      2、客观评估他人,分析他人的长处与不足
      
      我们要对自己的潜在战略伙伴进行了解,分析他们的强势在哪里,他们哪些方面比自己强,哪些方面值得借鉴,甚至为我所用;哪些方面不如自己,我们应该避免哪些资源的浪费,等等。
      
      只有知己知彼,方能做出正确科学的评估,判断自己需不需要借力。
      
      三、企业如何去借力——整合资源,创造价值
      
      1、借助渠道网络的力量
      
      联合商业公司,大力开发连锁药店。如古汉养生精与九州通的合作,迪诺制药与双鹤、老百姓联手等。他们借力“21世纪医药连锁高端论坛”,使产品低成本迅速进入新市场或者新的终端,得到连锁药店的青睐与推荐。古汉养生精在湖南市场已具有不可替代性,药店把其当成促销工具,作为吸引顾客上门的品牌。迪诺制药通过加大VIP终端的开发力度,希望在全国发展1000家具有战略合作性质的零售终端,以实现对市场的强力占有。
      
      2、借助专家的力量
      
      企业的营销团队需要经常性的培训,才能不断提升执行力;经销商队伍也需要经常性的客情维护和智力支持,才能进一步强化其对企业的忠诚度。而这些,如果仅靠企业自身的“说教”则难以服众。山东老来寿药业借助桑迪的专家团队设计新营销模式,并在招商大会上请桑迪专家进行解读,帮助经销商树立信心,就收到了很好的效果。广州潘高寿、广东德众药业的度营销会议,都曾请桑迪的专家去介绍品牌营销理念及成功策划经验,帮助每一位营销人员认识自身的不足,学习别人的长处,有效地提升了营销团队和经销商队伍的整体水平。
      
      3、借助传媒的力量
      
      媒体是进行品牌传播必不可少的信息载体。不管是处方药还是非处方药,都必须进行媒体宣传。虽然现在广告的效果大不如前,但广告在扩大品牌影响力与知名度方面的作用不可低估,做广告就意味着打品牌。但广告不是万能的,那么就很有必要根据市场形势经常调整产品的媒体策略。比如现在的药品高度同质化,又有太多的政策限制,我们可以借力媒体进行新闻造势。潘高寿的“野狼行动”、奇康维药的“七剑下天山”、蓝天玉叶的“宗正信阳毛尖”、南京同仁堂的乐家老铺重出江湖、东药集团的“珍珠行动”等,桑迪都为其策划过新闻造势传播,取得了意想不到的宣传效果。特别是2005年到2006年连续两次为潘高寿策划的新闻造势活动,在包括新华社、工人日报等全国性和省、市级媒体发稿近500篇,很好地推动了企业的渠道铺货和对终端网络的拓展。
      
      4、借助数据的力量
      
      现在的医药企业越来越重视数据分析,很多投资公司更需要数据说话。要推出新品,或者拓展新区域,企业都有必要借助数据。客观、真实的数据分析,可以使企业有效规避营销风险,发现市场契机。曾经有一家投资公司想收购某企业,都快尘埃落定了,后来投资公司研究数据后发现了融资企业很多与事实不符的信息,后来放弃了收购,为企业挽回了数千万元的资金风险。当前,对数据的认可也逐渐从大企业、外资企业向中型企业延伸。目前,国内能提供医药行业数据分析的权威机构有南方医药经济研究所。南方所主要依托全国重点药品零售系统、医院系统的网络资源提供的数据进行统计、研究和分析。
      
      5、借助资本的力量
      
      资本是一个奇怪的东西,当你在遭遇困境的时候能够助你一臂之力,帮助你快速走向市场,将技术转换成生产力,使企业再次腾飞。但资本对项目的要求是苛刻的:从被投资企业的行业地位来看,风投愿意进入的医药企业基本上都是位于细分领域的前五名;从被投资企业所掌握的技术来看,这些企业都已经具有相当成熟且具备一定市场前景的技术。
    四环生物产业集团的董事长陈军说过,他愿意在未来帮助很多企业上市,但所选项目必须是在该领域领先的。目前风投也在全线布局医药行业,除了研发和生产领域外,还将目光瞄准了流通与零售终端,海王星辰、开心人(集团)控股股份就被风投相中。风投一般看好生物医药、中成药、抗肿瘤药等在未来具有成长前景的医药生产企业;其次是选择在中国具有良好前景的医疗器械、医疗保健企业;具有IT概念的医疗电子商务也颇受风投的关注。
      
      5、借助策划的力量
      
      策划不是万能的,但没有策划是万万不能的。医药营销从一开始就注定离不开策划。医药营销脱胎于保健品营销,医药营销的很多模式及策略都在向保健品借鉴,国内很多医药企业的营销高管都来自三株、红桃K、健特(脑白金)等知名保健品企业。在药品竞争激烈、终端力量强势、消费者日趋理性、政策日趋规范的时代,策划是提升销量、打造品牌的一条有效途径。古汉养生精从3000万到目前的2个亿、整肠生从多年徘徊在1个亿,到目前不断增长的2亿多,南京同仁堂的排石颗粒从8000万元到实现亿元的突破、潘高寿从岭南走向全国的“野狼行动”等,无不是策划在起到主要的推动作用。
      
      四、借力外脑实现共赢
      
      1、借力外脑提升产品力
      
      神威药业五福心脑清软胶囊是一个典型的借力外脑迅速提升品牌力的产品。在桑迪为其提供服务之前,尽管其依靠渠道营销取得了不错的市场业绩,在河南、河北、山东,已成为心脑血管自主用药的强势品牌,但在业界的知名度并不高。2008年,面对新的竞争环境,针对产品具有自用兼礼品的特性,结合奥运年主题(奥运的吉祥物为五个“福娃”),桑迪为其提炼出“华夏传五福,盛世显神威”的品牌策略,依托亚洲最大的软胶囊生产基地、进口设备全自动化生产流程等优势,强化神威药业打造现代中药软胶囊的品牌形象,在区域媒体上进行深度传播与演绎,巩固老市场,拓展新市场,使产品力得到了极大的提升。在医药产业回暖的大好形势下,神威药业肩扛“中药现代化”大旗,专注于现代中药的研发、生产和销售,加大神威品牌推广力度,进一步增加企业的核心竞争力,使企业品牌和产品品牌都有了长足的发展。
      
      2、借力外脑强化决策力
      
      作为东药集团旗下的核心企业——沈阳第一制药厂,推出的拳头品种整肠生在经历了13年的市场积淀后,决定发力肠药市场。为此,整肠生与桑迪营销咨询机构合作,从确定品牌定位、代言人选择、广告风格定位、广告表现形式、媒体策略,到试点市场选择、营销模式优化,进行了重新决策。
      
      依据品牌6力营销理论,进行充分的市场调研,深刻研究思密达、必奇、丽珠肠乐、金双歧、培菲康、米雅、妈咪爱、肠炎宁等主要竞品的市场策略,桑迪建议整肠生采取差异化营销策略,不是与微生态制剂竞争,而是与所有肠道用药竞争,向所有肠道药物竞品宣战,强化微生态制剂的高科技和肠道用药的升级换代。这样的品牌策略将最有可能扩大市场占有率,从临床市场向零售普药市场扩展。根据这一品牌策略,整肠生将核心定位修正为“新一代肠道药”,和所有肠道药物展开竞争,并提出“治肠不伤肠”的核心诉求,针对拉肚子、老肠炎、腹胀气三大症状,明确症状,模糊人群,大力倡导“肠道用药急需升级换代”,在“升级换代”中抢占先机,顺势成为领军品牌。
      
      同时,整肠生的广告策略也非常成功,在深入挖掘产品内涵后,选择了着名的广告明星许晓力出任产品代言人。在整肠生的平面和电视广告中,许晓力拿着整肠生,做着“OK”手势的幽默形象在消费者脑海留下了深刻印象。此外,为了应对目前品牌药普遍存在的在终端被拦截的现象,桑迪为其度身订制了“新终端战术”,从而保证了策略最大限度的落地。经过一系列的成功运作,包装为蓝白双色搭配的整肠生,成为了2006年、2007年肠药市场最抢眼的品牌,其微生态系列产品销售业绩达4个多亿的规模。
      
      3、借力外脑精准企划力
      
      桑迪接受东药集团珍稀渭的整体营销策划时,其产品概念、包装毫无优势可言。虽然产品效果不错,企业有强大的研发实力,但是珍稀渭的策划一直没有引起足够重视。产品包装、形象与概念的不足,使得产品销售严重受阻。
      
      包装是产品的门面,好的包装能够激起患者的购买欲望与建立信任感。通过对产品成分的研究分析,桑迪发现产品里面含有珍珠成分,这在胃药中是比较少见的。“珍稀渭里有珍珠”这一发现立刻激发了策划人员的灵感,于是决定把“珍珠”作为产品的差异点进行放大宣传,并确立以渐变的绿色作为基本色调,体现产品天然绿色、安全性高的特点。在包装最醒目的位置上,用闪闪发光的珍珠串联成一个胃的形状,直白诉求产品功能。
      
      珍珠美容修复创伤面的效果自古医书就有记载,胃病需要三分治、七分养,策划人员又提出“治胃养胃”的核心诉求:抑酸止痛、迅速修复、长效养护。珍珠可有效修复胃部溃疡面,形成珍珠保护膜,这在胃药里是一个很大胆的机理创新与提法。广告词“珍稀渭里有珍珠,治胃养胃珍稀胃”更是理性与感性交融,差异与关怀同在。策划为珍稀渭大幅提升市场份额创造了必要的成功条件。为此,东药集团特别将珍稀渭的上市推广命名为“珍珠行动”。经过一年的策略性推广,珍稀渭一跃成为辽宁省胃药市场的前三甲。
      
      4、借力外脑完善执行力
      
      迪诺制药前期的成功主要是靠低价策略,企业品牌优势不明显,掌控的网络终端较少。在桑迪的协助下,2007年,企业确立以奥美拉唑为品牌根基品种,希望以湖南为样板,从区域启动,逐步过渡到在全国推广,最终与主要经销商、零售商共同打造万家连锁终端样板店,带动系列品种销售,力争1~2年内突破2个亿的规模。
      
      桑迪对迪诺的洛克奥美拉唑肠溶胶囊进行系统、整体的策划,借势洛赛克全球的绝对领导品牌地位,提出“(奥美拉唑)全球领先的经典胃药——迪罗洛克”。但是,再好的策划也需要系统扎实的执行。迪诺制药在湖南上市也遇到了一系列棘手的问题:公司业务员过去很少与连锁药店打过交道,缺少基本的客情关系;企业虽有规模但在大众化市场仍显名气不足;药店要求较大的利润空间,进场条件苛刻,企业无法满足;企业广告还没有上,药店铺货进展缓慢……为此,桑迪为其策划了“21世纪医药连锁高端论坛(湖南站)”活动,邀请业内知名专家、商业企业老总助阵,与会嘉宾除了湖南主流连锁药店界的上百名老总,还包括全国50强连锁药店的老总。迪诺制药借机展示了丰富的行业资源和品牌战略思想,与商业公司、连锁药店建立了沟通的渠道,大大节省了与终端药店谈判进场的时间和终端建设的成本,为老品种奥美拉唑的终端销售创造必胜条件,也较好地树立了迪诺制药在湖南乃至全国连锁药店心中的企业形象.

    5、借力外脑彰显创新力
      
      我国茶叶企业普遍规模较小,市场主体多而杂,产品同质,品牌意识淡薄,营销管理落后……中国茶业的现状急需改变,一场以营销为龙头,以品牌为核心,贯穿茶产业链的整合势在必行。
      
      位于“中国茶都”信阳的河南蓝天茶业旅游股份有限公司,率先扛起了中国茶业整合大旗。蓝天茶首次将品牌营销引入茶业,借鉴快速消费品、保健品的渠道营销经验,计划用5年时间,通过对标准、渠道、终端的全面掌控来整合上下游资源,最终实现整合中国茶业的目的。为此,桑迪营销团队为其精心策划了准确的核心概念定位,提炼了“蓝天茶,润天下”的企业品牌宣传口号,作为蓝天茶的核心概念定位,并作为核心广告语。这6个字大气磅礴、寓意深远,不仅可以制造消费流行,而且还有一种“整合茶业,舍我其谁”的气势。
      
      有了大定位,大气势,还需要大表现,需要迸发出一股雄壮力量。蓝天集团的决策者曾经想在中央级媒体上做品牌广告,但桑迪认为条件远没有成熟:第一,信阳毛尖有区域消费特征,上全国媒体广告费会很大部分被浪费;第二,蓝天集团刚介入茶产业,渠道网络队伍建设一切从零开始,根基没有打稳;第三,产品并未正式面市,营销模式还在探讨之中,没有定型。作为一个新入门的集团企业,需要得到行业前辈的支持,同时展示企业的实力,以获得经销商的信任。因此,前期宜在茶业界形成话题、造大声势,把钢用在刀刃上。
      
      信阳是蓝天集团所在地,在此举办的每年一届的中国茶文化节在业内享有盛誉。茶文化节是交流信息、推广品牌、弘扬茶文化的重要平台。桑迪建议蓝天集团申请承办2006年的茶文化节,并为其策划了一场到位的新闻造势活动。
      
      在为期3天的“中国信阳第十四届茶文化节”上,通过赞助冠名茶艺表演、文艺晚会、茶文化论坛、茶叶评比、拍卖会等活动,全国报纸、网络等400多家媒体争相报道,“蓝天玉叶”成为了一个热门话题。尤其是在信阳毛尖极品拍卖会上,“蓝天玉叶”以100克14.9万元的天价被拍卖,创下了中国绿茶拍卖价格之最。通过桑迪的策划,一时间,“二两茶叶拍出14.9万”、“天价茶叶拍卖正在变成‘作秀’”、“茶叶拍卖斗富,吓跑欧洲客商”等有关“蓝天玉叶”的新闻报道铺天盖地,从新华社、新浪网等权威媒体开始,全国400多家媒体争相报道或转载,“蓝天玉叶”的品牌价值得到了极大提升。
      
      6、借力外脑打造品牌力
      
      欧诗漫是一个来自江南水乡的珍珠品牌。但近年来集团遇到了品牌经营瓶颈。在新的竞争形势下,品牌形象概念如何整合?是走国际化道路,还是立足本土?新竞争态势下该如何定位欧诗漫的品牌形象及概念?
      
      当前的珍珠美容市场,竞争态势可谓鱼龙混杂,消费者缺少评判标准,无所适从。因此,珍珠美容行业呼唤领袖,珍珠产业需要一个领军企业来维护和整合。欧诗漫化妆品是一个二线品牌,主要集中在二三级市场销售,产品价格走中低路线;来自浙江,但品牌形象不够鲜明;基本没有做广告,销量完全靠终端推荐与口碑效应。
      
      欧诗漫如向保健食品领域延伸,需要给消费者一个足够好的利益点,一个好的购买理由。首先需要高起点定位,把欧诗漫打造成“中国珍珠第一品牌”,让人们一提到珍珠粉就想到欧诗漫,提到欧诗漫就让人想到珍珠粉,把欧诗漫打造成浙江的名片、江南的名片。那么,如何有效地表现品牌内涵呢?
      
      民族的也就是国际的。欧诗漫来自江南水乡,那么品牌应该体现十足的江南水乡风情。提及江南,很多人会联想到小桥、流水、美女、旗袍,这是一个非常小资的印象。其实,江南盛产珍珠也是很多人知晓的,来江南旅游的人都记得,旅游景点到处都是珍珠铺子。有了这个启发联想的点,桑迪希望把珍珠也融入江南印象元素,让欧诗漫成为纯粹的江南品牌。因此,确立了以“珍珠—美女—江南水乡—旗袍—晴雨伞”等为视觉印象的江南风情画卷。在明确了品牌的核心表达内涵后,桑迪希望在整体设计风格上让其尽情得以体现。最后,决定由职业模特来演绎江南珍珠文化品牌内涵,要求她穿上旗袍,撑一把小晴雨伞,烙上江南印象,担当欧诗漫珍珠美容品牌的形象大使。
      
      随后,桑迪的品牌设计师们决定整体调整欧诗漫的品牌印象,将欧诗漫的品牌力进行全面提升。升级范围除了珍珠粉、美容保健食品外,珍珠化妆品、珠宝等系列产品也要作相应调整,因为这才符合欧诗漫整合珍珠美容资源的目的,也能使企业在三大领域互相借势,互相带动销售。
      
      为表现江南水乡的韵味,欧诗漫在产品终端建立了统一的江南形象,包括专卖店、专柜的装饰等,促销员的着装为丝绸旗袍,在终端形成了眼球聚焦点,营造出了一股江南水乡氛围,给人以浪漫、诗情画意的品牌联想。在销售主张上,桑迪为其确立了“以内养外”的珍珠美容理念,提出了“珍珠整体美容方案”,充分借势欧诗漫的化妆品,让两者在同一个终端进行销售,以内养外,内外兼修,相互融合。并把广告语定为“以内养外,光彩肌肤养出来!”这虽然算不上一句经典的广告语,但对欧诗漫来说是最适合不过了,非常好地表现了欧诗漫珍珠美容的品牌内涵

    展开全文
  • 项目整合管理

    千次阅读 2008-12-08 23:02:00
    它是一项综合、全局的工作,主要内容是在相互冲突的目标或可选择的目标中权衡得失。虽然所有的项目管理过程在某种程度上都可看成是一个整体,但在整合管理中所描述的这些过程是最基本的管理知识。整合管理主要...
    项目整合管理就是为满足各方需求而进行协调以达到预期目的的过程。它是一项综合性、全局性的工作,主要内容是在相互冲突的目标或可选择的目标中权衡得失。虽然所有的项目管理过程在某种程度上都可看成是一个整体,但在整合管理中所描述的这些过程是最基本的管理知识。整合管理主要包括:项目计划开发、项目计划实施、项目综合变更控制这三个过程。这些过程彼此相互影响,同时与其它领域中的过程也互相影响。
    项目计划开发
    在整合管理中,项目计划开发就是利用其它各领域的项目规划过程的输出,创建一个内容充实、结构紧凑的文件来指导项目的实施和控制。因此,项目计划开发过程所需要的主要的依据是其它项目规划过程的成果。在这里,项目规划过程主要包括:范围计划、范围界定、活动定义、进度安排、资源规划、成本预算、质量规划、管理规划、沟通规划等一系列规划过程。在这些过程中,最基本的文件是:工作分析结构和辅助说明。在项目计划开发中还需要考虑组织的管理政策。所有项目相关组织可能都有正式或非正式的政策。这些政策是项目实施的规范和标准,必须被项目团队进行遵守和执行,因此在计划时必须考虑到它们的影响。例如:人事管理政策中的雇佣和解雇标准等。
    同时,项目计划开发也需要参考项目的历史资料。项目的历史资料是进行项目规划的基础,它为项目规划提供了参考依据。
    最后,在项目计划开发中还需要考虑项目的制约因素和假定条件。制约因素是限制项目管理团队运行的因素。例如:当一个项目按照合同执行时,合同条款通常是制约因素。假定是指为了项目规划目标的需要,需要将一些不确定的内容作为真实的和确定的内容来看待。作为项目规划的一部分,项目团队经常识别、记录并促成这些假定。假定通常包含着一定程度的风险。
    在项目计划开发时,通常会采用程序化的计划方法来引导项目团队的工作。对于小型项目,可能是非常简单和结构化的方法,如:标准的模板、图纸等;对于一些大型项目,可能需要采用一系列的模型和各种数学方法,如:蒙特卡洛方法、价值分析法等。对于大多数项目,一般会采用将“刚性”工具和方法和“柔性”工具和方法结合在一起使用。在项目计划开发过程中,需要从事大量的信息收集、整理和加工处理工作,为了方便工作的进展,常常采用项目管理信息系统(PMIS)。随着计算机系统应用的普及,PMIS已被大多数项目实施组织所采用,特别是对于一些大型项目,没有这种基于计算机系统,很难编制出复杂的项目计划。项目计划开发将会产生两项重要的成果:项目计划和辅助说明。项目计划是正式的、被批准的用于管理和控制项目实施的文件。对于项目计划中不能包含的内容需要以辅助说明的形式来体现出来。
    项目计划实施
    项目计划实施过程是完成整个项目计划任务的过程。在这一过程中,项目的各种目标需要被实现,各项专项计划需要被落实。项目计划实施的主要依据是项目计划开发阶段的成果——项目计划、辅助说明,同时组织管理政策也将作为辅助文件来指导项目实施工作。在项目计划实施过程中,势必会有各种风险事件的发生,为了降低项目风险事件对项目实施的影响,通常会设计一些预防措施来减少项目风险事件发生概率。这些措施也将作为输入信息应用于项目计划实施过程中。
    另外,在项目计划实施中,通常很难保障项目完全按照计划进行,当项目有了偏差时,就需要采取一定措施来降低偏差对于项目的影响,这些措施被称为纠偏措施,它也将作为输入信息应用于项目计划实施过程中。
    项目计划实施过程是项目中最有影响的过程,项目经理和项目管理团队必须协调和解决项目中存在各种技术和组织问题以实现项目目标。在这一过程中通常采用的方法、技术和工具包括以下几方面:
    1、普通管理技能。如领导艺术、信息交流和谈判等都对项目计划实施产生实质性的影响。
    2、生产技能和知识。有关项目产品的技能与知识是项目计划实施的基础。这些必要的技能被作为项目规划的一部分,由人力资源管理中的人员来获得。
    3、工作分配系统。这是为确保项目工作能按时、按序地完成而建立的过程。基本的方式是以书面委托的形式开始进行工作活动或启动工作包。但在某些情况下,需要根据具体的项目特点来采用适当的工作分配系统。
    4、进展状况检查会议。它是项目进展信息交流的常规会议。在许多项目中,进展状况会议以各种不定期和不同级别的形式召开(比如:项目管理团队内部的周会等)。
    5、项目管理信息系统(PMIS)。
    6、组织管理过程。在项目实施过程中,项目的所有相关组织均存在着正式的和非正式的过程,这些过程对于项目的执行有很大的影响。
    项目实施的结果是项目实施过程中产生的项目产出物。另外,还包括项目实施工作和实施结果的各种文件资料,如:哪些任务已经完成,哪些工作没有完成,满足的质量标准是什么等等。
    综合变更控制
    对于项目而言,变更是必然的。为了将项目变更的影响降低到最小,就需要采用变更控制的方法。综合变更控制主要包含以下内容:找出影响项目变更的因素、判断项目变更范围是否已经发生等。进行综合变更控制的主要依据有:项目计划、变更请求和提供了项目执行状况信息的绩效报告。
    保证项目变更的规范和有效实施,通常项目实施组织会有一个变更控制系统。变更控制系统是一个正式和文档化的程序,它定义了项目绩效如何被监控和评估,并且包含了哪种级别的项目文件可以被变更。它包括文书处理、系统跟踪、过程程序、变更审批权限控制等。
    综合变更控制的结果主要有:更新的项目计划、纠正措施、经验总结。
    以上概括性地分析了项目整合管理的主要过程和工作以及过程中采用的方法和技术。值得注意的是,在项目的不同阶段,项目整合管理工作的内容会侧重不同,工作量也会不同。但是要想使项目获得成功,必须从整合的角度,以全局的观点开展整合管理,不能只强调各项具体的专项管理工作。
    项目采购管理
    众所周知,公司的根本目标是追求利润最大化。增加利润的方法之一就是增加销售额。假设某公司购进50000元的原材料,加工成本为50000元,若销售利润为10000元,需实现销售额110000元。如果将销售利润提高到15000元而利润率不变,那么销售额就需实现165000元。这意味着公司的销售能力必须提高50%,这是非常困难的。还有一种方法也可实现,假定加工成本不变,可以通过有效的采购管理使原材料只花费45000元,节余的5000元就直接转化为利润,从而在110000元的销售额上把利润提高到15000元。
    上面的案例说明了良好的采购将直接增加公司利润和价值,有利于公司在市场竞争中赢得优势。采购管理涉及内容繁杂,本文主要从制定采购计划、采购过程管理、采购成本分析、采购安全和保密等方面对采购管理加以探讨。
    制定采购计划
    1、制造、采购分析
    一般而言,在采购之前首先要做制造、采购分析,以决定是否要采购、怎样采购、采购什么、采购多少以及何时采购等。 在制造、采购分析中,主要对采购可能发生的直接成本、间接成本、自行制造能力、采购评标能力等进行分析比较,并决定是否从单一的供应商或从多个供应商采购所需的全部或部分货物和服务,或者不从外部采购而自行制造。
    2、合同类型的选择
    当决定需要采购时,合同类型的选择成为买卖双方关注的焦点,因为不同的合同类型决定了风险在买方和卖方之间分配。买方的目标是把最大的实施风险放在卖方,同时维护对项目经济、高效执行的奖励;卖方的目标是把风险降到最低,同时使利润最大化。常见的合同可分为以下5种。不同合同类型适用于不同的情形,买方可根据具体情况进行选择。一般来说,其适用情况如下:成本加成本百分比(CPPC)合同:由于不利于控制成本,目前很少采用。成本加固定费用(CPFF)合同:适合于研发项目。成本加奖励费(CPIF)合同:主要用于长期的、硬件开发和试验要求多的合同固定价格加奖励费用(FPI)合同:长期的高价值合同。固定总价(FFP)合同:买方易于控制总成本,风险最小;卖方风险最大而潜在利润可能最大,因而最常用。
    3、采购计划编制
    根据制造、采购分析的结果和所选择的合同类型编制采购计划,说明如何对采购过程进行管理。具体包括:合同类型、组织采购的人员、管理潜在的供应商、编制采购文档、制定评价标准等。根据项目需要,采购管理计划可以是正式、详细的,也可以是非正式、概括的。
    采购过程管理:
    1、询价(Solicitation)询价就是从可能的卖方那里获得谁有资格完成工作的信息,该过程的专业术语叫供方资格确认(SourceQualification)。获取信息的渠道有:招标公告、行业刊物、互联网等媒体、供应商目录、约定专家拟定可能的供应商名单等。通过询价获得供应商的投标建议书。
    2、供方选择(SourceSelection)这个阶段根据既定的评价标准选择一个承包商。评价方法有以下几种:
    合同谈判:双方澄清见解,达成协议。这种方式也叫“议标”。
    加权方法:把定性数据量化,将人的偏见影响降至最低程度。这种方式也叫“综合评标法”。
    筛选方法:为一个或多个评价标准确定最低限度履行要求。如最低价格法。独立估算:采购组织自己编制“标底”,作为与卖方的建议比较的参考点。一般情况下,要求参与竞争的承包商不得低于三个。选定供方后,经谈判,买卖双方签订合同。
    3、合同管理
    合同管理是确保买卖双方履行合同要求的过程,一般包括以下几个层次的集成和协调。
    1)授权承包商在适当的时间进行工作。2)监控承包商成本、进度计划和技术绩效
    3)检查和核实分包商产品的质量。4)变更控制,以保证变更能得到适当的批准,并保证所有应该知情的人员获知变更。5)根据合同条款,建立卖方执行进度和费用支付的联系。
    6)采购审计7)正式验收和合同归档。
      采购成本分析
    本文开始的案例揭示了将采购成本降到最低对公司利润的增长的重要性,但更重要的是,应该考虑项目生命周期内的最低整体采购成本。在实际采购工作中,很多招标单位通常只关注承包方的投标报价,而忽视了招标成本、建设成本和所有权损耗成本等项目整体采购成本。
    1、招标成本
    首先要考虑发出招标要约前的行为,招标方需要确定目标、调查主题、编写需求建议书(RFP)、考察和认同供应商、获取内部的授权、寻求预算支持等,然后发出要约。该过程可能需要整个合同价的2%到5%。 然后,竞标者需要对招标方的招标文件制定其投标建议书,费时又费钱,每个竞标者在竞标说明上都要花费合同价的约1%到6.7%的成本。如果有五个竞标者,该成本将达到合同价的5%到30%。表面上看来,这笔款项由竞标者承担;但是,从长远看是由招标方承担。因为竞标者总把竞标成本直接加在每次竞标的项目上。评标程序开始后,招标方需做包括开标、评标、定标、谈判、批准等事项。这个总成本可能占合同价的2%到5%。如果因为某种原因必须重新招标时,这部分成本将大幅增加。
    因此,对于一般行业来说,竞标的总成本可能占到合同价的10%到50%。无论招标方处于何种行业,降低招标成本都是一种责任。
    2、建设成本建设成本是投标报价的主要依据,往往是买卖双方关注的重点。一般包括如下几个方面: 前期准备、正式建设费用等、与其它系统的集成、授权、交付和保险、相关手册、对员工和管理者的培训等。3、所有权损耗成本 所有权损耗成本指长期损耗成本,包括项目运营成本和处置成本。项目运营成本可能会持续多年,并且可能是前期费用的许多倍;在设备濒于报废之时还需考虑其销毁或处理的处置成本。
    综合考虑这些成本将有助于以正确的观点看待实际采购价,帮助买方选择最好的方案。
    采购安全和保密,采购过程中的“黄金规则”是要绝对保密,不让任何不应外传的信息从机构中泄密,不要和不应该知道此事的陌生人交谈,当对方是机构中的成员时可能会很难,但知道的人越少越不会有漏洞。妥善安置相关文件和计算机内的材料,不用时(包括周末和晚上)要将其锁好,不要将评估的表格展开放在桌子上,以免被看见,及时销毁那些敏感的文件而不是随手扔掉,以免别有用心者发现它们。

    风险管理
    风险在字典中的解释是“损失或伤害的可能性”,一般人们对风险的理解是“可能发生的问题”。风险与许多事物都有关联,例如,一个已经投入使用的存有易燃品的仓库,随时会有发生火灾的风险。一个建设中的项目也会面临许多不确定性的风险。风险就像“隐形杀手”一样,不知什么时候会出现。无论人们是否喜欢,风险是不以人的意志为转移的。但这并不意味着风险是无法避免的。比如,人们为了避免“患上重大疾病”,平时会积极参加各种健身活动,增强体质,提高防病能力。可以说,风险的存在要求人们要积极面对风险,做到有备无患,才能将风险的影响减到最小。
    挥之不去的“隐形杀手”
    项目是为完成某一独特的产品或服务所做的一次性努力。项目的最终交付成果在项目开始时只是一个书面的规划,无论是项目的范围、时间还是费用都无法完全确定。同时,项目创造产品或服务是一个渐近明细的过程,这就意味着项目开始时有很多的不确定性。这种不确定性就是项目的风险所在。风险一旦发生,它的影响是多方面的,如导致项目产品的功能无法满足客户的需要、项目费用超出预算、项目计划拖延或被迫取消、项目客户不满等。可以说,风险存在于任何项目中,并往往会给项目的推进和项目的成功带来负面影响。不过,人们也无须恐惧风险,只要掌握风险发生的因果关系,风险是完全可以管理的。因此,关注项目风险,掌握风险管理的知识与技能,从项目组织、职责、流程与制度上建立一套风险管理机制是确保项目成功的前提与保障。
    隐形杀手来自何方
    不同类型的项目有不同的风险,相同类型的项目根据其所处的环境、项目客户与项目团队以及所采用的技术与工具的不同,其项目风险也是各不相同的。总的来说,基本可分为以下四类:1、技术、性能、质量风险
    项目采用的技术与工具是项目风险的重要来源之一。一般说来,项目中采用新技术或技术创新无疑是提高项目绩效的重要手段,但这样也会带来一些问题,许多新的技术未经证实或并未被充分掌握,则会影响项目的成功。还有,当人们出于竞争的需要,就会提高项目产品性能、质量方面的要求,而不切实际的要求也是项目风险的来源。
    2、项目管理风险
    项目管理风险包括项目过程管理的方方面面,如:项目计划的时间、资源分配(包括人员、设备、材料)、项目质量管理、项目管理技术(流程、规范、工具等)的采用以及外包商的管理等。
    3、组织风险
    组织风险中的一个重要的风险就是项目决策时所确定的项目范围、时间与费用之间的矛盾。项目范围、时间与费用是项目的三个要素,它们之间相互制约。不合理的匹配必然导致项目执行的困难,从而产生风险。项目资源不足或资源冲突方面的风险同样不容忽视,如人员到岗时间、人员知识与技能不足等。组织中的文化氛围同样会导致一些风险的产生,如团队合作和人员激励不当导致人员离职等。
    4、项目外部风险
    项目外部风险主要是指项目的政治、经济环境的变化,包括与项目相关的规章或标准的变化,组织中雇佣关系的变化,如公司并购、自然灾害等。这类风险对项目的影响和项目性质的关系较大。
    风险管理一般包括风险识别、风险分析、风险应对、风险监控这几个过程,它们之间的关系可见下图:
    风险识别
    它是管理风险的第一步,即识别整个项目过程中可能存在的风险。一般是根据项目的性质,从潜在的事件及其产生的后果和潜在的后果及其产生的原因来检查风险。收集、整理项目可能的风险并充分征求各方意见就形成项目的风险列表。
    风险分析
    确定了项目的风险列表之后,接下来就可以进行风险分析了。风险分析的目的是确定每个风险对项目的影响大小,一般是对已经识别出来的项目风险进行量化估计,这里要注意三个概念。
    (1)风险影响:它是指一旦风险发生可能对项目造成的影响大小。如果损失的大小不容易直接估计,可以将损失分解为更小部分再评估它们。风险影响可用相对数值表示,建议将损失大小折算成对计划影响的时间表示。
    (2)风险概率:它是风险发生可能性的百分比表示,是一种主观判断。
    (3)风险值:它是评估风险的重要参数。“风险值”=“风险概率”ד风险影响”。如:某一风险概率是25%,一旦发生会导致项目计划延长4周,因而,风险值=25%×4周=1周。
    3、风险应对:完成了风险分析后,就已经确定了项目中存在的风险以及它们发生的可能性和对项目的风险冲击,并可排出风险的优先级。此后就可以根据风险性质和项目对风险的承受能力制定相应的防范计划,即风险应对。制定风险应对策略主要考虑以下四个方面的因素:可规避性、可转移性、可缓解性、可接受性。风险的应对策略在某种程度上决定了采用什么样的项目开发方案。对于应“规避”或“转移”的风险在项目策略与计划时必须加以考虑。 确定风险的应对策略后,就可编制风险应对计划,它主要包括:已识别的风险及其描述、风险发生的概率、风险应对的责任人、风险对应策略及行动计划、应急计划等等。
    4、风险监控
    制定了风险防范计划后,风险并非不存在,在项目推进过程中还可能会增大或者衰退。因此,在项目执行过程中,需要时刻监督风险的发展与变化情况,并确定随着某些风险的消失而带来的新的风险。
    风险监控包括两个层面的工作:其一是跟踪已识别风险的发展变化情况,包括在整个项目周期内,风险产生的条件和导致的后果变化,衡量风险减缓计划需求。其二是根据风险的变化情况及时调整风险应对计划,并对已发生的风险及其产生的遗留风险和新增风险及时识别、分析,并采取适当的应对措施。对于已发生过和已解决的风险也应及时从风险监控列表调整出去。
    最有效的风险监控工具之一就是“前10个风险列表”,它是一种简便易行的风险监控活动,是按“风险值”大小将项目的前10个风险作为控制对象,密切监控项目的前10个风险。每次风险检查后,形成新的“前10个风险列表”。
    时刻警惕,再警惕
    风险贯穿于项目的整个生命周期中,因而风险管理是个持续的过程,建立良好的风险管理机制以及基于风险的决策机制是项目成功的重要保证。风险管理是项目管理流程与规范中的重要组成部分,制定风险管理规则、明确风险管理岗位与职责是做好风险管理的基本保障。同时,不断丰富风险数据库、更新风险识别检查列表、注重项目风险管理经验的积累和总结更是风险管理水平提高的重要动力源泉。
    项目沟通管理
    回想一下你所经历的项目,有没有出现过以下这样的情况:客户在检查项目阶段成果时,指出曾经要求的某个产品特性没有包含在其中,并且抱怨说早就以口头的方式反映给了项目组的成员,糟糕的是作为项目经理的你却一无所知,而那位成员解释说把这点忘记了;或者,你手下的程序员在设计评审时描述了他所负责的模块架构,然而软件开发出来后,你发现这和你所理解的结构大相径庭……
    可能你遇到的情况比上面谈到的还要复杂。问题到底出在哪儿呢?其实很简单,就两个字——沟通。以上这些问题都是由于沟通引起的,沟通途径不对导致信息没有到达目的地。“心有灵犀一点通”可能只是一种文学描绘出的美妙境界。在实际生活中,文化背景、工作背景、技术背景可以造成人们对同一事件理解方式偏差很大。在项目中,沟通更是不可忽视。项目经理最重要的工作之一就是沟通,通常花在这方面的时间应该占到全部工作的75%~90%。良好的交流才能获取足够的信息、发现潜在的问题、控制好项目的各个方面。
    沟通管理的体系
    一般而言,在一个比较完整的沟通管理体系中,应该包含以下几方面的内容:沟通计划编制、信息分发、绩效报告和管理收尾。沟通计划决定项目干系人的信息沟通需求:谁需要什么信息,什么时候需要,怎样获得。信息发布使需要的信息及时发送给项目干系人。绩效报告收集和传播执行信息,包括状况报告、进度报告和预测。项目或项目阶段在达到目标或因故终止后,需要进行收尾,管理收尾包含项目结果文档的形成,包括项目记录收集、对符合最终规范的保证、对项目的效果(成功或教训)进行的分析以及这些信息的存档(以备将来利用)。

    项目沟通计划是项目整体计划中的一部分,它的作用非常重要,也常常容易被忽视。很多项目中没有完整的沟通计划,导致沟通非常混乱。有的项目沟通也还有效,但完全依靠客户关系或以前的项目经验,或者说完全靠项目经理个人能力的高低。然而,严格说来,一种高效的体系不应该只在大脑中存在,也不应该仅仅依靠口头传授,落实到规范的计划编制中很有必要。因而,在项目初始阶段也应该包含沟通计划。
    设想一下,当你被任命接替一个项目经理的职位时,最先做的应该是什么呢?召开项目组会议、约见客户、检查项目进度……都不是,你要做的第一件事就是检查整个项目的沟通计划,因为在沟通计划中描述了项目信息的收集和归档结构、信息的发布方式、信息的内容、每类沟通产生的进度计划、约定的沟通方式等等。只有把这些理解透彻,才能把握好沟通,在此基础之上熟悉项目的其它情况。
    在编制项目沟通计划时,最重要的是理解组织结构和做好项目干系人分析。项目经理所在的组织结构通常对沟通需求有较大影响,比如组织要求项目经理定期向项目管理部门做进展分析报告,那么沟通计划中就必须包含这条。项目干系人的利益要受到项目成败的影响,因此他们的需求必须予以考虑。最典型也最重要的项目干系人是客户,而项目组成员、项目经理以及他的上司也是较重要的项目干系人。所有这些人员各自需要什么信息、在每个阶段要求的信息是否不同、信息传递的方式上有什么偏好,都是需要细致分析的。比如有的客户希望每周提交进度报告,有的客户除周报外还希望有电话交流,也有的客户希望定期检查项目成果,种种情形都要考虑到,分析后的结果要在沟通计划中体现并能满足不同人员的信息需求,这样建立起来的沟通体系才会全面、有效。
      语言、文字还是项目中的沟通形式是多种多样的,通常分为书面和口头两种形式。书面沟通一般在以下情况使用:项目团队中使用的内部备忘录,或者对客户和非公司成员使用报告的方式,如正式的项目报告、年报、非正式的个人记录、报事帖。书面沟通大多用来进行通知、确认和要求等活动,一般在描述清楚事情的前提下尽可能简洁,以免增加负担而流于形式。口头沟通包括会议、评审、私人接触、自由讨论等。这一方式简单有效,更容易被大多数人接受,但是不象书面形式那样“白纸黑字”留下记录,因此不适用于类似确认这样的沟通。口头沟通过程中应该坦白、明确,避免由于文化背景、民族差异、用词表达等因素造成理解上的差异,这是特别需要注意的。沟通的双方一定不能带有想当然或含糊的心态,不理解的内容一定要表示出来,以求对方的进一步解释,直到达成共识。除了这两种方式,还有一种作为补充的方式。回忆一下体育老师授课,除了语言描述某个动作外,他还会用标准的姿势来教你怎么做练习,这是典型的形体语言表达。像手势、图形演示、视频会议都可以用来作为补充方式。它的优点是摆脱了口头表达的枯燥,在视觉上把信息传递给接受者,更容易理解。
    两条关键原则
    在项目中,很多人也知道去沟通,可效果却不明显,似乎总是不到位,由此引起的问题也层出不穷。其实要达到有效的沟通有很多要点和原则需要掌握,尽早沟通、主动沟通就是其中的两个原则,实践证明它们非常关键。曾经碰到一个项目经理,检查团队成员的工作时松时紧,工期快到了和大家一沟通才发现进度比想象慢得多,以后的工作自然很被动。尽早沟通要求项目经理要有前瞻性,定期和项目成员建立沟通,不仅容易发现当前存在的问题,很多潜在问题也能暴露出来。在项目中出现问题并不可怕,可怕的是问题没被发现。沟通得越晚,暴露得越迟,带来的损失越大。
    沟通是人与人之间交流的方式。主动沟通说到底是对沟通的一种态度。在项目中,我们极力提倡主动沟通,尤其是当已经明确了必须要去沟通的时候。当沟通是项目经理面对用户或上级、团队成员面对项目经理时,主动沟通不仅能建立紧密的联系,更能表明你对项目的重视和参与,会使沟通的另一方满意度大大提高,对整个项目非常有利。
    保持畅通的沟通渠道
    沟通看似简单,实际很复杂。这种复杂性表现在很多方面,比如说,当沟通的人数增加时,沟通渠道急剧增加,给相互沟通带来困难。典型的问题是“过滤”,也就是信息丢失。产生过滤的原因很多,比如语言、文化、语义、知识、信息内容、道德规范、名誉、权利、组织状态等等,经常碰到由于工作背景不同而在沟通过程中对某一问题的理解产生差异。如果深层次剖析沟通,其实可以用一个模型来表示:从沟通模型中可以看出,如果要想最大程度保障沟通顺畅,当信息在媒介中传播时要尽力避免各种各样的干扰,使得信息在传递中保持原始状态。信息发送出去并接收到之后,双方必须对理解情况做检查和反馈,确保沟通的正确性。如果结合项目,那么项目经理在沟通管理计划中应该根据项目的实际明确双方认可的沟通渠道,比如与用户之间通过正式的报告沟通,与项目成员之间通过电子邮件沟通;建立沟通反馈机制,任何沟通都要保证到位,没有偏差,并且定期检查项目沟通情况,不断加以调整。这样顺畅、有效的沟通就不再是一个难题。
    项目人力资源管理
    天时、地利、人和一直被认为是成功的三大因素。其中,“人和”是主观因素,就显得更为重要。比如,在足球比赛中,主场球迷甚至可以被视为主队又多了一名队员。在项目管理中“人”的因素也极为重要,因为项目中所有活动均是由人来完成的。如何充分发挥“人”的作用,对于项目的成败起着至关重要的作用。
    项目人力资源管理中所涉及的内容就是如何发挥“人”的作用。它包括组织计划编制、人员募集和团队建设三部分。
    排兵布阵
    组织计划编制也可以看作战场上的“排兵布阵”,就是确定、分配项目中的角色、职责和回报关系。在进行组织计划编制时,我们需要参考资源计划编制中的人力资源需求子项,还需要参考项目中各种汇报关系(又称为项目界面),如:组织界面、技术界面、人际关系界面等。一般采用的方法包括:参考类似项目的模板、人力资源管理的惯例、分析项目干系人的需求等。
    组织计划编制完成后将明晰以下几方面任务:

    1、角色和职责分配。项目角色和职责在项目管理中必须明确,否则容易造成同一项工作没人负责,最终影响项目目标的实现。为了使每项工作能够顺利进行,就必须将每项工作分配到具体的个人(或小组),明确不同的个人(或小组)在这项工作中的职责,而且每项工作只能有唯一的负责人(或小组)。同时由于角色和职责可能随时间而变化,在结果中也需要明确这层关系。表示这部分内容最常用的方式为:职责分配矩阵(RAW),示例参见图1。对于大型项目,可在不同层次上编制职责分配矩阵(RAW)。
    2、人员配备管理计划。它主要描述项目组什么时候需要什么样的人力资源。为了清晰地表明此部分内容,我们经常会使用资源直方图,如图2所示。在此图中明确了高级设计者在不同阶段所需要的数目。
    由于在项目工作中人员的需求可能不是很连续或者不是很平衡,容易造成人力资源的浪费和成本的提高。例如:某项目现有15人,设计阶段需要10人;审核阶段可能需要1周的时间,但不需要项目组成员参与;编码阶段是高峰期,需要20人,但在测试阶段只需要8人。如果专门为高峰期提供20人,可能还需要另外招聘5人,并且这些人在项目编码阶段结束之后,会出现没有工作安排的状况。为了避免这种情况的发生,通常会采用资源平衡的方法,将部分编码工作提前到和设计并行进行,在某部分的设计完成后立即进行评审,然后进行编码,而不需要等到所有设计工作完成后再执行编码工作。这样将工作的次序进行适当调整,削峰填谷,形成人员需求的平衡,会更利于降低项目的成本,同时可以降低人员的闲置时间,以防止成本的浪费。
    3、组织机构图。它是项目汇报关系的图形表示,主要描述团队成员之间的工作汇报关系。
    招兵买马
    在确定了项目组什么时候需要什么样的人员之后,需要做的就是确定如何在合适的时间获得这些人员,或者说开始“招兵买马”,这就是人员募集要做的工作。人员募集需要根据人员配备管理计划以及组织当前的人员情况和招聘的惯例来进行。项目中有些人员是在项目计划前就明确下来的,但有些人员需要和组织进行谈判才能够获得,特别是对于一些短缺或特殊的资源,可能每个项目组中都希望得到,如何使你的项目组能够顺利得到,就需要通过谈判来实现。谈判的对象可能包括职能经理和其他项目组的成员。另外有些人员可能组织中没有或无法提供,这种情况下就需要通过招聘来获得。结束这部分工作后,我们就会得到项目团队清单和项目人员分配。
    团结就是力量
    项目团队是由项目组成员组成的、为实现项目目标而协同工作的组织。项目团队工作是否有效也是项目成功的关键因素,任何项目要获得成功就必须有一个有效的项目团队。团队建设涉及到很多方面的工作,如:项目团队能力的建设、团队士气的激励、团队成员的奉献精神等。团队成员个人发展是项目团队建设的基础。通常情况下,项目团队成员既对职能经理负责,又对项目经理负责,这样项目团队组建经常变得很复杂。对这种双重汇报关系的有效管理经常是项目成功的关键因素,也是项目经理的重要责任。
    进行项目团队建设我们通常会采用以下几种方式:
    团队建设活动 团队建设活动包括为提高团队运作水平而进行的管理和采用的专门的、重要的个别措施。例如:在计划过程中由非管理层的团队成员参加,或建立发现和处理冲突的基本准则;尽早明确项目团队的方向、目标和任务,同时为每个人明确其职责和角色;邀请团队成员积极参与解决问题和做出决策;积极放权,使成员进行自我管理和自我激励;增加项目团队成员的非工作沟通和交流的机会,如工作之余的聚会、郊游等,提高团队成员之间的了解和交流。这些措施作为一种间接效应,可能会提高团队的运作水平。团队建设活动没有一个确定的定式,主要是根据实际情况进行具体的分析和组织。
    绩效考核与激励 它是人力资源管理中最常用的方法。绩效考核是通过对项目团队成员工作业绩的评价,来反映成员的实际能力以及对某种工作职位的适应程度。激励则是运用有关行为科学的理论和方法,对成员的需要予以满足或限制,从而激发成员的行为动机,激发成员充分发挥自己的潜能,为实现项目目标服务。
    集中安排 集中安排是把项目团队集中在同一地点,以提高其团队运作能力。由于沟通在项目中的作用非常大,如果团队成员不在相同的地点办公,势必会影响沟通的有效进展,影响团队目标的实现。因此,集中安排被广泛用于项目管理中。例如,设立一个“作战室”,队伍可在其中集合并张贴进度计划及新信息。在一些项目中,集中安排可能无法实现,这时可以采用安排频繁的面对面的会议形式作为替代,以鼓励相互之间的交流。
    培训 
    培训包括旨在提高项目团队技能的所有活动。培训可以是正式的(如教室培训、利用计算机培训)或非正式的(如其他队伍成员的反馈)。如果项目团队缺乏必要的管理技能或技术技能,那么这些技能必须作为项目的一部分被开发,或必须采取适当的措施为项目重新分配人员。培训的直接和间接成本通常由执行组织支付。在项目的人力资源管理中,团队建设的效果会对项目的成败起到很大的作用,特别是某些较小的项目,项目经理可能是由技术骨干转换过来的,对于团队建设和一般管理技能掌握得不是很多,经常容易造成团队成员之间的关系紧张,最终影响项目的实施。这就更加需要掌握更多的管理知识以适应项目管理的需要。
    项目质量管理
    提起如今的IT项目,软件工程倍受关注。而软件的质量更是众人关注的焦点,因为目前还没有一套完善的评估标准。甚至有人提出,现在的软件开发根本提不上是“工程”,因为它太稚嫩了,还没有一套成熟的标准来比照;因而软件项目极易出现失败或失误。大量实践证明,软件工程项目的成败,通常是因为管理问题(协同工作的能力),而不是技术上的问题。要想做一盘“完美”的软件大餐,质量管理的作用是不言而喻的。
    在实际的项目质量管理中,质量管理总是围绕着质量保证(QualityAssurance)过程和质量控制(QualityControl)过程两方面。这两个过程相互作用,在实际应用中还可能会发生交叉。正如引言所述,关于软件的质量,很难下一个非常明确的定义。本文主要针对软件工程中的质量管理来进行讨论。

    做软件“大餐”的工序
    软件质量保证(SoftwareQualityAssurance,以下简称SQA)的目的是验证在软件开发过程中是否遵循了合适的过程和标准。软件质量保证过程一般包含以下几项活动:首先是建立SQA组;其次是选择和确定SQA活动,即选择SQA组所要进行的质量保证活动,这些SQA活动将作为SQA计划的输入;然后是制定和维护SQA计划,这个计划明确了SQA活动与整个软件开发生命周期中各个阶段的关系;还有执行SQA计划、对相关人员进行培训、选择与整个软件工程环境相适应的质量保证工具;最后是不断完善质量保证过程活动中存在的不足,改进项目的质量保证过程。独立的SQA组是衡量软件开发活动优劣与否的尺度之一。SQA组的这一独立性,使其享有一项关键权利——“越级上报”。当SQA组发现产品质量出现危机时,它有权向项目组的上级机构直接报告这一危机。这无疑对项目组起到相当的“威慑”作用,也可以看成是促使项目组重视软件开发质量的一种激励。这一形式使许多问题在组内得以解决,提高了软件开发的质量和效率。选择和确定SQA活动这一过程的目的是策划在整个项目开发过程中所需要进行的质量保证活动。质量保证活动应与整个项目的开发计划和配置管理计划相一致。一般把该活动分为以下五类:
    1)评审软件产品、工具与设施:软件产品常被称为“无形”的产品。评审时难度更大。在此要注意的一点是:在评审时不能只对最终的软件代码进行评审,还要对软件开发计划、标准、过程、软件需求、软件设计、数据库、手册以及测试信息等进行评审。评估软件工具主要是为了保证项目组采用合适的技术和工具。评估项目设施的目的是保证项目组有充足设备和资源进行软件开发工作。这也为规划今后软件项目的设备购置、资源扩充、资源共享等提供依据。
    2)SQA活动审查的软件开发过程:SQA活动审查的软件开发过程主要有:软件产品的评审过程、项目的计划和跟踪过程、软件需求分析过程、软件设计过程、软件实现和单元测试过程、集成和系统测试过程、项目交付过程、子承包商控制过程、配置管理过程。特别要强调的是,为保证软件质量,应赋予SQA阻止交付某些不符合项目需求和标准产品的权利。
    3)参与技术和管理评审:参与技术和管理评审的目的是为了保证此类评审满足项目要求,便于监督问题的解决。
    4)做SQA报告:SQA活动的一个重要内容就是报告对软件产品或软件过程评估的结果,并提出改进建议。SQA应将其评估的结果文档化。
    项目成本管理
    究竟如何进行项目成本管理呢?简单地说,就是通过开源和节流两条腿走路,使项目的净现金流(现金流入减去现金流出)最大化。开源是增大项目的现金流入,节流是控制项目的现金流出。
    在项目建设期,开源表现为扩大项目融资渠道,保证项目能够筹集足够的建设资金;节流是使融资成本或代价最低,最节省地实现项目的必要功能。在项目经营期,开源表现为增加主营业务收入、其他业务收入以及投资收益等;节流就是控制项目经营成本。
    在我国,项目的成本管理一直是项目管理的弱项,“开源”和“节流”总是说得多、做得少。例如,在项目前期,由于没有深入地调研,不能准确估算完成项目活动所需的资源成本,造成开源不足的局面;或者由于项目的资金“源”自政府或股东,花起来不心疼,更谈不上节流了。甚至部分项目根本就没有预测和分析项目现金流和财务执行情况,决策失误就在所难免了。
    成本管理的现金流分析采用的数据大都来自估算和预测,具有一定的不确定性,可能造成项目的现金流入减少或现金流出增加。不确定性成本管理或风险成本管理已成为我国项目管理中的弱项,也是很多商业银行贷款最关心的问题。即使是专业的咨询公司或项目管理公司,大多只停留在简单的量本利分析和敏感性分析。本文着重介绍概率分析、挣值分析等项目成本管理新方法。
    项目成本或投资估算
      成本估算(CostEstimating)是为完成项目各项任务所需要的资源成本的近似估算。
      美国项目管理学会(PMI)认为,有三种成本估算方法:
      类比估算:是一种自上而下的估算形式,通常在项目的初期或信息不足时进行。
      参数估算:是一种建模统计技术,如回归分析和学习曲线。
      自下而上估算:通过对项目工作包进行详细的成本估算,然后通过成本账户和工作分解结构(WBS)将结果累加起来得出项目总成本。这种方法最为准确。
      PMI成本估算的概念在我国常称作投资估算,即在对项目的建设规模、技术方案、设备方案、工程方案和项目实施进度等进行研究的基础上,估算项目的总投资。
    项目的现金流分析
    项目成本管理的基础是编制财务报表,主要有财务现金流量表、损益表、资金来源与运用表、借款偿还计划表等。其中,项目的现金流量分析是最重要的项目管理报表。
    通过项目的财务现金流分析,可以计算项目的财务内部收益率、财务净现值、投资回收期等指标,从而对项目的决策做出判断。
      (1)财务内部收益率(FIRR):它是指项目在整个计算期内各年净现金流量现值累计为零时的折现率,是评价项目盈利能力的相对指标。该指标可根据财务现金流量表中净现金流量,用插差法计算,也可以直接利用微软Excel软件提供的财务内部收益率函数计算,计算得到的项目财务内部收益率与行业基准收益率(Ic)比较,如果FIRR>Ic,即认为项目盈利能力能够满足要求。
    (2)财务净现值(FNPV):它是指项目按基准收益率Ic将各年净现金流量折现到建设起点的现值之和。它是评价项目盈利能力的绝对指标,反映项目在满足基准收益率要求的盈利之外所获得的超额盈利的现值。也可直接利用微软Excel软件提供的财务净现值函数计算。若得到的FNPV≥0,表明项目的盈利能力达到或超过基准计算的盈利水平,项目可接受。
    (3)投资回收期(Pt):它是反映项目真实偿债能力的重要指标,是指以项目的净收益抵偿项目全部投资所需要的时间。在现金流量表中,是累计现金流量由负

    项目的不确定性分析
    根据拟建项目的具体情况,有选择性地进行盈亏平衡分析、敏感性分析和概率分析等。
    (1)盈亏平衡分析:它是根据项目正常生产年份的产品产量(销售量)、固定成本、可变成本、税金等,研究建设项目产量、成本、利润之间变化与平衡关系的方法。当项目的收益与成本相等时,即为盈亏平衡点(BEP)。
    (2)敏感性分析:它是研究项目的产品售价、产量、经营成本、投资、建设期等发生变化时,项目财务评价指标(如财务内部收益率)的预期值发生变化的程度。通过敏感分析,可以找出项目的最敏感因素,使决策者能了解项目建设中可能遇到的风险,提高决策的准确性和可靠性。一般以某因素的曲线斜率的绝对值大小来比较。财务内部收益率对建设投资和商品房销售价格的变化都较为敏感。相比之下,财务内部收益率对建设投资的变化更为敏感。
    (3)概率分析:它是通过概率预测不确定性因素和风险因素对项目经济评价指标的定量影响。一般是计算项目评价指标,如项目财务净现值的期望值大于或等于零时的累计概率。累计概率值越大,项目承担的风险越小。
    项目挣值管理
    挣值管理(EarnedValueManagement,EMV)是综合了项目范围、进度计划和资源,测量项目绩效的一种方法。它比较计划工作量、实际挣得多少与实际花费成本,以决定成本和进度绩效是否符合原定计划。 要进行挣值管理,必须熟悉与挣值管理密切相关的计划成本(PV)、挣值(EV)和实际成本(AC)之间的相互关系,以及完工预算(BAC)、完工估算(EAC)和完工尚需估算(ETC)之间相互关系。挣值管理也离不开偏差管理。偏差=计划-实际。当成本偏差(CV)>0,表明成本节约;反之,当CV<0,表明成本超支。当进度偏差(SV)>0,表明进度超前;反之,当SV<0,表明进度滞后。特别注意的是,这是根据PMI的偏差含义做出的推断,与我国的工程监理投资控制中的偏差定义正好方向相反。
    项目范围管理
     做过项目的人可能都会有这样的经历:一个项目做了很久,感觉总是做不完,就像一个“无底洞”。用户总是有新的需求要项目开发方来做,就像用户在“漫天要价”,而开发方在“就地还钱”。实际上,这里涉及到一个“范围管理”的概念。项目中哪些该做,哪些不该做,做到什么程度,都是由“范围管理”来决定的。那么,到底什么是“范围管理”,请跟我们一块来揭开谜底。
    几年前,我和一位同事在外地共同参与一个软件项目的开发。项目本身并不算很大,开始的需求调研进行了很长时间,期间不但几乎拜访了所有部门,还与用户反复讨论,征求意见,需求文档几易其稿。即便这样仍然有许多不确定因素,搞得人心烦意乱。当时我牢骚很多,总觉得又花时间似乎还没真正做事。我的同事经验比较丰富,他给我说了一个他自己的亲身经历。那时候他在深圳参与一个证券项目,当时软件开发管理非常不规范,基本上是了解需求后就编程序,根本没有太多的交流,需求文档就更没有了。系统开发出以后,用户不断提出新需求。每天追着开发人员解决问题,项目实际是一个无底洞,没完没了地往下做,按他的说法是项目成员“肥的拖瘦,瘦的拖死”,实在做不下去只能跑了。
      这个故事刚听起来感觉非常可笑,当我自己真正做项目负责人时才体会到这其实是一个项目范围管理的问题。上面提到我所参与的项目中花费大量时间用于需求调研也是为了确定项目范围。作为一个合格的项目经理,切记要准确控制好项目范围。孙子兵法中提到“知己知彼,百战不殆”,在一个项目中我们应该知道对方需要什么,自己要做什么,这是项目成功的基础所在。那么,首先要明确的是项目范围管理中的范围是如何定义的?
    什么是范围?
    我们知道项目是为完成产品或服务所做的一次性努力。因此在这里,范围的概念包含两方面,一个是产品范围,即产品或服务所包含的特征或功能,另一个是项目范围,即为交付具有规定特征和功能的产品或服务所必须完成的工作。在确定范围时首先要确定最终产生的是什么,它具有哪些可清晰界定的特性。要注意的是特性必须要清晰,以认可的形式表达出来,比如文字、图表或某种标准,能被项目参与人理解,绝不能含含糊糊、模棱两可,在此基础之上才能进一步明确需要做什么工作才能产生所需要的产品。也就是说产品范围决定项目范围。
    举例说明可能会更好理解一些。假设你在一家培训公司做培训专员,负责组织一次PMP(美国项目管理专业人员认证)考前培训。那么我们完全可以把这项工作当成一个项目来管理,如何确定产品范围和项目范围呢?培训产生的不是有形的产品,而是无形的服务。组织PMP考前培训的目的是讲授项目管理体系基础知识,提高学员的项目管理理论水平,为参加PMP考试做准备,这就是产品范围。如果学员突然提出想获得如何提高企业核心竞争力的知识,很明显此内容不在本项目的产品范围之内。有了明确的产品范围,接下来就可以确定为达到这个目的需要做哪些工作,即项目范围。首先要聘请知名的项目管理权威专家,拟订授课内容,根据授课内容准备学员教材,联络舒适的培训地点,安排好学员食宿。开始培训也并非万事大吉,每天都要与学员交流,听取他们的意见并反馈给老师,甚至学员的日常起居都要过问。由于PMP考试是英文试题,而模拟习题都是中文,假设某些学员希望讲解一些英文题以避免翻译带来的理解偏差,这时老师就要多讲一些内容,产品范围有所扩大,但从总的培训目标看是合理的。
    如何做好范围管理?
    范围管理保证项目包含了所有要做的工作而且只包含要求的工作,它主要涉及定义并控制哪些是项目范畴内的,哪些不是。范围管理的基本内容包括:项目启动、范围计划编制、范围核实、范围变更控制等等。以下所讨论的是其中比较重要的部分。

    值变为0的时点。 投资回收期越短,表明项目盈利能力和抗风险能力越强。

    1.编制范围计划
    “公欲善其事,必先利其器”。一个项目经理要想真正管理好项目范围,没有必要的技术和方法是肯定不行的。国外曾经有人对项目失败原因进行调查,其中计划被放到了首位,可见它在项目管理中的重要性。
    我们这里首先强调的就是周密地做好范围计划编制。范围计划编制是将产生项目产品所需进行的项目工作(项目范围)渐进明细和归档的过程。做范围计划编制工作是需要参考很多信息的,比如产品描述,首先要清楚最终产品的定义才能规划要做的工作,项目章程(典型的例子是合同)也是非常主要的依据,通常它对项目范围已经有了粗线条的约定,范围计划在此基础上进一步深入和细化。
    范围计划中究竟应该包含哪些内容呢?不同的计划详尽程度自然不一样,其中范围说明和范围管理计划必须包含在内。范围说明在项目参与人之间确认或建立了一个项目范围的共识,作为未来项目决策的文档基准。范围说明中至少要说明项目论证、项目产品、项目可交付成果和项目目标。项目论证是商家的既定目标,要为估算未来的得失提供基础;项目产品是产品说明的简要概况;项目可交付成果一般要列一个子产品级别概括表,如:为一个软件开发项目设置的主要可交付成果可能包括程序代码、工作手册、人机交互学习程序等。任何没有明确要求的结果,都意味着它在项目可交付成果之外;项目目标是要考虑到项目的成功性,至少要包括成本、进度表和质量检测。项目目标应该有标志(如:成本、单位)和绝对的或相对的价值(如:少于150万美元等)。不可量化的目标(如:“客户的满意程度”)要承担很高的风险。范围管理计划是描述项目范围如何进行管理,项目范围怎样变化才能与项目要求相一致等问题的。它也应该包括一个对项目范围预期的稳定而进行的评估(比如:怎样变化、变化频率如何及变化了多少)。范围管理计划也应该包括对变化范围怎样确定,变化应归为哪一类(当产品特征仍在被详细描述的时候,做到这点特别困难,但绝对必要)等问题的清楚描述。
    2.范围分解
    计划明确了,然而该做哪些事情似乎还是一把抓,因为完成项目本身是一个复杂的过程,必须采取分解的手段把主要的可交付成果分成更容易管理的单元才能一目了然,最终得出项目的工作分解结构(WBS)。恰当的范围定义对项目成功十分关键,当范围定义不明确时,变更就不可避免地出现,很可能造成返工、延长工期、降低团队士气等一系列不利的后果。
    比较常用的方式是以项目进度为依据划分WBS,第一层是大的项目成果框架,每层下面再把工作分解,这种方式的优点是结合进度划分直观,时间感强,评审中容易发现遗漏或多出的部分,也更容易被大多数人理解。Microsoft的项目管理工具Project就可以自动为各个层次的任务编码。
    3.范围变更
    一个项目的范围计划可能制订的非常好,但是想不出现任何改变几乎是不可能的。因此对变更的管理是项目经理必备的素质之一。变并不糟糕,糟糕的是缺乏规范的变更管理过程。范围变更的原因是多方面的,比如用户要求增加产品功能、环保问题导致设计方案修改而增加施工内容。项目经理在管理过程中必须通过监督绩效报告、当前进展情况等来分析和预测可能出现的范围变更,在发生变更时遵循规范的变更程序来管理变更。我们强烈建议企业的项目管理体系中包含一套严格、高效、实用的变更程序,它对管好项目至关重要。
    项目时间管理
    “按时、保质地完成项目”大概是每一位项目经理最希望做到的。但工期托延的情况却时常发生。因而合理地安排项目时间是项目管理中一项关键内容,它的目的是保证按时完成项目、合理分配资源、发挥最佳工作效率。它的主要工作包括定义项目活动、任务、活动排序、每项活动的合理工期估算、制定项目完整的进度计划、资源共享分配、监控项目进度等内容。
    磨刀不误砍柴功:时间管理工作开始以前应该先完成项目管理工作中的范围管理部分。如果只图节省时间,把这些前期工作省略,后面的工作必然会走弯路,反而会耽误时间。项目一开始首先要有明确项目目标、可交付产品的范围定义文档和项目的工作分解结构(WBS)。由于一些是明显的、项目所必须的工作,而另一些则具有一定的隐蔽性,所以要以经验为基础,列出完整的完成项目所必需的工作,同时要有专家审定过程,以此为基础才能制定出可行的项目时间计划,进行合理的时间管理。
    怎样把时间“分解”?
    1、项目活动定义:将项目工作分解为更小、更易管理的工作包也叫活动或任务,这些小的活动应该是能够保障完成交付产品的可实施的详细任务。在项目实施中,要将所有活动列成一个明确的活动清单,并且让项目团队的每一个成员能够清楚有多少工作需要处理。活动清单应该采取文档形式,以便于项目其他过程的使用和管理。当然,随着项目活动分解的深入和细化,工作分解结构(WBS)可能会需要修改,这也会影响项目的其他部分。例如成本估算,在更详尽地考虑了活动后,成本可能会有所增加,因此完成活动定义后,要更新项目工作分解结构上的内容。
    2、活动排序:在产品描述、活动清单的基础上,要找出项目活动之间的依赖关系和特殊领域的依赖关系、工作顺序。在这里,既要考虑团队内部希望的特殊顺序和优先逻辑关系,也要考虑内部与外部、外部与外部的各种依赖关系以及为完成项目所要做的一些相关工作,例如在最终的硬件环境中进行软件测试等工作。设立项目里程碑是排序工作中很重要的一部分。里程碑是项目中关键的事件及关键的目标时间,是项目成功的重要因素。里程碑事件是确保完成项目需求的活动序列中不可或缺的一部分。比如在开发项目中可以将需求的最终确认、产品移交等关键任务作为项目的里程碑。在进行项目活动关系的定义时一般采用优先图示法、箭线图示法、条件图示法、网络模板这4种方法,最终形成一套项目网络图。其中比较常用的方法是优先图示法,也称为单代号网络图法。
    3、活动工期估算
    项目工期估算是根据项目范围、资源状况计划列出项目活动所需要的工期。估算的工期应该现实、有效并能保证质量。所以在估算工期时要充分考虑活动清单、合理的资源需求、人员的能力因素以及环境因素对项目工期的影响。在对每项活动的工期估算中应充分考虑风险因素对工期的影响。项目工期估算完成后,可以得到量化的工期估算数据,将其文档化,同时完善并更新活动清单。
    一般说来,工期估算可采取以下几种方式:1)专家评审形式。由有经验、有能力的人员进行分析和评估。2)模拟估算。使用以前类似的活动作为未来活动工期的估算基础,计算评估工期。3)定量型的基础工期。当产品可以用定量标准计算工期时,则采用计量单位为基础数据整体估算。4)保留时间。工期估算中预留一定比例作为冗余时间以应付项目风险。随着项目进展,冗余时间可以逐步减少。4、安排进度表
    项目的进度计划意味着明确定义项目活动的开始和结束日期,这是一个反复确认的过程。进度表的确定应根据项目网络图、估算的活动工期、资源需求、资源共享情况、项目执行的工作日历、进度限制、最早和最晚时间、风险管理计划、活动特征等统一考虑。进度限制即根据活动排序考虑如何定义活动之间的进度关系。一般有两种形式:一种是加强日期形式,以活动之间前后关系限制活动的进度,如一项活动不早于某活动的开始或不晚于某活动的结束;另一种是关键事件或主要里程碑形式,以定义为里程碑的事件作为要求的时间进度的决定性因素,制定相应时间计划。在制定项目进度表时,先以数学分析的方法计算每个活动最早开始和结束时间与最迟开始和结束日期得出时间进度网络图,再通过资源因素、活动时间和可冗余因素调整活动时间,最终形成最佳活动进度表。
    关键路径法(CPM)是时间管理中很实用的一种方法,其工作原理是:为每个最小任务单位计算工期、定义最早开始和结束日期、最迟开始和结束日期、按照活动的关系形成顺序的网络逻辑图,找出必须的最长的路径,即为关键路径。
    时间压缩是指针对关键路径进行优化,结合成本因素、资源因素、工作时间因素、活动的可行进度因素对整个计划进行调整,直到关键路径所用的时间不能再压缩为止,得到最佳时间进度计划。
    5、进度控制:进度控制主要是监督进度的执行状况,及时发现和纠正偏差、错误。在控制中要考虑影响项目进度变化的因素、项目进度变更对其他部分的影响因素、进度表变更时应采取的实际措施。在前几期中曾经对此进行过探讨,在此不再赘述。
    巧用工具来帮忙:目前项目管理软件正被广泛地应用于项目管理工作中,尤其是它清晰的表达方式,在项目时间管理上更显得方便、灵活、高效。在管理软件中输入活动列表、估算的活动工期、活动之间的逻辑关系、参与活动的人力资源、成本,项目管理软件可以自动进行数学计算、平衡资源分配、成本计算,并可迅速地解决进度交叉问题,也可以打印显示出进度表。项目管理软件除了具备项目进度制定功能外还具有较强的项目执行记录、跟踪项目计划、实际完成情况记录的能力,并能及时给出实际和潜在的影响分析。

     

     

    展开全文
  • 科技研究及顾问机构 Gartner 公布 2013年十大策略性技术与趋势; Gartner 所定义的策略性技术是在未来三年可能对企业造成重大影响的技术,而构成重大影响的因素包含为 IT技术或业务带来巨变的可能、主要投资需求,...
  • 刘淼表示,从联想自身的经历来看,中国企业在未来很长一段时间根据业务要求而采用公共云、私有云、混合云或传统高可用IT并存的混合环境。 数据亦是表现如此。根据市场调研公司数据显示,在2018年,有71%的企业...
  • 腾讯后台面经大全(整合版)

    万次阅读 多人点赞 2018-07-24 16:25:16
    类似hdfs,结合块存储的速度和文件存储的易用。并且支持并发读写 ,异常, 请求失败怎么办, 重发 加锁失败怎么办, 轮询cas 回滚失败怎么办, 记录日志,重试 加密算法, 1 md5,sha...
  • 王者荣耀背景故事整合

    千次阅读 2020-11-12 16:41:27
    王者荣耀背景故事整合 青木水流年 青木水流年 欢迎大家与我讨论,我尊重人的差异。 508 人赞同了该文章 自己整理的王者荣耀全英雄全背景故事大全,不一定全部正确,看看就好,不要搬运 有错误可以告诉我,很喜欢...
  • 最后,作者还介绍了如何将书中提出的策略有效地整合到你的投资过程中,以创造优秀的选股模型,构建自己的量化模型和投资组合,并实现超越市场的收益。本书中概括出的量化方法可以为定性投资者提供一个被证实的设计...
  • Web答辩问题整合

    千次阅读 2018-05-24 22:13:34
    Web答辩问题整合1 1. css和div 开发的优势? A、显示和内容实现分离 B、有利于搜索引擎搜索 C、有利于维护和程序的扩展 2. 谈谈页面间的参数传递有哪些方式 ? A、通过作用域对象session、request 的...
  • 与此同时,由于微信用户群体的庞大,也吸引了大量的内容生产者在微信公共平台创造内容,以获取用户关注、点赞、收藏等。微信内的内容推荐产品:看一看应运而生。 基于微信通过多年的用户沉淀,积累得到的大量...
  • 本人产品新人一枚,近期闲来没事,于是查阅相关资料,进行了整合,现对“今日头条”这款App的发展困境与未来发展策略说说自己的看法,如有不对,敬请批评。&nbsp; &nbsp; &nbsp; &nbsp;“今日头条...
  • 机器学习多因子策略

    千次阅读 2019-04-29 13:58:50
    在二级市场的量化策略中,多因子策略称得上是最早被创造但同时也是变化最多的投资策略之一,好的因子意味着长期稳定的收入,因此能够发现一个好的因子是每位宽客的一致愿望。多因子策略理解起来并不复杂,实现起来却...
  • 识别新机会并基于洞察力实施有效策略可以为企业提供竞争的市场优势和长期稳定。 我们谈论 数据建模 数据分析 数据挖掘 数据库,rdb,nosql 数据仓库 tableau,powerBI ETL 销售人员,SAP,ERP Python,R,...
  • 针对风险篇中提到的小贷公司存在的风险,本文总结了相关的应对策略,帮助小贷公司实现稳定健康发展。 赋予明确的身份性质 小贷公司应明确定位,监管部门应尽快修订政策法规,赋予小贷公司明确合法的金融机构身份,...
  • 全面了解风控策略体系

    万次阅读 多人点赞 2020-06-28 14:09:48
    模型和策略的开发是一个系统工程,这其中需要有业务经验、统计理论、算法运用、和数据认知,是一个不断反思,不断积累经验的过程。沙滩上建不起摩天大楼。扎扎实实的基本功永远有价值,永远不会过时。
  • 战略策略

    千次阅读 2006-10-24 22:46:00
    01-001 战略策划 所谓的战略就是针对这三个方面所作出的艰难抉择:哪些是我们所应该注重的客户,哪些客户已经被明确地列为非服务对象;... 实用的策划方法: 战略思考:退出日常决策,寻找大图景
  • yolo5的改进策略

    万次阅读 多人点赞 2020-10-26 14:18:29
    据悉YOLO V5的作者Glen Jocher正是Mosaic Augmentation的创造者,故认为YOLO V4性能巨大提升很大程度是马赛克数据增强的功劳,也许你不服,但他在YOLO V4出来后的仅仅两个月便推出YOLO V5,不可否认的是马赛克数据...
  • 移动网站在功能、第三方服务整合及分析工作方面与我们所熟悉的桌面网站也比较相近,这就保证了企业能够利用同一套网络策略轻松管理两种不同的业务平台。 而在营销机遇方面,移动站点对营收的推动作用则更为强劲...
  • 个人应对冲突的五种策略

    千次阅读 2021-04-21 15:15:26
    2、回避策略可以避免冲突扩大化,但当冲突双方相互依赖很强时,回避则会影响工作,降低绩效,不利于冲突的解决。这种说法是否正确? A. 正确 B. 错误 标准答案:A 3、以下哪项不属于回避行为策略适用的场合? A. ...
  • 它为有抱负的公司提供了一种策略性地管理客户关系的模型,并表明良好的CRM可以促进业务增长,客户保留和收入创造。 该研究通过实施CRM来获得优于竞争对手的竞争优势,这对学者和从业人员均具有重要意义。 研究得出...
  • 第二章 沉默策略 第一节 沉默策略的应用背景  沉默,会是个有效应对危机的手段吗?想必大多数人不会同意。尤其那些受过西方危机...无数成功的案例似乎印证了这一思路的正确。在一件事情被质疑的时候,出来说话的
  • 中国基础软件:并购整合寻求突围

    千次阅读 2011-10-30 15:26:24
    中国基础软件:并购整合寻求突围 (2010-01-10 19:47:26) 转载 标签: it 中国基础软件 中标软件 ibm 普华软件 亚信 海辉 普元 红旗2000    《中国基础软件:并购整合 寻求...
  • 处于完全竞争、买方市场格局下的电商企业和用户的关系是密不可分的,而用户关系的核心理念是“以用户...B2C电商企业要制订好用户体验成本策略,首先必须要设法去找到影响用户体验的因素有多少,然后将其落实或者分解到
  • 系统集成策略及方案

    千次阅读 2009-09-02 13:28:00
    系统集成策略及运作方案一、什么是系统集成? 系统集成,从字面上讲就是将各功能部分综合、整合为统一的系统。然而系统集成的应用含义远不止此。系统集成包含以下五大要素: 1.客户行业知识 要求对客户所在行业...
  • 中英文数据库检索策略对比

    千次阅读 2019-07-02 20:31:05
    知网,是国家知识基础设施(National Knowledge Infrastructure,NKI)的概念,由世界银行于1998年提出。... CNKI工程的具体目标:一是大规模集成整合知识信息资源,整体提高资源的综合和增值利用价值;二是建设...
  • 谷歌如何成功整合创业公司

    千次阅读 2012-09-21 09:02:53
    而谷歌创造了这样一个生态环境:允许收购对象的创始人加盟之后,仍能保持对原有业务的较大控制权,并能够从谷歌其他部门挑选相关人才,从而加强该部门的实力,如此就能保持该业务在谷歌这家大型公司之中的应有活力。...
  • 网络营销策略

    千次阅读 2008-01-09 11:11:00
     ·整合策略 ·传播策略  ·互动策略 ·数据库策略  ·附加价值策略 ·品牌策略  ·虚拟社区策略 ·市场力策略  ·离线策略 ·价格/销售比策略  ·“鼠标+水泥”策略 ·利润点策略  ·上市...
  • 如果你身处技术界, 你不会怀疑API对业务的显著影响,否则,可能有点难以理解为...在商业领域API每天都在快速增长,本章节的论点应该能帮助不在科技界的人了解API的重要,同时提供一些论据使其相信采用API策略的价值。
  • 第四章 情感公关策略 第一节 情感公关的应用原则 现代心理学研究表明,人的行为是由动机支配的,动机是由需要引起的,需要是以个性心理现象为基础的。 需要,就是人们对某种目标的渴求或欲望,也就是一种心理的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,967
精华内容 7,586
关键字:

创造性整合策略