2005年11月19日

此代码下载后,可以直接运行
手游戏程序运行在NOKIA S60上面,
MIDP 1.0 采用socket网络联接,测试机为NOKIA 7650,索爱T610 P800系列等

版权所有中国数码娱乐网-追梦坊,未经许可不得商用。

下载地址

      代码是使用JB写的,存储方式是UTF-8,因此使用ECLIPSE等IDE打开源程序会出现乱码,这时只需要使用UltraEdit等工具进行UTF-8=>ASCII转换一下即可。代码中有一些地方如读取图片使代码复杂化,这是考虑到将来图片及其它资源可能会采用加密的方式存储,使一般用户在PC上无法进行还原而设计的。

(源地址在http://www.ismyway.com/down.asp?id=23)

2005年11月18日

      这是一篇写给初学者看的文章。在前一段时间的招聘软件设计师的过程中,我对每一个看似初学者的人都会问这个问题,“您觉得平台相关性和平台无关性哪个更好一些”,呵呵(偷笑),其实这是唬人的,多数回答者都会顺着出题者假装的思路回答“我个人认为平台无关性比较好”,可是只要有点软件设计经验或是对这个问题有所思考的人都知道其实这个问题不只两个标准答案。

      关于平台无关性,我不想说什么,说什么也没用。大量软件设计或软件架构以此来标榜自己的优秀和出众,其实这没什么,因为大部分平台无关性的工作不是由你来做的。如果你正在写一个Java程序,并依照Sun的100% Pure Java的要求来做,那么应该就是平台无关的,如果你正在写一个Eclipse应用或直接用SWT/JFace组合来写应用程序,那么也是平台无关的,如果你喜欢C/C++,并在用wxWidget写应用程序,那么也是平台无关的,如果你实在是很牛,在依照OSGi的规范写代码,那么ok,你的程序已经可以从微设备到大型机统统可以用。

      平台无关也是一个相对的概念,在多个操作系统上运行可以称为平台无关的,以往在多个不同品种的CPU上运行可以称为平台无关性,还有一件搞笑的事情,某个公司出了一套系统,可以在Java和.NET两个平台上运行,但却只能依赖于Windows系统(因而只能依赖于x86芯片),居然还可以称平台无关性,可见这个概念有多么混乱。我个人评价是不是平台无关的仅有一个标准,那就是——你有没有为平台无关做出贡献!如果你为了能在多个平台上跑出你的代码而做了很多工作,那么你就可以称自己为平台无关的,而如果你仅仅依赖SWT工作,那就不能称自己为平台无关的。不过话又说回来,如果你把SWT包含在自己的软件中,并为此出了该软件的多个版本(每个SWT的版本是一个发行包),那么你也可以称此为平台无关的,虽然这个贡献并非出自你手。

      平台无关也不见得总是个优点,很多系统为了坚持平台无关而牺牲了很多特性,或不必要的提高了成本。比如前几年很多系统原意搞WEB界面,导致了很多易用性方面的问题,Cooper说Web使人机交互技术倒退了10年,的确如此。我还读过一段源码,大概是一个单机版个人软件的源码,令人惊奇的是,该软件很小,却把很多笔墨花在了业务对象和JDBC访问层之间的一个“抽象数据存储层”,理由是便于将来移至非JDBC平台,天哪!会有多少用户有机会使用不支持 JDBC的数据库??!!这种设计和下面一种设计是一样的效用:“为了让这段代码支持非OO语境,我决定整个软件只用一个类!”。这种追求可以用四个字来表述就是:“过渡设计”或者“吃饱撑着”!

      相反,这个世界上有80%的软件是平台相关的,这没什么不能理解的。就像“民主和专制的TCO哪个高”这个问题的答案一样,如果我现在要招聘的是部门经理或副总裁,我很可能会问这个问题。事实上是,在整个人类的发展历程中,总成本最低(即总效率最高)的几个“社会时期”,几乎全是专制,但如果你不假思索,你的答案一定是民主!当然,平台相关也是相对的概念。

      说到这里,有兴趣的读者可能会说结合二者是最好的选择,我不喜欢这种说法,因为太辨证了,我喜欢的是首先考虑依赖于哪个框架,再找寻该框架的平台无关性,如果没有必要,尽量不要为平台无关(实际上是一种优先级非常低的非功能性需求)做任何事情,但如果有必要且成本允许,再做少许考虑,最好还是能够重用开源世界的产品。

      仍以OSGi为例,这个例子很好,它对Java语言本身(还不是面向对象的公共语义)非常依赖,直接依赖至VM的spec,当然也写了些代码以避开 ClassLoader的个性,即使如此,OSGi事实上实现了从微设备到大型机全套支持,借助Java的平台无关性,既没有易用性、性能和成本方面的丝毫损失,也为上层平台提供了平台无关的环境。同样,为Mac OSX设计的很多非常优秀的软件都没有考虑平台无关的问题,而是用在PC上再做一套的方式来解决,这些都是值得思考和借鉴的解决方案。

转自Brian Sun @ 爬树的泡泡[http://www.briansun.com]

2005年11月16日

(作者:俞良松 2002年09月26日 本文选自:开放系统世界)

      Struts是源代码开放的企业级Web应用开发框架,它的设计目的是从整体上减轻构造企业Web应用的负担。本文通过一个Struts应用的实例,帮助你迅速掌握Struts。

  Struts是在Jakarta项目下开发的源代码开放软件,由一系列的框架类、辅助类和定制的JSP标记库构成,定位在基于Model 2设计模式的J2EE应用开发。Model 2体系是MVC(Model-View-Controller)体系的一种应用。在MVC体系中,数据模型(Model)、表现逻辑(View)和控制逻辑(Controller)是分离的组件,但它们可以互相通信。Struts力图从整体上减轻构造企业级Web应用的负担,并提供国际化和数据库连接池支持。

  Struts体系可以看成两个相对独立的部分:第一个部分是Struts API,用于编写支持Struts的应用组件;第二部分是Struts的JSP标记库,由html、bean、logic和template四个标记组成。Struts的两个部分有着各自不同的用户。对于规模较小的项目,同一个用户可能同时使用这两个部分;但对于规模较大的项目,通常开发者使用API组件,而负责HTML页面布局的人使用标记库。

  Struts的设计目标是为Model 2 Web应用开发提供一个强大的框架。同时,Struts还包含了一些实用组件,例如Digest,但这些组件并不从属于上面提到的两个部分。

  Struts应用的体系结构

  对于从传统编程环境转入Web开发的人来说,Web编程中令人很不习惯的一个特点是缺乏“程序”。传统的应用总是有主入口点、流程控制和出口点。但在Web网站上,用户可能从任何地方进入,按照一种完全随机的次序访问各个页面,甚至可能跳过多个页面,也可能在一、两个小时内毫无动静。这是HTTP访问的基本特征,无论是Struts还是其他Web编程框架,都无法改变这一点。然而,Struts能够隐藏Web访问固有的“混乱”,帮助开发者建立起清晰和明确的秩序和规则。

  在Struts应用中,有一个称为ActionServlet的主调度程序(或称为分配器),如图1所示。不过,并非所有的请求都必须通过ActionServlet。用户的请求目标可以是非Struts的页面,也可以是那些使用了Struts标记库但不使用Struts请求分配服务的页面。这正是Struts体系的优点之一:按需使用。许多编程框架要求你要么不用,要么全部使用,而且一旦你决定使用,以后要悔改从前的错误就会付出高昂的代价。Struts按需使用的优点与这类系统形成了强烈对比。

  

  图1 Struts框架中的请求处理

  Struts应用由下面这些基本模块构成:

  1.配置信息;

  2.Servlet,主要是Struts的ActionServlet;

  3.动作类(Action),执行逻辑和控制(请求分配)功能,它们由ActionServlet调用;

  4.JSP页面(属于View),常常通过动作类分派;

  5.JSP标记库,根据需要使用;

  6.各种形式的JavaBean,包括用户定义的JavaBean。

  典型的Struts应用要用到三种配置文件:web.xml、struts-config.xml和可选的应用资源文件。

  web.xml是Web应用的标准配置文件,是所有J2EE Web应用必需的组成部分。应用服务器通过该配置文件把URL映射到Servlet和JSP,通过该配置文件为Servlet和JSP指定启动参数。为Struts应用提供的基本web.xml文件很简单,真正必需的只有一个主ActionServlet定义,以及一个确保Struts请求传递到ActionServlet的映射。按照惯例,以“.do”结尾的URL都是Struts请求,例如/login.do。应用服务器利用web.xml文件中的映射,把该请求传递给ActionServlet。接着,ActionServlet决定如何分配该请求。ActionServlet的决定依据是struts-config.xml中定义的规则,和/或是通过ActionServlet派生类额外定义的分配逻辑。

  struts-config.xml称为Struts配置文件。Struts应用是一个依靠struts-config.xml文件把组件连接起来的网络。struts-config.xml文件为Web应用的组件定义了逻辑名称,也定义了它们在Struts框架下的属性和关系,就像web.xml文件在Web应用框架之内定义组件一样。struts-config.xml文件包含了与Struts框架有关的应用信息,这些信息分四个类:

  1.数据源信息,它是可选的。在这里可以指定一个或者多个JDBC数据源,使得数据库定义信息集中化。对于数据库访问,Struts还有一个额外的优点,即支持基本的数据库连接池功能。

  2. Form Bean是JavaBean的一种特殊类型,它简化了Web表单的处理。

  3. Global Forwards是全局性的转发定义信息。Struts动作按照一种“请求—转发”机制运行。为了最大限度地分  离动作模块与转发目标,这里使用了一种映射机制,允许通过同义词引用转发目标。一些目标页面可能被多个动作类引用,例如登录页面,因此可以在全局转发定义部分把逻辑目标页面映射到物理目标页面,避免把这部分信息加入到动作定义部分。

  4. Actions定义了Struts应用体系的请求分配信息,它们是核心分配器的补充定义,负责处理各种具体的请求类型。

一个简单的应用

  基于Struts的Web应用和普通Web应用有着许多同样的要求,但Struts应用也有自己特殊的需求。一个可部署的Web应用应该可组织和构成一个WAR文件。WAR文件是带有图2所示目录结构的JAR包。对于Struts Web应用来说,Web-INF目录下还要加上一些额外的文件,例如struts-confg.xml文件和标记库描述器(TLD)文件。注意:应用的资源应该放入应用的类路径下,也就是Web-INF/lib目录或Web-INF/class目录下的JAR包内。对于大多数简单的Struts页面,我们只用到Struts标记库,而按照MVC的术语就不需要涉及Model和Controller部分,只涉及View。请看图3所示的主页例子。虽然这个页面没有表单,但Struts仍能够在设计这类页面时提供帮助。

  

  图2 Struts应用的目录结构

  

  图3 一个简单的View

  要管理会话,最简单的途径是使用Cookie。会话标识符被传递到客户端之后,客户端把它保存到Cookie,以后的每次请求就把Cookie发送到服务器。然而,和其他的许多Web解决方案一样,Cookie方案也不是万能的,因为一些用户可能不信任Cookie,关闭浏览器的Cookie支持。由于这种情况,URL改写技术就出现了。使用URL改写技术时,整个网站的所有URL后面都将加上会话标识符。虽然这个方案不像采用Cookie方案那样简单、稳固,但它确实行得通。URL改写技术的不稳固是有两方面的原因。首先,和Cookie不同,URL没有过期时间,如果一个带有会话标识的URL被截取后又重新在以后的访问中使用,那么这种URL不会很有用,因为会话一般在一定的时间后会被作废。其次,如果有一个URL链接的后面没有带上会话标识符,整个链都会中断,客户程序无法再次获取会话标识符,除非它备份了带有会话标识符的URL访问历史。

Servlet能够只通过一次方法调用完成URL改写。从技术上讲,JSP也一样能够办到这一点,但一个好的JSP页面应该不包含Java代码,或包含尽量少的Java代码。为此,Struts提供了一个链接标记。本例使用了该标记来维持客户端和服务器之间的会话状态信息。

  绑定View、Model和Controller

  前面的简单页面不需要Struts分配器,因为它只有简单的链接。图4显示了一个比较复杂的“类别”页面。它列举出了数据库中的类别条目,并将这些条目分别链接到对应的编辑页面。为显示这个页面,我们就要用到Struts的ActionServlet分配机制。

  

  图4 类别页面

  在web.xml文件中,放入一项表示所有以“.do”结尾的URL请求必须发送给Struts分配器的声明。这里的分配器可以是org.apache.struts.action.ActionServlet或其扩展类。Struts分配器在启动时读取struts-config.xml文件,并构造出一个动作映射图。本例指定了一个名为ShowCategories的动作类,来处理“ShowCategories”动作。可以看出,Struts应用的基本工作模式是:主分配器调用一个动作分配器,动作分配器确定或构造出Model部分(一个JavaBean或其它Java对象),并把它提供给View(通常是一个JSP页面)。

  本例使用Bean的情况稍微有点复杂,它有多个数据项,因此我们不是使用单个提供数据的Bean,而是要生成一组Bean。遗憾的是,JSP页面以HTML为基础,HTML没有提供循环或其他控制逻辑。不过,Struts的logic:iterate允许对数组进行迭代操作,如下面的代码片断所示:

<table> <logic:iterate id="category" type="com.strutsdemo.Category" name="<%= Constants.CATALOG_CATEGORIES %>" scope="application"> <tr> <td> <html:link page="/editCategory.do" name="category" property="mapping"> 编辑 </html:link> <html:link page="/removeCategory.do" name="category" property="mapping"> 删除 </html:link> <bean:write name="category" property="category"/> </td> </tr> </logic:iterate> </table>



  上面讨论了Model 2体系的Model和View部分,下面来看看Controller部分。Struts体系有一个主控制器,即ActionServlet。ActionServlet负责选择和调用合适的动作控制器—即org.apache.struts.action.Action的扩展类。动作控制器实现了process()方法。process()方法分析从URL请求传入的每一个参数,执行必要的业务逻辑,并返回一个指定了调用链中下一个链接的动作(通常是View)。在本例中,我们想要从数据库提取数据,创建管理这些数据的JavaBean,把多个JavaBean整理成一个数组,再把数组保存到请求的上下文,从而使得作为View的JSP页面能够方便地进行页面布局

为保证上述操作顺利进行,在struts-config.xml文件中加入声明,指定由哪一个动作处理器来处理指定的动作:

<action path="/showCategories" scope="request" type="com.strutsdemo.ShowCategoriesAction" unknown="false" validate="false"> <forward name="viewCategories" path="/       ShowCategories.jsp"/> </action>



  表单处理

  表单处理过程充分体现出Struts的优势。在Web应用中,大部分复杂的HTML处理任务都涉及到表单。表单编辑过程具有类似图5所示的请求或应答结构。更新操作的过程与创建操作的过程相似,但对于更新操作来说,“创建Model”这一步骤变成“装入Model”,而“保存Model”变成了“更新Model”。请注意Web应用的特点:操作过程随时可能中止,这既可能是因为用户通过显式的动作取消了当前的操作,也可能是因为用户没有提交表单,例如用户跳转到了一个不是用来处理当前表单的URL。

  

  图5 表单处理流程

  表单编辑过程分三个阶段:这里分别称之为准备(Preparation)、表现(Presentation)和存储(Preservation)。准备和存储阶段都属于Struts动作,而表现阶段主要是客户端的活动。表1显示了该过程中涉及的各种部件:

  表单Bean(Form Bean)是一种特殊的JavaBean类型,它简化了表单处理。Form Bean从org.apache.struts. action.ActionForm类扩展而来。Form Bean有几个有用的特点,例如,通过reset()方法可以把Bean的属性设置成默认值,通过validate()方法让Bean验证属性的合法性。更重要的是,ActionServlet确保Form Bean被创建且可供它的动作方法调用。HTML标记库还能够确保Form Bean被正确地初始化并从Form View获取数据。

  Form Bean应当属于Model部分,然而,由于它有validate()方法,因此从某些特征来看它更接近分配器。不过,不必太在乎这些概念上的问题。Model 2并不完全等同于MVC,而且一些人已经在责难MVC不外乎是几种简单设计模式的混合物。不管怎样,从应用实践的角度来讲,系统的稳定性远比概念的严格性更重要。在本例中,这个问题更加富有代表性,因为我们把持久化机制也包装到Form Bean里面。从技术上看,Bean数据的持久化副本就是一个View,因此,从这个意义上来讲,我们现在有了一个结合了分配器和View特点的Model。这种设计方式看起来似乎否定了引入Struts之类框架的理由,但实际上,这种设计方式两方面的特点弥补了许多遗憾。

  首先,由于验证代码和SQL代码在很大程度上依赖于Form Bean拥有的属性,所以把它们作为一个单元管理会带来很大的方便。由于这里只对Form Bean的属性感兴趣,“重量级”的分配器和View部件都得到了有效的隔离。其次,Form Bean与HTML标记库一起使用时,Form Bean可以包含其他对象。这些对象可以通过“.”符号应用。使用预定义的Java对象时,“.”引用方式能够带来很大的方便,因为Java不支持多重继承。“.”引用方式避免了手工编写大量get/set代码的繁杂工作。

  当内部对象是EJB时,“.”引用方式带来的方便更加突出,因为在JSP页面中引用EJB时,EJB往往显得很“笨重”。如果EJB嵌入到了Form Bean里面,许多这方面的遗憾就不再存在。更重要的是,它分离了Controller和Model,而且View持久化也简缩到了最简单的程度,因为EJB容器可以处理所有持久化方面的细节。这样,Form Bean就几乎成了一个纯粹的分配器,一切都变得整洁和清晰。

  如果EJB有大量的属性,而且按照ActionServlet通常对Form Bean所做的那样,按照每个属性分别更新的方式进行更新,就会出现大量的RMI调用开销。对于要求较高的应用,更好的选择是利用EJB 2.0本地接口,或者在EJB之前加上一个传统的JavaBean(通常是会话EJB),并把该Bean传递给实体Bean的UpdateAllProperties()业务方法。后面这种方案允许在单个RMI调用中完成所有的更新操作.

准备阶段

  一次典型的编辑会话要求有一个动作处理器准备View,即一个作为View的JSP页面,还要求有第二个动作处理器存储更新后的View。当然,存储操作之后会有第二个属于View的页面被显示,例如一个“数据已经更新,点击此处继续”的页面(参见表1)。

   表1:基于Form Bean的编辑过程要用到的部件

部件

说明

CatalogForm

Form Bean

EditCategoryAction

准备阶段

EditCategory.jsp

编辑

SaveCategoryAction

存储阶段

EditDone.jsp

确认数据已经保存

EditFailed.jsp

“数据没有保存”错误



  下面的代码片断显示了如何在struts-config.xml文件中配置准备阶段:

<action path="/editCategory" scope="request" name="catForm" type="com.strutsdemo.EditCategoryAction" unknown="false" validate="false"> <forward name="success" path="/EditCategory.jsp"/> </action>



  在准备阶段,容器尝试从Session或Request找出指定的Form Bean,这是因为在动作中指定了“name=…”。ActionServlet在struts-config.xml文件的
区域寻找Form Bean的别名,利用Form Bean的别名寻找对应的Java类。如果用户的请求带有参数,其名字匹配Form Bean属性名字的参数将被设置为属性值。Struts扩展了“属性名字”的含义,使得访问Form Bean内嵌对象的属性成为可能。本文的例子也用到了Struts的这一优点。

  准备好Form Bean之后,ActionServlet接着调用动作的process()方法,Form Bean作为参数之一传入process()方法。在这里,我们对Form Bean的属性作最后的调整,调用业务方法,委派作为View的EditCategory,从而生成一个以Form Bean中合适数据为基础的HTML页面。这个页面被传递给客户端,接下来就进入了“表现”阶段。

  表现阶段

  这一阶段用户编辑表单并提交。如果服务器端的应用认为用户提交的内容存在问题,它把表单再次显示给用户,加上适当的提示信息;重复该过程,直至用户提交了合法的表单,或取消了表单处理过程。编辑过程的中止可能是由于用户跳转到了其他页面,或者启动了一个取消动作(例如点击了一个由html:cancel标记定义的按钮)。虽然在理论上,View的验证和再次显示操作应该属于表现阶段,但在Struts应用中,这部分功能在存储阶段实现最方便。

  存储阶段

  准备阶段创建了一个带有“name=”属性定义的动作CatForm,存储阶段要加入另外两个属性,即:“validate=‘true’”和“input=”属性。

<action path="/saveCategory" scope="request" name="catForm" type="com.strutsdemo.SaveCategoryAction" unknown="false" input="/EditCategory1.jsp" validate="true"> <forward name="success" path="/CategoryUpdated.jsp"/> </action>



  设置了“validate=‘true’”属性选项之后,服务器端就会增加一个处理步骤。重新用来自View的数据构造出Form Bean,或更新From Bean的时候,Form Bean的validate()方法会被调用。validate()方法执行必要的合法性验证操作。如果用户的输入数据中存在错误,validate()方法就创建一个或多个ActionError对象。这些ActionError对象包含了错误信息源ID和表单输入域的名称。这些ActionError对象被收集和整理到一个ActionErrors对象,随后ActionErrors对象由validate()方法返回。如果用户输入的数据不包含错误,validate()返回null。

  由于指定了“input=”属性,一旦出现了错误,动作会被忽略,而“input=”指定的View被显示。这个View既包含Form Bean,也包含当前出现的错误对象集合。一般地,这个输入页面就是原来执行编辑功能的JSP页面。

  大多数Struts的html标记有对应的HTML标记,但Struts有一个HTML没有的标记,即标记。要中止表单编辑过程,用户既可以手工输入URL,也可以点击不指向存储动作处理器的链接。因此,用标记定义的“取消”按钮,不是取消编辑操作的唯一方法。

  假设validate()方法没有发现任何错误,且用户没有点击“取消”按钮,存储动作的process()方法将被调用。在本例的process()方法中,我们调用了Form Bean的save()方法把数据写入持久性存储设备,然后根据写入操作是否成功,显示“存储操作成功”或“存储操作失败”的View。

构造和运行Struts应用

  要构造和运行本文的示例应用,你必须了解如何使用Jakarta的Ant工具。如果你还不了解Ant,现在该是学习它的时候了!赶紧到网站下载Ant,通常要解开压缩,设置一下ANT_HOME环境变量,然后把Ant加入到执行路径就可以了。

本文示例的build.xml需要稍微定制一下,修改指示本地Tomcat位置的配置,使它能够找到在Tomcat下编译所必需的类。另外,你还要有一份Struts的JAR。你可以去下载最新的版本。

  struts-config.xml文件是粘合Struts应用各个部分的配置文件。在部署完成后的Web应用中,struts-config.xml在Web-INF目录下。你应该修改一下数据源配置,使之符合你当前使用的DBMS环境。数据模型和SQL模式文件在下载包的DBMS目录下,SQL文件针对PostgreSQL DBMS编写。

  示例中src/com/strutsdemo/ShowCategoriesAction. java是一个简单的分配器。ActionForward()是请求分配方法,从ActionServlet调用。该方法可以完成主要的工作,例如分析请求参数、执行计算,以及构造出View使用的JavaBean。另外,该方法还要根据处理结果,确定下一个要显示的是什么页面:可能是预设的多个页面之一,也可能是一个错误信息页面。

  ActionForward()的请求分配过程

  当然,最复杂的处理过程与表单有关。ActionForward方法的请求分配过程是:

  1. ActionServlet,对请求进行解码。由于为动作指定了Form Bean,ActionServlet处理Form Bean(参见下面有关“ActionServlet如何使用Form Bean”的说明)。然后,请求传递给了EditCategoryAction。

  2. EditCategoryAction;准备处理View,或者从数据库装入现有数据,或者创建新的数据项。动作处理器利用Mapping.findForward把控制传递给EditCategory.jsp。

  3. DitCategory.jsp,显示出Form Bean,允许用户编辑数据。用户提交数据后,控制转到ActionServlet。

  4. ActionServlet,对请求进行解码。这一次,Form Bean将从View的数据初始化,因为它是一个Struts的JSP表单页面。由于有Form Bean,且struts-config.xml中指定了“validate=‘true’”,名为“catForm”的Form Bean的validate()方法被调用。如果用户提交的数据未能通过合法性验证,则控制转到EditCategory1.jsp。

  5. EditCategory1.jsp,它只是EditCategory.jsp略加修改后的一个版本。如果有必要,原始编辑页面和带有错误提示的编辑页面可以使用同一个View。Struts的JSP标记能够帮助我们轻松地办到这一点。该页面提交给/saveCategory.do。这样,用户就在这几个页面之间绕圈子,直到他跳转到一个与编辑操作无关的页面,或者他提交的数据通过了合法性验证。

  6. 如果Form Bean合法性验证通过,ActionServlet把请求(包括Form Bean)传递给SaveCategoryAction。在这个例子中,“save”可能意味着创建操作,也可能意味着更新操作,具体由URL提供的选项决定。写入数据的操作通过调用Form Bean的store()方法完成。注意:实际的应用应当使用某种类型的事务管理机制(或使用EJB,因为EJB有内建的事务管理机制),以避免并发访问带来的问题。

  ActionServlet如何使用Form Bean

  涉及Form Bean的ActionServlet处理过程包含六个步骤:

  1. 找到或创建Form Bean;

  2. 据从HTTP请求传入的相应数据,更新Form Bean的各个属性;

  3. 检查用户是否点击了“取消”按钮。如是,跳过步骤4和步骤5;

  4. 验证Form Bean数据的合法性;

  5. 如数据未能通过合法性验证,发送“input=”参数中指定的View;

  6. 否则,把Form Bean传递给动作处理器

    今天终于把论文交上去了,人一下子轻松了下来,竟然不知道做什么了。本来昨天就把论文调好了,突然听到学院的通知,格式要求变了,于是一大早爬起来,可怜我只睡了4个小时,一边忿忿不平,一边认命地改论文。头晕脑胀地改了一天,算是大概有了个样子,联系到了复印社,准备去打印。本来想自己打的,可是算了算,一点不便宜,而且极慢,大概要打6个钟头,其间还需要去灌几次墨,于是死心了,找到了这家24小时营业的复印社,打算和它死拼了,大不了熬通宵,心中一股慷慨激昂的感觉油然而生。

      来到复印社,很快就打出了第一份,然后他们还胸有成竹地接下了复印、装订的工作,告诉我,明天早上来取就好了。竟然这么顺利!!害得我还穿了厚毛衣和厚棉服,来抵御通宵的寒冷,没想到竟然这么容易。不禁感叹自己决策的英明伟大!这家复印社做起事情来真是干净利索呀,不过,他们收钱的时候也同样地干净利落,500大洋就这样落入了他们的腰包

      今天一早,天气很好,跑去取了论文,好沉滴,然后匆匆又跑去学院交了论文,办了一大堆的手续,又拿回来一大堆的表填写,辛苦辛苦。本来以为会好好休息一下的,没想到却办了好多事情。先是去交了取暖费,又联系了工程队来安装暖气管。陪LG去买了一件外套,(可怜LG的外套在大火中都被烧毁了)又去取了之前干洗的毛毯和被子。刚刚回到家,工程队的就来了,装上了暖气管,结果又发现我家的暖气片漏水,由于我们包了暖气,现在暖气片取不出来,只好去买新的。于是,我和LG又跑去家居,逛了一晚上,尽可能快地在家居关门前1分钟买了暖气片,可怜的LG又呼哧呼哧地把他抱回了家,好在家居离我家很近。

      唉,真是辛苦的一天,估计这样的日子还要继续一阵了。现在总算坐下来休息了,写论文的日子总是这么忙乱。LG又开始玩他喜欢的“罗马”了,真是佩服死了,碰到他喜欢的游戏就能变成体力狂,嚯嚯。

2005年11月15日

Java 语言的Calendar(日历),Date(日期), 和DateFormat(日期格式)组成了Java标准的一个基本但是非常重要的部分. 日期是商业逻辑计算一个关键的部分. 所有的开发者都应该能够计算未来的日期, 定制日期的显示格式, 并将文本数据解析成日期对象. 我们写了两篇文章,这是第一篇, 我们将大概的学习日期, 日期格式, 日期的解析和日期的计算.   

我们将讨论下面的类: 

1、具体类(和抽象类相对)java.util.Date 
2、抽象类java.text.DateFormat 和它的一个具体子类,java.text.SimpleDateFormat 
3、抽象类java.util.Calendar 和它的一个具体子类,java.util.GregorianCalendar 

具体类可以被实例化, 但是抽象类却不能. 你首先必须实现抽象类的一个具体子类. 

Date 类从Java 开发包(JDK) 1.0 就开始进化, 当时它只包含了几个取得或者设置一个日期数据的各个部分的方法, 比如说月, 日, 和年. 这些方法现在遭到了批评并且已经被转移到了Calendar类里去了, 我们将在本文中进一步讨论它. 这种改进旨在更好的处理日期数据的国际化格式. 就象在JDK 1.1中一样, Date 类实际上只是一个包裹类, 它包含的是一个长整型数据, 表示的是从GMT(格林尼治标准时间)1970年, 1 月 1日00:00:00这一刻之前或者是之后经历的毫秒数. 


一、创建一个日期对象 

让我们看一个使用系统的当前日期和时间创建一个日期对象并返回一个长整数的简单例子. 这个时间通常被称为Java 虚拟机(JVM)主机环境的系统时间. 
[code:1:ad22b58018]import java.util.Date; 

public class DateExample1 { 
public static void main(String[] args) { 
// Get the system date/time 
Date date = new Date(); 

System.out.println(date.getTime()); 


[/code:1:ad22b58018]

在星期六, 2001年9月29日, 下午大约是6:50的样子, 上面的例子在系统输出设备上显示的结果是 1001803809710. 在这个例子中,值得注意的是我们使用了Date 构造函数创建一个日期对象, 这个构造函数没有接受任何参数. 而这个构造函数在内部使用了System.currentTimeMillis() 方法来从系统获取日期. 

那么, 现在我们已经知道了如何获取从1970年1月1日开始经历的毫秒数了. 我们如何才能以一种用户明白的格式来显示这个日期呢? 在这里类java.text.SimpleDateFormat 和它的抽象基类 java.text.DateFormat 就派得上用场了. 


二、日期数据的定制格式 

假如我们希望定制日期数据的格式, 比方星期六-9月-29日-2001年. 下面的例子展示了如何完成这个工作:
[code:1:ad22b58018]import java.text.SimpleDateFormat; 
import java.util.Date; 

public class DateExample2 { 

public static void main(String[] args) { 

SimpleDateFormat bartDateFormat = 
new SimpleDateFormat("EEEE-MMMM-dd-yyyy"); 

Date date = new Date(); 

System.out.println(bartDateFormat.format(date)); 


[/code:1:ad22b58018]
只要通过向SimpleDateFormat 的构造函数传递格式字符串"EEE-MMMM-dd-yyyy", 我们就能够指明自己想要的格式. 你应该可以看见, 格式字符串中的ASCII 字符告诉格式化函数下面显示日期数据的哪一个部分. EEEE是星期, MMMM是月, dd是日, yyyy是年. 字符的个数决定了日期是如何格式化的.传递"EE-MM-dd-yy"会显示 Sat-09-29-01. 请察看Sun 公司的Web 站点获取日期格式化选项的完整的指示.


三、将文本数据解析成日期对象 

假设我们有一个文本字符串包含了一个格式化了的日期对象, 而我们希望解析这个字符串并从文本日期数据创建一个日期对象. 我们将再次以格式化字符串"MM-dd-yyyy" 调用SimpleDateFormat类, 但是这一次, 我们使用格式化解析而不是生成一个文本日期数据. 我们的例子, 显示在下面, 将解析文本字符串"9-29-2001"并创建一个值为001736000000 的日期对象. 

例子程序: 
[code:1:10d3268d34]import java.text.SimpleDateFormat; 
import java.util.Date; 

public class DateExample3 { 

public static void main(String[] args) { 
// Create a date formatter that can parse dates of 
// the form MM-dd-yyyy. 
SimpleDateFormat bartDateFormat = 
new SimpleDateFormat("MM-dd-yyyy"); 

// Create a string containing a text date to be parsed. 
String dateStringToParse = "9-29-2001"; 

try { 
// Parse the text version of the date. 
// We have to perform the parse method in a 
// try-catch construct in case dateStringToParse 
// does not contain a date in the format we are expecting. 
Date date = bartDateFormat.parse(dateStringToParse); 

// Now send the parsed date as a long value 
// to the system output. 
System.out.println(date.getTime()); 

catch (Exception ex) { 
System.out.println(ex.getMessage()); 



[/code:1:10d3268d34]


四、使用标准的日期格式化过程 

既然我们已经可以生成和解析定制的日期格式了, 让我们来看一看如何使用内建的格式化过程. 方法 DateFormat.getDateTimeInstance() 让我们得以用几种不同的方法获得标准的日期格式化过程. 在下面的例子中, 我们获取了四个内建的日期格式化过程. 它们包括一个短的, 中等的, 长的, 和完整的日期格式. 
[code:1:10d3268d34]import java.text.DateFormat; 
import java.util.Date; 

public class DateExample4 { 

public static void main(String[] args) { 
Date date = new Date(); 

DateFormat shortDateFormat = 
DateFormat.getDateTimeInstance( 
DateFormat.SHORT, 
DateFormat.SHORT); 

DateFormat mediumDateFormat = 
DateFormat.getDateTimeInstance( 
DateFormat.MEDIUM, 
DateFormat.MEDIUM); 

DateFormat longDateFormat = 
DateFormat.getDateTimeInstance( 
DateFormat.LONG, 
DateFormat.LONG); 

DateFormat fullDateFormat = 
DateFormat.getDateTimeInstance( 
DateFormat.FULL, 
DateFormat.FULL); 

System.out.println(shortDateFormat.format(date)); 
System.out.println(mediumDateFormat.format(date)); 
System.out.println(longDateFormat.format(date)); 
System.out.println(fullDateFormat.format(date)); 


[/code:1:10d3268d34]

注意我们在对 getDateTimeInstance的每次调用中都传递了两个值. 第一个参数是日期风格, 而第二个参数是时间风格. 它们都是基本数据类型int(整型). 考虑到可读性, 我们使用了DateFormat 类提供的常量: SHORT, MEDIUM, LONG, 和 FULL. 要知道获取时间和日期格式化过程的更多的方法和选项, 请看Sun 公司Web 站点上的解释. 

运行我们的例子程序的时候, 它将向标准输出设备输出下面的内容: 
9/29/01 8:44 PM 
Sep 29, 2001 8:44:45 PM 
September 29, 2001 8:44:45 PM EDT 
Saturday, September 29, 2001 8:44:45 PM EDT


五、Calendar 类 

我们现在已经能够格式化并创建一个日期对象了, 但是我们如何才能设置和获取日期数据的特定部分呢, 比如说小时, 日, 或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类. 就如我们前面提到的那样, Calendar 类中的方法替代了Date 类中被人唾骂的方法. 

假设你想要设置, 获取, 和操纵一个日期对象的各个部分, 比方一个月的一天或者是一个星期的一天. 为了演示这个过程, 我们将使用具体的子类 java.util.GregorianCalendar. 考虑下面的例子, 它计算得到下面的第十个星期五是13号. 
[code:1:041aeb23d1]import java.util.GregorianCalendar; 
import java.util.Date; 
import java.text.DateFormat; 

public class DateExample5 { 

public static void main(String[] args) { 
DateFormat dateFormat = 
DateFormat.getDateInstance(DateFormat.FULL); 

// Create our Gregorian Calendar. 
GregorianCalendar cal = new GregorianCalendar(); 

// Set the date and time of our calendar 
// to the system&s date and time 
cal.setTime(new Date()); 

System.out.println("System Date: " + 
dateFormat.format(cal.getTime())); 

// Set the day of week to FRIDAY 
cal.set(GregorianCalendar.DAY_OF_WEEK, 
GregorianCalendar.FRIDAY); 
System.out.println("After Setting Day of Week to Friday: " + 
dateFormat.format(cal.getTime())); 

int friday13Counter = 0; 
while (friday13Counter <= 10) { 

// Go to the next Friday by adding 7 days. 
cal.add(GregorianCalendar.DAY_OF_MONTH, 7); 

// If the day of month is 13 we have 
// another Friday the 13th. 
if (cal.get(GregorianCalendar.DAY_OF_MONTH) == 13) { 
friday13Counter++; 
System.out.println(dateFormat.format(cal.getTime())); 
} } } } 

在这个例子中我们作了有趣的函数调用: 
cal.set(GregorianCalendar.DAY_OF_WEEK, 
GregorianCalendar.FRIDAY); 

和: 
cal.add(GregorianCalendar.DAY_OF_MONTH, 7); 

set 方法能够让我们通过简单的设置星期中的哪一天这个域来将我们的时间调整为星期五. 注意到这里我们使用了常量 DAY_OF_WEEK 和 FRIDAY来增强代码的可读性. add 方法让我们能够在日期上加上数值. 润年的所有复杂的计算都由这个方法自动处理. 

我们这个例子的输出结果是: 
System Date: Saturday, September 29, 2001 
当我们将它设置成星期五以后就成了: Friday, September 28, 2001 
Friday, September 13, 2002 
Friday, December 13, 2002 
Friday, June 13, 2003 
Friday, February 13, 2004 
Friday, August 13, 2004 
Friday, May 13, 2005 
Friday, January 13, 2006 
Friday, October 13, 2006 
Friday, April 13, 2007 
Friday, July 13, 2007 
Friday, June 13, 2008 


六、时间掌握在你的手里 

有了这些Date 和Calendar 类的例子, 你应该能够使用 java.util.Date, java.text.SimpleDateFormat, 和 java.util.GregorianCalendar 创建许多方法了. 

在下面的文章中, 我们将讨论更高级的Date 和 Calendar 类的使用技巧, 包括时区和国际化的格式. 我们还会考察两个日期类 java.sql.Date 和 java.util.Date 之间的区别.

几个比较好的Symbian论坛

Symbian公司官方网站
http://www.symbian.com/

UIQ公司官方网站:
http://www.uiq.com/

UIQ开发者门户网站(含UIQ论坛):
http://developer.uiq.com/

诺基亚论坛:
http://www.forum.nokia.com/

诺基亚论坛讨论区:
http://discussion.forum.nokia.com/forum/

诺基亚论坛中文讨论区:
http://discussion.forum.nokia.com/forum/forumdisplay.php?s=2c69701ec253c19473de786fc91d0fdc&forumid=71

索爱官方网站:
http://www.sonyericsson.com/

索爱开发者论坛:
http://developer.sonyericsson.com/show_forums.do

NewLC:
http://www.newlc.com/

NewLC论坛:
http://forum.newlc.com/

My Symbian:
http://my-symbian.com/
http://my-symbian.com/forum/

All About Symbian:
http://www.allaboutsymbian.com/
http://www.allaboutsymbian.com/forum/

Symbian One:
http://www.symbianone.com/
http://www.symbianone.com/index.php?option=com_simpleboard&Itemid=48&Itemid=74

——————————————————————————–
WDA中文网
http://www.wda.com.cn/

CSDN:
http://www.csdn.net/
http://community.csdn.net/
http://www.csdn.net/mobile/
http://www.csdn.net/mobile/nokia/listncsp.aspx

移动未来:
http://www.move2008.com/
http://www.move2008.com/bbs/index.asp

Mobile2008(挂了):
http://www.mobile2008.com/

M9W移动无限:
http://www.m9w.com/
http://www.m9w.com/forum/

其它:
http://www.symbian.org.cn/
http://www.3g4g.net/
http://www.sf.org.cn/
http://www.msale.net/

——————————————————————————–
以下是热心网友推荐的,未验证:
http://www.3g-express.com/
http://www.ayychina.com/7650bbs/index.asp
http://www.ioicn.com/
http://www.ioicn.com.cn/
http://www.phonesky.net/
http://www.dfun.net/
http://www.ebds.com.cn/bbs/
http://www.bwo.com.cn/forum/

2005年11月12日

本系列源改编自O’Reily的Strus Cookbook,后因时间关系没再进行。如果以后有时间还需继续,可能会更简化一些 。

Struts秘籍之第1段:配置Struts应用

Struts秘籍之起式:第1.2式:部署Struts示例应用

Struts秘籍之起式:第1.3式:迁移至Struts 1.1

Struts秘籍之起式:第1.4式:升级至Struts 1.2

Struts秘籍之起式:第1.5:JSP 应用转到Struts

Struts秘籍之起式:第1.6式:管理Struts配置文件

Struts秘籍之起式:第1.7式:使用Ant进行构建和部署

Struts秘籍之起式:第1.8式:用XDoclet 产生Struts配置文件

Struts秘籍之第2段:UI

Struts秘籍之第1段:第2.1式:用插件来进行应用初始化

Struts秘籍之第1段:第2.1式:关于标签库声明

Struts秘籍之第1段:第2.3式:在JSP中使用常数

Struts秘籍之第1段:第2.4式:多配置文件

Struts秘籍之第1段:第2.5. 将应用重构为模块

Struts秘籍之第1段:第2.6. 使用多个资源束

Struts秘籍之第1段:第2.7. 访问来自于数据库中的消息资源

Struts秘籍之第1段:第2.8. 有选择地禁止Action

Struts秘籍之第2段:第3.1式: 使用JSTL

Struts秘籍之第2段:第3.2式:使用Struts-EL 标签

Struts秘籍之第2段:第3.3式:显示索引属性

Struts秘籍之第2段:第3.4式:在表单中使用索引属性

Struts秘籍之第2段:第3.5式: JSTL循环中使用索引属性

Struts秘籍之第2段:第3.6式: 从图像提交表单

Struts秘籍之第2段:第3.7式: 动态产生JavaScript

Struts秘籍之第2段:第3.8式:使用JavaScript动态改变选择项

Struts秘籍之第2段,第 3.9式,产生动态选择列表项目

Struts秘籍之第2段:第3.10:过滤文本输入

2005年11月11日

1、如何学习Spring?
你可以通过下列途径学习spring:
(1) spring下载包中doc目录下的MVC-step-by-step和sample目录下的例子都是比较好的spring开发的例子。

(2) AppFuse集成了目前最流行的几个开源轻量级框架或者工具Ant,XDoclet,Spring,Hibernate(iBATIS),JUnit,Cactus,StrutsTestCase,Canoo’s WebTest,Struts Menu,Display Tag Library,OSCache,JSTL,Struts 。
你可以通过AppFuse源代码来学习spring。
AppFuse网站:http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse

(3)Spring 开发指南(夏昕)(http://www.xiaxin.net/Spring_Dev_Guide.rar)
一本spring的入门书籍,里面介绍了反转控制和依赖注射的概念,以及spring的bean管理,spring的MVC,spring和hibernte,iBatis的结合。

(4) spring学习的中文论坛
SpringFramework中文论坛(http://spring.jactiongroup.net)
Java视线论坛(http://forum.javaeye.com)的spring栏目

2、利用Spring框架编程,console打印出log4j:WARN Please initialize the log4j system properly?
说明你的log4j.properties没有配置。请把log4j.properties放到工程的classpath中,eclipse的classpath为bin目录,由于编译后src目录下的文件会拷贝到bin目录下,所以你可以把log4j.properties放到src目录下。
这里给出一个log4j.properties的例子:

log4j.rootLogger=DEBUG,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p (%F:%L) – %m%n


3、出现 java.lang.NoClassDefFoundError?
一般情况下是由于你没有把必要的jar包放到lib中。

比如你要采用spring和hibernate(带事务支持的话),你除了spring.jar外还需要hibernat.jar、aopalliance.jar、cglig.jar、jakarta-commons下的几个jar包。

http://www.springframework.org/download.html下载spring开发包,提供两种zip包
spring-framework-1.1.3-with-dependencies.zip和spring-framework-1.1.3.zip,我建议你下载spring-framework-1.1.3-with-dependencies.zip。这个zip解压缩后比后者多一个lib目录,其中有hibernate、j2ee、dom4j、aopalliance、jakarta-commons等常用包。


4、java.io.FileNotFoundException: Could not open class path resource [....hbm.xml],提示找不到xml文件?
原因一般有两个:
(1)该xml文件没有在classpath中。
(2)applicationContext-hibernate.xml中的xml名字没有带包名。比如:
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref bean="dataSource"/></property>
<property name="mappingResources">
<list>
<value>User.hbm.xml</value> 错,改为: <value>com/yz/spring/domain/User.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect"> net.sf.hibernate.dialect.MySQLDialect </prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>


5、org.springframework.beans.NotWritablePropertyException: Invalid property ‘postDao’ of bean class?
出现异常的原因是在application-xxx.xml中property name的错误。
<property name="…."> 中name的名字是与bean的set方法相关的,而且要注意大小写。
比如
public class PostManageImpl extends BaseManage implements PostManage {
private PostDAO dao = null;
public void setPostDAO(PostDAO postDAO){
this.dao = postDAO;
}
}
那么xml的定义应该是:
<bean id="postManage" parent="txProxyTemplate">
<property name="target">
<bean class="com.yz.spring.service.implement.PostManageImpl">
<property name="postDAO"><ref bean="postDAO"/></property> 对
<property name="dao"><ref bean="postDAO"/></property> 错
</bean>
</property>
</bean>


6、Spring中如何实现事务管理?
首先,如果使用mysql,确定mysql为InnoDB类型。
事务管理的控制应该放到商业逻辑层。你可以写个处理商业逻辑的JavaBean,在该JavaBean中调用DAO,然后把该Bean的方法纳入spring的事务管理。

比如:xml文件定义如下:
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref bean="transactionManager"/></property>
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

<bean id="userManage" parent="txProxyTemplate">
<property name="target">
<bean class="com.yz.spring.service.implement.UserManageImpl">
<property name="userDAO"><ref bean="userDAO"/></property>
</bean>
</property>
</bean>

com.yz.spring.service.implement.UserManageImpl就是我们的实现商业逻辑的JavaBean。我们通过parent元素声明其事务支持。


7、如何管理Spring框架下更多的JavaBean?
JavaBean越多,spring配置文件就越大,这样不易维护。为了使配置清晰,我们可以将JavaBean分类管理,放在不同的配置文件中。 应用启动时将所有的xml同时加载。
比如:
DAO层的JavaBean放到applicationContext-hibernate.xml中,商业逻辑层的JavaBean放到applicationContext-service.xml中。然后启动类中调用以下代码载入所有的ApplicationContext。

String[] paths = {"com/yz/spring/dao/hibernate/applicationContext-hibernate.xml",
"com/yz/spring/service/applicationContext-service.xml"};
ctx = new ClassPathXmlApplicationContext(paths);


8、web应用中如何加载ApplicationContext?
可以通过定义web.xml,由web容器自动加载。

<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-hibernate.xml</param-value>
<param-value>/WEB-INF/applicationContext-service.xml</param-value>
</context-param>

9、在spring中如何配置的log4j?
在web.xml中加入以下代码即可。
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>


10、Spring框架入门的编程问题解决了,我该如何更深地领会Spring框架呢?
这两本书你该去看看。这两本书是由Spring的作者Rod Johnson编写的。
Expert One on one J2EE Design and Development
Expert One on one J2EE Development Without EJB
你也该看看martinfowler的Inversion of Control Containers and the Dependency Injection pattern。
http://www.martinfowler.com/articles/injection.html

再好好研读一下spring的文档。
http://www.jactiongroup.net/reference/html/index.html(中文版,未全部翻译)

还有就是多实践吧。

  先将近期我国外交上的重要事件列举如下:
  5月16日,国务院总理温家宝在会见美国商会代表团时,表示“人民币汇率改革属中国主权,
不屈从外界压力”。
  5月23日,国务院副总理吴仪紧急取消了原定与日本首相小泉的会谈。
  5月30日,国家商务部部长薄熙来宣布我国从6月1日起对81项纺织品取消征收出口关税。
  6月1日,中国常驻联合国代表王光亚明确表示,由“四国联盟”提出的要求增加安理会常任理
事国的决议草案危害联合国改革进程,如四国执意将这一决议草案付诸表决,中国将投票反对。
  6月21日,中国常驻联合国代表王光亚在第59届联合国大会发言时重申,中国反对人为的为安
理会改革设定时限,如果强行表决尚有重大分歧的安理会改革方案,中国将坚定地投反对票。
  在短短的一个多月时间内,中国对美国说不,对日本说不,对欧盟说不,对德日巴印四国联盟
说不。一时间,国际媒体一片愕然 ,国内媒体以及网络社区则是一致热评,有网友甚至惊呼“中国人民已经从站起来、富起来进入到了硬起来的时代”!笔者也同大多数网友一样,对今日中国外交方面的强硬举措欢欣鼓舞,扬眉吐气。
   
  在激动之余,不禁想起了网上流传的一道著名的微软面试题——海盗分金币。题目的大意是:
  5个海盗抢得100枚金币后,讨论如何进行公正分配。他们商定的分配原则是:
  (1)抽签确定各人的分配顺序号码(1,2,3,4,5);
  (2)由抽到1号签的海盗提出分配方案,然后5人进行表决,如果方案得到超过半数
的人同意,就按照他的方案进行分配,否则就将1号扔进大海喂鲨鱼;
  (3)如果1号被扔进大海,则由2号提出分配方案,然后由剩余的4人进行表决,当且仅当超过
半数的人同意时,才会按照他的提案进行分配,否则也将被扔入大海;
  (4)依此类推。
  这里假设每一个海盗都是绝顶聪明而理性,他们都能够进行严密的逻辑推理,并能很理智的判
断自身的得失,即能够在保住性命的前提下得到最多的金币。同时还假设每一轮表决后的结果都能顺利得到执行,那么抽到1号的海盗应该提出怎样的分配方案才能使自己既不被扔进海里,又可以得到更多的金币呢?
  此题公认的标准答案是:1号海盗分给3号1枚金币,4号或5号2枚金币,自己则独得97枚金币,
即分配方案为(97,0,1,2,0)或(97,0,1,0,2)。现来看如下各人的理性分析:
  首先从5号海盗开始,因为他是最安全的,没有被扔下大海的风险,因此他的策略也最为简单
,即最好前面的人全都死光光,那么他就可以独得这100枚金币了。
  接下来看4号,他的生存机会完全取决于前面还有人存活着,因为如果1号到3号的海盗全都喂
了鲨鱼,那么在只剩4号与5号的情况下,不管4号提出怎样的分配方案,5号一定都会投反对票来让4号去喂鲨鱼,以独吞全部的金币。哪怕4号为了保命而讨好5号,提出(0,100)这样的方案让5号独占金币,但是5号还有可能觉得留着4号有危险,而投票反对以让其喂鲨鱼。因此理性的4号是不应该冒这样的风险,把存活的希望寄托在5号的随机选择上的,他惟有支持3号才能绝对保证自身的性命。
  再来看3号,他经过上述的逻辑推理之后,就会提出(100,0,0)这样的分配方案,因为他知
道4号哪怕一无所获,也还是会无条件的支持他而投赞成票的,那么再加上自己的1票就可以使他稳获这100金币了。
  但是,2号也经过推理得知了3号的分配方案,那么他就会提出(98,0,1,1)的方案。因为
这个方案相对于3号的分配方案,4号和5号至少可以获得1枚金币,理性的4号和5号自然会觉得此方案对他们来说更有利而支持2号,不希望2号出局而由3号来进行分配。这样,2号就可以屁颠屁颠的拿走98枚金币了。
  不幸的是,1号海盗更不是省油的灯,经过一番推理之后也洞悉了2号的分配方案。他将采取的
策略是放弃2号,而给3号1枚金币,同时给4号或5号2枚金币,即提出(97,0,1,2,0)或(97,0,1,0,2)的分配方案。由于1号的分配方案对于3号与4号或5号来说,相比2号的方案可以获得更多的利益,那么他们将会投票支持1号,再加上1号自身的1票,97枚金币就可轻松落入1号的腰包了。
  
  看到这里,读者一定会问,这个海盗分金币的题目与中国说“不”有何关联呢?好,下面就切
入正题。

  海盗分金币模型的最终答案可能会出乎很多人的意料,因为从直觉来看,此模型中如此严酷的规定,若谁抽到1号真是天底下最不幸的人了。因为作为第一个提出方案的人,其存活的机会真是微乎其微,即使他一个金币也不要,都无私的分给其他4个人,那4个人也很可能因为觉得他的分配不公而反对他的方案,那他也就只有死路一条了。可是看起来处境最凶险的1号,却凭借着其超强的智慧和先发的优势,不但消除了喂鲨鱼的危险,而且最终还使自己的收益最大化,这不正像是当今国际社会国与国之间在政治、经济等领域相互博弈过程中,先发制人的智慧和优势的凸现吗?而5号表面上看起来是最安全的,可以坐山观虎斗,先让前面的海盗拼个你死我活而坐收渔翁之利,可实际上最后却不得不看别人的脸色行事,勉强分得一杯小羹,这不正是本想以静制动,后发制人而反得劣势的写照吗?看到这里,大家应当已经明白笔者所要表达的意思了,而很多事实也已证明,如果中国人总是处于5号位置,总是坐等着别人制定规则,那么就无法避免陷入“看人脸色”行事的不利处境。
   
  1989年前后,面对当时特殊的国际环境,邓小平先生提出了“冷静观察,稳住阵脚,沉着应付
,韬光养晦,有所作为”的方针,强调“决不当头”,之后中国就以一种中庸的姿态出现在国际舞台上。1989年9月16日,邓小平在会见李政道时说:“别国的事情,我们管不了”,从而道破其“韬光养晦”对国际事务做壁上观的玄机。笔者认为,“韬光养晦”的目的是着眼于国际竞争中的最后胜利,主张国家在经济和军事力量处于相对弱势时,收敛锋芒,保持克制,积累实力,不意气行事,甚至不惜以局部让步来谋求整体利益的最大化。其本质是为图强而忍辱,为进攻而防御,一旦功成,实力达就,破关而出,便可变退为进、转守反攻而致全胜。“韬光养晦”可以说是中国特定时期的特定外交策略。
   
  在这一策略的引导下,中国以经济建设为中心,埋头发展自身经济,对外则近睦远和,努力营
造有利于经济建设的外部环境,避免与别国发生直接正面的冲突与对抗。在过去的二十多年里,中国人小心翼翼地为自己成功营造了相对和平稳定的发展空间,并使中国成为全球发展最快的大型经济体,其GDP以年均超过8%的速度增长,成为世界第六大经济体和第二大外汇储备国。“韬光养晦”策略确保了中国经济的腾飞,大幅提升了国家在世界经济中的影响力。
   
  但是,在另一方面,中国人有时却不得不承受将愤怒埋藏于心中的痛苦。1993年美国一手制造
了“银河号”事件,1999年美国轰炸中国驻南大使馆,2001年美国侦察机冲撞中国军机;而另一个邻国则以参拜靖国神社、美化侵略史、宣称对钓鱼岛拥有主权等恶行一而再、再而三的挑战中国的忍耐极限。而通常情况下,中国虽然“很生气”,但所作反应的后果却不会“很严重”,最愤慨的也不过是表示“最强烈的抗议”。世人也已经习惯了作为五大常任理事国之一的中国,在面对国际重大突发事件上,总是以投弃权票而圆滑的平衡利益各方。

  我们大家都明白“小洞不补,大洞尺五”的道理,在一些局部利益上暂时的退让与妥协是理智的,可若长此以往,谁又能保证这样的退让不会更加助长对方的气焰,而最终导致自己权利的丧失呢?在中国蓄练健韧之后,该是到了转换博弈策略的时侯了。这次,吴仪副总理因不满日本首相小泉决意参拜靖国神社的言论拂袖而去;中国政府面对美国强逼人民币升值的巨大压力,面临欧美对中国纺织品设限的威胁,没有一味退让妥协,而是依托现有国际规范,据理力争,尽最大努力维护国家利益;特别是针对日本“入常”问题上明确说“不”,一扫中国政府以往在联合国中动辄“弃权”的模糊形象,向世界明示了中国的态度,向国际社会展示出中国的外交魄力,也使得日本不得不被迫接受由中国人参与制定的“游戏规则”。此后日本等四国联盟成员表示新常任理事国的否决权问题,可以等到安理会扩大完成的15年后再予以解决便是明证。在国际格局向多极化趋势发展,世界政治和经济新秩序亟待重塑的大背景下,中国必须直面外交挑战,并积极的有所作为。正如我国外交部长李肇星在与日本外相会谈时所指出的,在历史问题、台湾问题和钓鱼岛问题上挑战中国的核心利益,是非常危险的。道理很简单,中国的综合国力早已今非昔比,任何势力来挑战中国的核心国家利益,都会付出代价。这也就难怪有网友会惊呼中国“硬起来”了,而这一切在让国人感觉痛快淋漓的同时,也表明了中国新一代领导人正力图以更积极的态度介入国际事务,把握外交主动权,发挥先发优势。这也预示着中国正在告别多年来“太极推手”式模棱两可、谦逊忍让的时代,而逐步进入了作风强硬,棱角分明,态度明朗,昂扬务实的后“韬光养晦”时代。

  也许有人会担心,这样说不正是给某些不怀好意的国家散布“中国威胁论”找到借口吗?这让笔者又想起了一则笑话。说的是狼和狐狸遇见了一只山羊,就立刻指责其对自身安全构成了威胁,因为山羊长了角。后狼和狐狸又遇见了一只老虎,狼立刻提出双方建立“合作伙伴”乃至“战略伙伴”关系,而狐狸则声称要和老虎“世世代代友好下去”。中国必须习惯于与“中国威胁论”共生存,加大对地区事务和国际事务的参与力度,坚定地发出自己的声音,一个政治稳定、经济繁荣、社会和谐、军事强大的中国,将会是国际上许许多多国家愿意与之“世世代代友好”交往的国度。正如晨枫在《西线观察》里所谈到的,“中国的理想还不应仅仅是强国,而且应该更为远大。中国应该按照自己的世界观来示范和推动一个‘世界新秩序’——按照中国文化传统中的‘和而不同’的理想来对世界做出中国的贡献。”

  让我们把话题再次回到海盗分金币模型上来。显然,海盗分金币的模型相对于现实来说,实在是太粗糙了,现实中的情况远要比它复杂千万倍。
  首先,现实中肯定不可能人人都绝顶聪明并富有理性,海盗中只要3号、4号或5号中任何一人
偏离此假设,1号就极有可能被抛入大海。因此,现实中的1号必须首先考虑他的兄弟们是否足够的聪明与理性,而断然不能顾自取走那97枚金币。
  其次,在这一涉及个人重大利益的分配过程中,阴谋会像杂草一般疯长,而谎言与虚假承诺也
就有了用武之地。假如,2号事先对3、4、5号海盗大放烟雾弹,称基于1号所提出的任何分配方案,他都会再多加1个金币给他们,那结果可能又会是另一番景象了。
  再次,正如吴思先生在其所著的《血酬定律》一书中提到的,“所有规则的设立,说到底,都
遵循一条根本规则:暴力最强者说了算。这是一条元规则,决定规则的规则”。在发生争执时,如果在肉体上消灭对方是最合算的,付出成本也是最低的话,那么当5个海盗中最强悍的那个将刀架在其余海盗脖子上,并大喝道“要命还是要金币”的时候,那么任何的争执都不难解决,任何的意见也就不难统一了。
  当然,即使1号是那最强悍的海盗,其余4人也还是有可能组成一个反1号大联盟,并经过精心
策划和充分准备而起来“造反”,合力将1号制服并扔进大海,再由这4人重新商定分配规则。
  已经无需讨论更多的情况,相信大家已同意现实实在是太复杂的看法了。但是,海盗分金币的
模型还是不乏有启示意义,即任何“分配者”想让自己的方案获得通过,其关键是在于事先要考虑清楚“挑战者”所可能会提出的分配方案,然后尽力拉拢“挑战者”分配方案中最不得意的人,用最小的代价使自己的利益最大化,总之是离不开过人的智慧和高超的策略。

  毋庸讳言,随着中国国力的不断增强,我们在发展过程中所遇到的阻力和挑战也将会不断升级,中国与某些国家在政治、经济等领域的摩擦也将进入短兵相接的“巷战”时代。因此,中国不仅要适时的说“不”,更需要以超强的智慧、更娴熟的外交手腕去处理各种纷繁复杂的国际事务。但不管怎么说,这次对日本的当头棒喝,至少要比以往多次“强烈抗议”参拜靖国神社更能让日本当局正视他们的历史和现状。中国近期在经济、外交上的一系列强硬政策所体现出的“软的更软,硬的更硬”的新策略,可以说既有日益强盛的国力作实质支撑,又有国际竞争的实际需要,并且符合当今中国的民意潮流。愿我们祖国更加强盛,让妥善解决领土争端、能出游钓鱼岛看日出的日子早点到来!