| 海峰's profile随便写写了BlogListsNetwork | Help |
|
随便写写了随随便便 December, 2008 Software Process Improvement Tips and Tricks
信息安全(Information Security)简介 续 3 –信息安全风险管理 这一节我们继续介绍一下信息资产的威胁评估。
d ) 威胁评估
威胁是一种对组织及其资产构成潜在破坏的可能性因素,是客观存在的。威胁可以通过威胁主体、资源、动机、途径等多种属性来描述。造成威胁的因素可分为人为因素和环境因素。根据威胁的动机,人为因素又可分为恶意和非恶意两种。环境因素包括自然界不可抗的因素和其它物理因素。威胁作用形式可以是对信息系统直接或间接的攻击,在机密性、完整性或可用性等方面造成损害;也可能是偶发的、或蓄意的事件。 在对威胁进行分类前,应考虑威胁的来源。表1提供了一种威胁来源的分类方法。 表1 威胁来源列表 对威胁进行分类的方式有多种,针对上表的威胁来源,可以根据其表现形式将威胁分为以下几类。表2提供了一种基于表现形式的威胁分类方法。 表2 一种基于表现形式的威胁分类表
如果你对我们的过程小贴士有任何的疑问或建议,不妨给我们邮件:process@achievo.com 欢迎您的参与和支持 ! 下一期会继续介绍资产的脆弱性评估。 Software Process Improvement Tips and Tricks
信息安全(Information Security)简介 续2 –信息安全风险管理 从上一节的介绍中我们知道了,在风险评估的过程中要依据企业的服务分类和业务过程进行信息资产分类和识别。这一节我们就来介绍一下信息资产的分类方法及其重要性评估。 a) 信息资产的分类 资产是具有价值的信息或资源,它能够以多种形式存在,有无形的、有形的,有硬件、软件,有文档、代码,也有服务、形象等。机密性、完整性和可用性是评价资产的三个安全属性。风险评估中资产的价值不仅仅以资产的经济价值来衡量,而是由资产在这三个安全属性上的达成程度或者其安全属性未达成时所造成的影响程度来决定的。安全属性达成程度的不同将使资产具有不同的价值,而资产面临的威胁、存在的脆弱性、以及已采用的安全措施都将对资产安全属性的达成程度产生影响。为此,有必要对组织中的资产进行识别。 在一个组织中,资产有多种表现形式;同样的两个资产也因属于不同的信息系统而重要性不同,而且对于提供多种业务的组织,其支持业务持续运行的系统数量可能更多。这时首先需要将信息系统及相关的资产进行恰当的分类,以此为基础进行下一步的风险评估。在实际工作中,具体的资产分类方法可以根据具体的评估对象和要求,由评估者灵活把握。根据资产的表现形式,可将资产分为数据、软件、硬件、文档、服务、人员等类型,如下面表格所示。 分类 示例 电子资产 保存在信息媒介上的各种数据资料,包括源代码、数据库数据、系统文档、运行管理规程、计划、报告、用户手册等 软件资产 系统软件:操作系统、语言包、工具软件、各种库等 应用软件:外部购买的应用软件,外包开发的应用软件等 源程序:各种共享源代码、自行或合作开发的各种代码等 硬件资产 网络设备:路由器、网关、交换机等 计算机设备:大型机、小型机、服务器、工作站、台式计算机、移动计算机等 存储设备:磁带机、磁盘阵列、磁带、光盘、软盘、移动硬盘等 传输线路:光纤、双绞线等 保障设备:动力保障设备(UPS、变电设备等)、空调、保险柜、文件柜、门禁、消防设施等 安全保障设备:防火墙、入侵检测系统、身份验证等 其他:打印机、复印机、扫描仪、传真机等 服务资产 办公服务:为提高效率而开发的管理信息系统(MIS),包括各种内部配置管理、文件流转管理等服务 网络服务:各种网络设备、设施提供的网络连接服务 信息服务:对外依赖该系统开展的各类服务 纸质资产 纸质的各种文件,如传真、电报、财务报告、发展计划等 人员资产 掌握重要信息和核心业务的人员,如主机维护主管、网络维护主管及应用项目经理等 其它 企业形象,客户关系等 b) 资产安全属性 机密性、完整性和可用性是评价资产的三个安全属性。 机密性confidentiality指信息与信息系统不被非授权者所获取或利用的特性,包括数据保密和访问控制等方面。 完整性integrity指信息与信息系统真实、准确和完备,不被冒充、伪造和篡改的特性,包括身份真实、数据完整和系统完整等方面。 · 数据完整性 data integrity 数据所具有的特性,即无论数据形式作何变化,数据的准确性和一致性均保持不变。 · 系统完整性 system integrity 在防止非授权用户修改或使用资源和防止授权用户不正确地修改或使用资源的情况下,信息系统能履行其操作目的的品质。 可用性availability指信息与信息系统可被授权者在需要的时候访问和使用的特性 。 c) 资产价值 资产的重要程度或敏感程度的表征。资产价值是资产的属性,也是进行资产识别的主要内容。 资产价值应依据资产在机密性、完整性和可用性上的赋值等级,经过综合评定得出。综合评定方法可以根据自身的特点,选择对资产机密性、完整性和可用性最为重要的一个属性的赋值等级作为资产的最终赋值结果;也可以根据资产机密性、完整性和可用性的不同等级对其赋值进行加权计算得到资产的最终赋值结果。加权方法可根据组织的业务特点确定。最终确定资产的重要性等级。
如果你对我们的过程小贴士有任何的疑问或建议,不妨给我们邮件:process@achievo.com 欢迎您的参与和支持 ! October, 2008 SSH框架中根据不同的用户切换到对应的数据库特别是SAAS应用系统,一般的我们会为每个公司客户创建一个对应的数据库,然而我们肯定不会为每个客户部署一台服务器,这样应用系统就存在如何在多个数据库中自动切换的问题; 首先创建我们自己的数据源实现类,如下: package whf.framework.jdbc; import java.io.PrintWriter; import javax.sql.DataSource; import org.springframework.jdbc.datasource.DriverManagerDataSource; import whf.framework.config.Configuration; /** public void setDefaultDataSourceName(String defaultDataSourceName) { public final DataSource getDefaultDataSource() { @Override @Override @Override @Override @Override @Override @Override @Override @Override 这个数据源切换期可以按照三种方式切换: 1 在线程上下文环境中设置,参考whf.framework.util.ThreadContext.setCurrentDataSourceName, 在具体应用中这个也具有最高优先级,主要满足一些特殊的需要,限定某些操作只能在某个数据库中处理; 2 根据对象配置,如果当前操作的对象必须在某个数据库中,例如一些配置数据,可以限定为从某个自定的数据库中存取; 3 根据当前操作用户; 第二步 部署数据源到Spring <bean id="dataSource" class="whf.framework.jdbc.SwitchableDataSource">
其中参数使用Spring的properties设置,如 <bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 最后就是调用 首先用户的数据库是需要可识别的,因此用户必须有一个(用户所属组织)参数,用以识别对应的数据库;在用户登录时,需要三个参数:所属组织,用户名,密码 ok,上述已经完成; 完整代码参考:http://whfframework.googlecode.com/svn/trunk/ Spring中实现简单的性能监控以下介绍在Spring中如何实现简单的性能监控,监控每一个Spring管理的方法调用过程中花费的时间,并把相关信息记录到日志数据库中; June, 2008 数据流图数据流图(Data Flow Diagram,简称DFD) 是一种最常用的结构化分析工具,它从数据传递和加工角度,以图形的方式刻画系统内的数据运动情况。 数据流表示数据的流动情况;加工表示对数据的加工处理过程,它的名字应能简明扼要地表明所完成的是什么加工;数据存贮在数据流图中起着保存数据的作用,指向数据存贮的数据流可以理解为写数据,从数据存贮引出的数据流可以理解为读数据,双向数据流可以理解为修改数据;数据源点或终点,表示图中出现数据的始发点或终止点,它在图中的出现仅仅是一种符号,并不需要以软件的形式进行设计和实现。 在数据流图中,如果有两个以上数据流指向一个加工或从一个加工中引出,则这些数据流之间往往存在一定的关系。
在画数据流图时,如下几个问题值得注意:1)是画数据流图而不是画程序框图 对于很多人来说,通过学习计算机语言已经比较熟悉程序框图了,在画数据流图时很容易将它们搞混。程序框图是从对数据进行加工的角度描述系统的,其箭头是控制流,表示的是对数据进行加工的次序,它用于描述怎样解决问题;数据流图则是从数据的角度来描述系统的,其箭头是数据流,表示的是数据的流动方向,它用于描述是什么问题。 2)数据流及加工的命名 通常是先为数据流命名,然后再为加工命名。在给数据流命名时,应避免使用象“数据”、“输入”之类的缺乏具体含义的名字;在给加工命名时,理想的做法是取由一个具体的及物动词加一个具体的宾语构成的名字,如果必须用两个动词,则可以考虑将这个加工再分解成两个加工。在命名时,所取的名字应适合整个数据流或加工,而不是仅仅反映它的某些成分。如果发现某个数据流或加工难以命名,那么很可能是数据流图分解不当造成的,此时应该考虑重新分解数据流图。 取自"http://codex.wordpress.org.cn/%E6%95%B0%E6%8D%AE%E6%B5%81%E5%9B%BE" April, 2008 一位软件工程师的6年总结“又是一年毕业时”,看到一批批学子离开人生的象牙塔,走上各自的工作岗位;想想自己也曾经意气风发、踌躇满志,不觉感叹万千……本文是自己工作6年的经历沉淀或者经验提炼,希望对所有的软件工程师们有所帮助,早日实现自己的人生目标。本文主要是关于软件开发人员如何提高自己的软件专业技术方面的具体建议,前面几点旨在确定大的方向,算是废话吧。 谨以此文献给那个自己为你奉献3年青春与激情的开发团队。还有团队成员:PPL、YT、YK 、TYF、LGL、CHL、CDY、CB、DPD。 1、分享第一条经验:“学历代表过去、能力代表现在、学习力代表未来。”其实这是一个来自国外教育领域的一个研究结果。相信工作过几年、十几年的朋友对这个道理有些体会吧。但我相信这一点也很重要:“重要的道理明白太晚将抱憾终生!”所以放在每一条,让刚刚毕业的朋友们早点看到哈! 2、一定要确定自己的发展方向,并为此目的制定可行的计划。不要说什么,“我刚毕业,还不知道将来可能做什么?”,“跟着感觉走,先做做看”。因为,这样的观点会通过你的潜意识去暗示你的行为无所事事、碌碌无为。一直做技术,将来成为专家级人物?向管理方向走,成为职业经理人?先熟悉行业和领域,将来自立门户?还是先在行业里面混混,过几年转行做点别的?这很重要,它将决定你近几年、十年内“做什么事情才是在做正确的事情!”。 3、软件开发团队中,技术不是万能的,但没有技术是万万不能的!在技术型团队中,技术与人品同等重要,当然长相也比较重要哈,尤其在MM比较多的团队中。在软件项目团队中,技术水平是受人重视和尊重的重要砝码。无论你是做管理、系统分析、设计、编码,还是产品管理、测试、文档、实施、维护,多少你都要有技术基础。算我孤陋寡闻,我还真没有亲眼看到过一个外行带领一个软件开发团队成功地完成过软件开发项目,哪怕就一个,也没有看到。倒是曾经看到过一个“高学历的牛人”(非技术型)带一堆人做完过一个项目,项目交付的第二天,项目组成员扔下一句“再也受不了啦!”四分五裂、各奔东西。那个项目的“成功度”大家可想而知了。 4、详细制定自己软件开发专业知识学习计划,并注意及时修正和调整(软件开发技术变化实在太快)。请牢记:“如果一个软件开发人员在1、2年内都没有更新过自己的知识,那么,其实他已经不再属于这个行业了。”不要告诉自己没有时间。来自时间管理领域的著名的“三八原则”告诫我们:另外的那8小时如何使用将决定你的人生成败!本人自毕业以来,平均每天实际学习时间超过2小时。 5、书籍是人类进步的阶梯,对软件开发人员尤其如此。书籍是学习知识的最有效途径,不要过多地指望在工作中能遇到“世外高人”,并不厌其烦地教你。对于花钱买书,我个人经验是:千万别买国内那帮人出的书!我买的那些家伙出的书,!00%全部后悔了,无一本例外。更气愤的是,这些书在二手市场的地摊上都很难卖掉。“拥有书籍并不表示拥有知识;拥有知识并不表示拥有技能;拥有技能并不表示拥有文化;拥有文化并不表示拥有智慧。”只有将书本变成的自己智慧,才算是真正拥有了它。 6、不要仅局限于对某项技术的表面使用上,哪怕你只是偶尔用一、二次。“对任何事物不究就里”是任何行业的工程师所不应该具备的素质。开发Windows应用程序,看看Windows程序的设计、加载、执行原理,分析一下PE文件格式,试试用SDK开发从头开发一个Windows应用程序;用VC++、 Delphi、Java、.Net开发应用程序,花时间去研究一下MFC、VCL、J2EE、.Net它们框架设计或者源码;除了会用J2EE、 JBoss、Spring、Hibernate等等优秀的开源产品或者框架,抽空看看大师们是如何抽象、分析、设计和实现那些类似问题的通用解决方案的。试着这样做做,你以后的工作将会少遇到一些让你不明就里、一头雾水的问题,因为,很多东西你“知其然且知其所以然”! 8、养成总结与反思的习惯,并有意识地提炼日常工作成果,形成自己的个人源码库、解决某类问题的通用系统体系结构、甚至进化为框架。众所周知,对软件开发人员而言,有、无经验的一个显著区别是:无经验者完成任何任务时都从头开始,而有经验者往往通过重组自己的可复用模块、类库来解决问题(其实这个结论不应该被局限在软件开发领域、可以延伸到很多方面)。这并不是说,所有可复用的东西都必须自己实现,别人成熟的通过测试的成果也可以收集、整理、集成到自己的知识库中。但是,最好还是自己实现,这样没有知识产权、版权等问题,关键是自己实现后能真正掌握这个知识点,拥有这个技能。 9、理论与实践并重,内外双修。工程师的内涵是:以工程师的眼光观察、分析事物和世界。一个合格的软件工程师,是真正理解了软件产品的本质及软件产品研发的思想精髓的人(个人观点、欢迎探讨)。掌握软件开发语言、应用语言工具解决工作中的具体问题、完成目标任务是软件工程师的主要工作,但从软件工程师这个角度来看,这只是外在的东西,并非重要的、本质的工作。学习、掌握软件产品开发理论知识、软件开发方法论,并在实践中理解、应用软件产品的分析、设计、实现思想来解决具体的软件产品研发问题,才是真正的软件工程师的工作。站在成熟理论与可靠方法论的高度思考、分析、解决问题,并在具体实践中验证和修正这些思想与方式,最终形成自己的理论体系和实用方法论。 10、心态有多开放,视野就有多开阔。不要抱着自己的技术和成果,等到它们都已经过时变成垃圾了,才拿出来丢人现眼。请及时发布自己的研究成果:开发的产品、有创意的设计或代码,公布出来让大家交流或者使用,你的成果才有进化和升华的机会。想想自己2000年间开发的那些Windows系统工具,5、6年之后的今天,还是那个样子,今天流行的好多Windows系统工具都比自己的晚,但进化得很好,且有那么多用户在使用。并且,不要保守自己的技术和思想,尽可能地与人交流与分享,或者传授给开发团队的成员。“与人交换苹果之后,每个人还是只有一个苹果;但交换思想之后,每个人都拥有两种思想”,道理大家都懂,但有多少人真正能做到呢? 11、尽量参加开源项目的开发、或者与朋友共同研制一些自己的产品,千万不要因为没有钱赚而不做。网络早已不再只是“虚拟世界”,网上有很多的开源项目、合作开发项目、外包项目,这都是涉猎工作以外的知识的绝好机会,并且能够结识更广的人缘。不要因为工作是做ERP,就不去学习和了解嵌入式、实时、通信、网络等方面的技术,反过来也是一样。如果当他别人拿着合同找你合作,你却这也不会,那也不熟时,你将后悔莫及。 12、书到用时方恨少,不要将自己的知识面仅仅局限于技术方面。诺贝尔经济学奖得主西蒙教授的研究结果表明: “对于一个有一定基础的人来说,他只要真正肯下功夫,在6个月内就可以掌握任何一门学问。”教育心理学界为感谢西蒙教授的研究成果,故命名为西蒙学习法。可见,掌握一门陌生的学问远远没有想想的那么高难、深奥。多方吸取、广泛涉猎。极力夯实自己的影响圈、尽量扩大自己的关注圈。财务、经济、税务、管理等等知识,有空花时间看看,韬光养晦、未雨绸缪。 13、本文的总结与反思: A:不要去做技术上的高手,除非你的目标如此。虽然本文是关于提高软件开发知识的建议,做技术的高手是我一向都不赞同的。你可以提高自己的专业知识,但能胜任工作即止。 B:提高软件知识和技术只是问题的表面,本质是要提高自己认识问题、分析问题、解决问题的思想高度。软件专业知识的很多方法和原理,可以很容易地延伸、应用到生活的其它方面。 C:在能胜任工作的基础上,立即去涉猎其它领域的专业知识,丰富自己的知识体系、提高自己的综合素质,尤其是那些目标不在技术方面的朋友。 March, 2008 JBPM简介JBoss jBPM为设计及开发工作流和业务流程管理系统提供了一个先进的平台。由API、特定领域的语言和图形建模工具组成的框架让开发人员和业务分析人员能够使用通用平台进行沟通及操作。 工作流管理和业务流程管理(BPM)正在迅速成为企业获得软件敏捷性和适应性的重要方法。JBoss jBPM是一个面向流程的工作流/BPM框架和工具集,它使业务分析人员能够与软件组件进行交互、有助于获得有效的业务解决方案。 许多企业在积极寻求一种结构化方法,以便设计业务动作/事务,并且优先使用自动化流程加以执行。业务流程管理(BPM)和工作流管理使用动作、任务和流程等概念,提供了解决这个问题的办法。 业务流程管理一词通常是指企业通过一系列活动,以能够适应动态变化的环境的方式,自动管理及优化流程。这些活动通常寻求来自软件工程和工具的帮助。因而,BPM一词往往直接用来指软件工程技术和工具。 BPM体现为三个不同的实践 1.流程设计:指设计现有及新的流程这一任务; 2.流程执行:执行自动化序列的相关事件,这些事件涉及软件流程以及/或者人为活动; 3.流程监控:观察及审查单个流程的状态,以便这些流程的统计数字和性能可以加以记录、报告及优化。 BPM力求让软件工程师们能够与业务分析人员共享同样的概念和框架,因而,软件开发商试图创建这样的工具,让企业可以通过使用图形建模工具、特定领域的语言和专有应用软件,获取、设计及优化业务流程。 JBoss jBPM 3.0提供了这样的功能:使用业务流程执行语言(BPEL)、灵活而且可插入的应用编程接口(API)、本地流程定义语言以及图形建模工具,利用基于行业标准的编制机制开发新的自动化业务流程和工作流。 JBoss jBPM是采用开放源代码(LGPL许可证)的框架,包括了Java API、工具和定义语言,可以充当Web应用或者独立的Java应用。JBoss jBPM相当于业务分析人员和开发人员之间的中介,为他们提供了名为jPDL的通用流程定义语言。 JBoss jBPM架构综述 JBoss jBPM定义了使用JBoss流程定义语言编写的文件里面的流程定义。jPDL是一种面向图形编程(GOP)的语言,它基于节点、转换和动作组成的模型。在这种模型里面,节点是在流程定义过程中彼此相遇时执行的命令。转换负责指导流程定义的执行过程,而动作在节点或者转换事件发生时执行特定逻辑。 在jBPM中,流程定义被封装成流程档案(process archives)。流程档案被传送到jPDL流程引擎加以执行。jPDL流程引擎负责遍历流程图、执行定义的动作、维持流程状态,并且记录所有流程事件。 JBoss jBPM在以下组件里面进行封装: ● 流程引擎: 该组件通过下列委托组件(delegate component)来执行定义的流程动作、维持流程状态,并记录所有流程事件:请求处理程序、状态管理程序、日志管理程序、定义加载程序、执行服务。 ● 流程监管器: 该模块跟踪、审查及报告流程在执行时的状态。 ● 流程语言: 流程定义语言(jPDL)基于GOP。 ● 交互服务: 这些服务把遗留应用提供成流程执行时所用的功能或者数据。 图1表明了这些组件之间的关系。 如图1所示,含有动作处理程序的jBPM流程定义由jBPM流程引擎加以加载及执行。 如果流程引擎在流程定义过程中遇到拥有相关动作的节点,所有相关的动作处理程序就会被调用。动作处理程序是Java代码的实例,在执行时能够与外部系统进行交互。 下面就是简单的动作处理程序的示例: import org.jbpm.graph.def.*; import org.jbpm.graph.exe.*; public class MyActionHandler implements ActionHandler { public void execute(ExecutionContext executionContext) { System.out.println("MyActionHandler has executed: " + executionContext); } } 流程档案里面的PDL文件名为process-definition.xml。该文件含有诸流程的正式描述。以下示子表明了process-definition.xml文件的例子: < ?xml version="1.0" encoding="UTF-8"?> < !DOCTYPE process-definition PUBLIC "-//jbpm/jBPM Mapping DTD 2.0//EN" "http://jBPM.org/dtd/processdefinition-2.0.dtd"> < process-definition name="purchase process"> < !--...--> < !-- START-STATE --> < start-state name="request a purchase"> < transition to="evaluating"/> < /start-state> < !-- NODES --> < state name="evaluating"> < !--...--> < transition name="approve" to="purchase approved"/> < transition name="disapprove" to="done"/> < /state> < fork name="purchase approved">< transition to="decrement inventory" />< transition to="increment revenue" /> < /fork> < state name="decrement inventory"> < !--...--> < transition to="join" /> < /state> < state name="increment revenue"> < !--...--> < transition to="join" /> < /state> < join name="join"> < transition to="done" /> < /join> < !-- END-STATE --> < end-state name="done" /> < /process-definition> 流程定义基于定向图(directed graph)。有向图由节点、转换、一个起始状态以及一个终止状态组成。每个节点的类型定义了该节点的运行时行为。流程定义在执行时,以下实体就会起到作用: ● 流程实例: 流程实例是流程定义的一次执行。 ● 标记: 标记是一条执行路径。标记是运行时概念,它含有指向定向图中节点的指针。一旦创建了流程实例,就会创建主要执行路径的标记。该标记名为流程实例的根标记,它位于流程定义的起始状态。 ● 信号: 信号指示标记继续由转换实现的图像执行。 ● 节点: 节点负责图像执行的继续进行。如果标记进入节点,节点就会执行。不会传播执行的节点被认为是状态节点。 ● 动作:动作是流程执行过程中出现事件时执行的Java代码的实例。主要的事件类型有:“进入节点”、“离开节点”和“进行转换”。 图2 jBPM图形建模设计器 使用jBPM图形建模设计器,就很容易创建流程定义。设计器目前作为Eclipse插件而安装。图2表明了图形建模设计器的示例屏幕。 图形设计器可以用来创建流程定义、把动作处理程序与事件连接起来、编辑定义来源、创建流程档案、测试流程定义,等等。 部署JBoss jBPM JBoss jBPM把流程定义存储在数据库中。因而,把流程部署到JBoss jBPM里面需要解析process-definition.xml,并且把它存储在JBoss jBPM数据库中。可通过以下方法实现这项工作: ● 使用由JBoss jBPM提供的par Ant任务来创建流程档案。 ● 使用deploypar实用程序。该实用程序还可以创建流程档案,并且把流程档案部署到jBPM数据库上。Deploypar实用程序把jBPM.properties文件作为一个属性。该文件指定了配置选项,其中包括流程档案所要部署到的那个数据库。 ● 通过编程解析process-definition.xml,并把它存储到数据存储区中。 让JBoss jBPM完成一些简单步骤 JBoss jBPM充当编制引擎,它位于企业应用的中间,能够实现不同应用之间的集成和协调。 本文使用随同jBPM交付的示例部署,讨论如何利用jBPM和jPDL来创建及修改具有Web功能的简单的订单处理系统。 1.下载JBoss jBPM JBoss jBPM入门套件(http://www.jboss.com/products/jBPM/downloads)包括了执行JBoss jBPM所需的一切,只是没有Java开发者工具包(JDK)。JBoss jBPM入门套件里面的JBoss应用服务器需要J2SE 1.4或者更新版本。 下载入门套件后,把它解压缩到选择的目录下面。一旦解压缩了该入门套件,就有了类似如下的目录结构: ● Jbpm-starters-kit-3.1。 ● Jbpm:含有JBoss jBPM产品的源代码。 ● jbpm-bpel:含有JBoss jBPM的BPEL扩展件方面的信息。 ● jbpm-db:含有把JBoss jBPM连接到其他数据库的示例配置。 ● jbpm-designer:含有用于JBoss jBPM可视化流程设计器的Eclipse插件。 ● jbpm-server:含有JBoss应用服务器和JBoss jBPM引擎以及示例流程。 2.执行JBoss jBPM引擎 为了启动部署了jBPM的JBoss应用服务器,进入jbpm-server目录,执行里面的启动脚本。命令窗口会随同jBPM控制台窗口一起出现,类似图3。 现在,打开浏览器窗口,输入http://localhost:8080/jbpm。就会看到JBoss jBPM的示例Web应用的登录页面。 以cookie monster用户的身份登录,选择“创建新的Web销售订单”链接。这会创建预制的“Web销售”流程的新实例,如图4所示。 实际的定义文件:processdefinition.xml位于websale.par里面,如以下列表所示: < ?xml version="1.0"?> < process-definition name="websale" xmlns="urn:jbpm.org:jpdl-3.1"> < !-- SWIMLANES (= process roles) --> < swimlane name="buyer" /> < swimlane name="salesman"> < assignment expression="user(ernie)" /> < /swimlane> < swimlane name="accountant"> < assignment expression="user(bert)" /> < /swimlane> < swimlane name="shipper"> < assignment expression="user(grover)" /> < /swimlane> < !-- NODES --> < start-state name="create new web sale order"> < task swimlane="buyer"> < controller> < variable name="item"/> < variable name="quantity"/> < variable name="address"/> < /controller> < /task> < transition to="evaluate web order" /> < /start-state> < task-node name="evaluate web order"> < task swimlane="salesman"> < timer duedate="20 seconds" repeat="10 seconds"> < action class="org.jbpm.websale.RemindActor"> < swimlaneName>salesman< /swimlaneName> < /action> < /timer> < controller> < variable name="item" access="read"/> < variable name="quantity" access="read"/> < variable name="address" access="read"/> < variable name="comment"/> < /controller> < /task> < transition name="ok" to="salefork" /> < transition name="more info needed" to="fix web order data" /> < /task-node> < task-node name="fix web order data"> < task swimlane="buyer"> < controller> < variable name="comment" access="read"/> < variable name="item" /> < variable name="quantity" /> < variable name="address" /> < /controller> < /task> < transition to="evaluate web order" /> < /task-node> < fork name="salefork"> < transition name="payment" to="wait for money" /> < transition name="shipping" to="ship item" /> < /fork> < task-node name="wait for money"> < task swimlane="accountant"> < controller> < variable name="item" access="read" /> < variable name="quantity" access="read" /> < variable name="address" access="read" /> < variable name="money received" /> < /controller> < /task> < transition to="update books" /> < /task-node> < node name="update books"> < action class="org.jbpm.websale.UpdateBooks"> < msg>accountancy application is now informed of the payment< /msg> < /action> < transition to="salejoin" /> < /node> < node name="ship item"> < action class="org.jbpm.websale.ShipItem"> < swimlaneName>shipper< /swimlaneName> < msg>${shipper} now ships ${item} to ${address}< /msg> < /action> < transition to="salejoin" /> < /node> < join name="salejoin"> < transition to="end" /> < /join> < end-state name="end" /> < /process-definition> 一旦“创建新的Web销售订单”页面加载完毕,填写“物品”和“数量”表格字段,其中cookie作为物品,1作为数量。然后选择“保存”和“结束任务”,即可完成“创建新的Web销售订单”任务。Web应用会向jBPM发出信号,要求把Web销售流程标记转移到“评估Web订单”任务,然后使用输入数据作为流程变量。这时候,登录页面会再次显示。你会注意到这样一则消息:“新的任务已分配给'ernie’。” 现在,选择“以另一个用户登录”链接、以ernie的身份登录。你会注意到:“评估Web订单”任务呈高亮显示。把所需地址输入到注释字段,然后选择“需要更多信息”按钮。这样就可以把流程标记转移到“修复Web订单数据”任务,并且让浏览器回到登录页面。 这时候,可以以cookie monster用户的身份登录,选择“修复Web订单数据”链接,就可以查看高亮显示的任务如何变化、体现流程标记在执行流程中的位置。 重复流程定义在浏览器窗口中显示的步骤,出现提示时以相应用户的身份登录,完成填写每项新任务的表格。你最终会进入到流程末端,这时就会出现屏幕,告诉你流程已完成。 Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1793775 March, 2008 IReport编码问题解决(转)一、在ireport直接生成中的解决方法 1、mysql数据库的字符编码不变,采用默认编码; 2、ireport的数据库设置中用以下方式: jdbc:mysql://localhost:3306/database?user=&password=&useUnicode=true&characterEncoding=GB2312 3、 text field expresssion 中写入以下字符转换代码: new String($F{name}.getBytes("iso-8859-1"),"gb2312") 注:如果是在web中应用,就不需要字符集转换,直接写$F{name}就可以了,但是在web应用中的数据库连接写法中应照2的写法 4、i18n中的设置可以是UTF-8或者gb2312 二、移植到web(jsp)的应注意的事项 (1)找不到报表文件 三、生成PDF注意事项 以下引用:http://blog.csdn.net/CloneIQ/archive/2007/01/05/1474938.aspx 1 运行环境 February, 2008 如何使用Oracle 的imp 和 exp 导入,导出数据exp的参数供你参考:userid 执行导出的帐户的用户名/口令,如果这是exp命令后的第一个参数,则关键字userid就不必指定 buffer 用于获取数据行的缓冲区尺寸,缺省值随系统而定,通常设为一个高值(>64000) file 导出转储文件的名字 filesize 一个导出转储文件的最大尺寸。如果file条目中列出了多个文件,将根据filesize设置值导出这些文件 compress 一个Y/N标志,用于指定导出是否应把碎片段压缩成单个盘区。这个标志影响将存储到导出文件中的storage子句 grants 一个Y/N标志,用于指定数据库对象的权限是否导出 indexes 一个Y/N标志,用于指示表上的索引是否导出 rows 一个Y/N标志,用于指示行是否导出。如果设置为N,在导出文件中将只创建数据库对象的DDL constraints 一个Y/N标志,用于指示表上的约束条件是否导出。 full 若设为Y,执行Full数据库导出 owner 导出数据库帐户的清单;可以执行这些账户的User导出 tables 导出表的清单,可以执行这些表的Table导出 recordlength 导出转储文件记录的长度,以字节为单位。除非是在不同的操作系统间转换导出文件,否则就使用缺省值 inctype 要执行的导出类型(允许值为COMPLETE(缺省)、CUMULATIVE和INCREMENTAL),导出类型将在下几节描述 direct 一个Y/N标志,用于指示是否执行Direct导出。Direct导出在导出期间绕过缓冲区,从而大大提高导出处理的效率 record 用于Incremental导出,这个Y/N标志指示一个记录是否存储在记录导出的数据字典表中 parfile 传递给Export的一个参数文件名。这个文件可以包含这里列出的全部参数条目 statistics 这个参数指示导出对象的analyze命令是否应写到导出转储文件上。其有效值是 COMPUTE、ESTIMATE ( 缺省)和N。在较早的Oracle版本中,这个参数叫作ANALYZE consistent 一个Y/N标志,用于指示是否应保留全部导出对象的读一致版本。在Export处理期间,当相关的表被用户修改时需要这个标志 log 一个要写导出日志的文件名 feedback 表导出时显示进度的行数。缺省值是0,所以在一个表全部导出前没有反馈显示 point_in_time_recover 一个Y/N标志,用于向Oracle指示,是否正在导出用于表空间时间点恢复的元数据。这是个高级恢复技术 recover_tablespaces 在表空间时间点恢复期间,其元数据应被导出的表空间 query 导出时用于每个表的where子句 transport_tablespace 如果正在使用Oracle8i的可移动表空间选项,就设置成Y。和关键字tablespace一起使用 tablespaces 移动一个表空间时应导出其元数据的表空间 例如:exp system/manager file=export.dmp compress=Y owner=(HR,THUMPER) IMP的参数供你参考: userid 需执行导入操作的帐户的用户名/口令。如果这是imp命令后的第一个参数,就不必指定userid关键字 buffer 取数据行用的缓冲区尺寸。缺省值随系统而定;该值通常设为一个高值(>100000) file 要导入的导出转储文件名 show 一个Y/N标志,指定文件内容显示而不是执行。 ignore 一个Y/N标志,指定在发出Create命令时遇到的错误是否忽略。若要导入的对象已存在,就使用这个标志 grants 一个Y/N标志,指定数据库对象上的权限是否导入 indexes 一个Y/N标志,指定表上的索引是否导入 constraints 一个Y/N标志,指定表上的约束条件是否导入 rows 一个Y/N标志,确定行是否导入。若将其设为N,就只对数据库对象执行DDL full 一个Y/N标志,如果设置为Y,就导入Full导出转储文件 fromuser 应从导出转储文件中读取其对象的数据库帐户的列表(当Full=N时) touser 导出转储文件中的对象将被导入到的数据库帐户的列表。fromuser和touser不必设置成相同的值 tables 要导入的表的列表 recordlength 导出转储文件记录的长度,以字节为单位。除非要在不同的操作系统间转换,否则都用缺省值 inctype 要被执行导入的类型(有效值是COMPLETE[缺省]、CUMULATIVE和INCREMENTAL) commit 一个Y/N标志,确定每个数组导入后Import是否提交(其大小由BUFFER设置),如果设置为N,在每个表导入后都要提交Import。对于大型表,commit=N需要同样大的回滚段 parfile 传递给Import的一个参数文件名,这个文件可以包含这里所列出的全部参数的条目 indexfile 这是个非常有效的选项,可以把所有的create table、create cluster和create index命令写到一个文件中,而不是运行它们。几乎所有的create index命令都要改为注释。 这个文件在以index=N导入后就可以运行(进行少量修改)。这对把表和索引分别放在不同的表空间中非常有用。 charset 在为v5和v6执行导入操作期间使用的字符集(过时但被保留) point_in_time_recover 一个Y/N标志,确定导入是否是表空间时间点恢复的一部分。这是一个高级恢复技术 destroy 一个Y/N标志,指示是否执行在Full导出转储文件中找到的create tablespace命令(从而破坏正在导入的数据库数据文件) logImport日志将要写入的文件名 skip_unusable_indexes 一个Y/N标志,确定Import是否应跳过那些标有unusable的分区索引。可能要在导入操作期间跳过这些索引,然后用人工创建它们以改善创建索引的性能 analyze 一个Y/N标志,指示Import是否应执行在导出转储文件中找到的analyze命令 feedback 表导入时显示进展的行数。缺省值为0,所以在没有完全导入一个表前不显示反馈 tiod_novalidate 使Import能跳过对指定对象类型的确认。这个选项通常与磁带安装一起使用。可以指定一个或多个对象。 filesize 如果参数FILESIZE用在Export上,这个标志就是对Export指定的最大转储尺寸 recalculate_statistics 一个Y/N标志,确定是否应生成优化程序统计 transport_tablespace 一个Y/N标志,指示可移植的表空间元数据被导入到数据库中 tablespaces 要传送到数据库中的表空间名字或名字清单 datafiles 要传送到数据库的数据文件清单 tts_owner 可移植表空间中数据拥有者的名字或名字清单 例如:imp system/manager file=export.dmp buffer=64000 commit=Y February, 2008 项目管理的三个重要概念检查点(CheckPoint) 每个一个固定的时间检查项目进度与项目计划之间的差异 典型的表现为项目小组周例会;
里程碑(MileStone) 细化一个大的工作的粒度,每一个粒度的工作量的完成称之为一个里程碑 例如实现一个功能模块将功能模块细化为:需求讲解、设计、设计确认、实现
基线(BaseLine) 将一个或者一组配置项在生命周期的不同时间点上,通过正式评审后进入正式的受控状态; 基线控制的配置项是以前一基线为基础,完成预期目标(一个大的里程碑,通过正式评审),而且作为后续工作的基础 通常为高层的阶段性汇报;
我想基线是某个阶段时一些配置段的集合,如项目计划和准备阶段时一些项目计划经过评审即形成基线。 February, 2008 浅谈Hibernate的flush机制(转)随着Hibernate在Java开发中的广泛应用,我们在使用Hibernate进行对象持久化操作中也遇到了各种各样的问题。这些问题往往都是我们对Hibernate缺乏了解所致,这里我讲个我从前遇到的问题及一些想法,希望能给大家一点借鉴。 January, 2008 以Web2.0的名义(转)互联网最不缺的就是概念。但像Web2.0这样受人追捧,却难以准确定义的概念还真不多。 互联网最不缺的就是概念。但像Web2.0这样受人追捧,却难以准确定义的概念还真不多。尽管如此,有了新概念还是一定要往上靠的,这也是互联网从诞生起就惯有的传统。 先是号称向Web2.0转型的猫扑网获得了美国风投AccelPartners的千万美元注资;之后,是爱赶时髦的搜狐宣布将搜狐IT升级到2.0版本;而“博客中国”更名为“博客网”,更是拿Web2.0来说事儿的。 按照该公司董事长兼CEO方兴东的话来说,目前的互联网已然开始了2.0时代,“全球所有的互联网巨头都开始向2.0时代进军,整个互联网的发展接下来会进入一场新的竞争乃至新的战争!” 那么,带来如此重大变革、意义如此深远的Web2.0,到底是什么呢?它是一种新的技术么?与之前的Web1.0相比,它又有什么不同? 方兴东认为,与Web1.0最大的不同就是Web2.0所提倡的个性化,在其中,个人不是作为被动的客体而是作为一种主体参与到了互联网中,个人在作为互联网的使用者之外,还同时成为了互联网主动的传播者、作者和生产者。 到目前为止,关于Web2.0并没有清晰的定义。业内普遍的一种说法是,微内容是Web2.0的一个关键词。其中,微内容包括个人所形成的任何数据:比如一则网志、一个评论、一幅图片、收藏的书签、喜好的音乐列表、想结交的朋友等等。这些微内容,充斥在了人们的生活、工作和学习的方方面面。而Web2.0重点要解决的正是对这些微内容的重新发现和利用。于是,我们看到了这样的现象,只要是和微内容相关的技术和架构,都是以Web2.0为名义的。 也有人这么界定,Web2.0是相对Web1.0的新的一类互联网应用的统称。并且由此断言,由Web1.0单纯通过网络浏览器浏览html网页模式向内容更丰富、联系性更强、工具性更强的Web2.0互联网模式的发展已经成为互联网新的发展趋势。就目前的发展情况而言,Web2.0是以 Flickr、Craigslist、Linkedin、Tribes、Ryze、 Friendster、Del.icio.us、43Things.com等网站为代表,以Blog、TAG、SNS、RSS、wiki等应用为核心,依据六度分隔、xml、ajax等新理论和技术实现的互联网新一代模式。 无论怎样的注脚,我们都不难发现,这个Web2.0的概念,在很大程度上并不是从技术创新角度出发的,或者说与技术没有很大的相关性。如果从应用的角度看,Web2.0或许更好理解一些,它就像一个带着光芒的大筐,凡是于以往集中式互联网不同的模式或者技术,都可以框在其中。 Web1.0到Web2.0的转变,具体的说,从模式上是从读向写、信息共同创造的一个改变;从基本结构上说,则是是由网页向发表/展示工具演变;从工具上,是由互联网浏览器向各类浏览器、rss阅读器等内容发展;运行机制上,则是自“Client Server”向“Web Services”的转变;由此,互联网内容的缔造者也由专业人士向普通用户拓展。说白了,Web2.0的精髓就是以人为本,提升用户使用互联网的体验。这样远大的理想,这样庞大的范畴,也难怪新锐们凡是都要以Web2.0为名义了。 December, 2007 10年跳槽经验总结 高级人才不用找工作(转)首先,真正的高级人才是不用找工作的,因为只有被工作找的份。 December, 2007 免费午餐已经结束——软件历史性地向并发靠拢(转)免费大餐不久就将结束。对此,你有何打算,做好下一步准备了么? 对主要的处理器厂商以及架构,包括Intel、AMD和Sparc、PowerPC[译注1]来说,改善CPU性能的传统方法,如提升时钟速度和指令吞吐量,基本已走到尽头,现在开始向超线程和多核架构靠拢。而且这两个特性(特别是多核)已经在部分芯片实现,如PowerPC和Sparc IV;Intel和AMD也将在2005年内赶上。2004年In-Stat/MDR秋季处理器论坛[译注2]的主题就是多核设备,很多公司都展示了改进和新研发的多核处理器。不过,要将2004年称为多核年,显然还不够理直气壮。 多核将引领软件研发发生基础性变化,特别对接下来几年里那些面向一般应用、运行在PC和低端服务器上的应用软件(在今天已经销售出去的软件里占有很大比例)而言。在这篇文章里,我想就多核为何突然对软件产生重要影响,以及并发巨变如何影响我们和我们未来编写软件方式的问题展开讨论。 我可以这么说:免费大餐已经结束一两年了,但我们现在才开始意识到这个问题。 原文作者: Herb Sutter 原文链接:原文出http://www.gotw.ca/publications/concurrency-ddj.htm 免费的性能大餐业界存在一个有趣的现象:“安迪送,比尔取。”[译注3]无论处理器性能提升多少,软件都有办法迅速吞噬。CPU速度十倍于前,软件就有十倍于前的活要干(或者肆无忌惮猛增软件的工作量,导致性能下降)。在过去几十年里,由于CPU、内存和硬盘特别是CPU厂商强力推进主流系统向更新更快的方向发展,大多数软件不做版本升级,甚至原封不动,就可轻松而持续地享受处理器性能提升的成果。尽管时钟速度不是衡量系统性能的唯一和最好的标尺,但其重要意义不容忽视。我们见证了CPU的发展历史:从500MHz到1GHz,然后再到2GHz,不断提高。今天,主流计算机已经进入3GHz时代。 不过,有一个很关键的问题:这种提升模式什么时候会走到尽头?尽管莫尔定律预言了历史上的指数式增长,但我们很清楚指数式增长不可能永远维持,因为硬件毕竟受物理极限约束;光速是不可能更快的[译注4]。所以增长必然放缓,最后停滞。顺便说明一点,莫尔定律的主要描述对象是晶体管集成密度,但在一些相关的领域,如时钟速度方面,也出现了类似的指数式增长;甚至在别的领域有更快的增长速度,例如著名的数据存储量爆炸。不过这些重要趋势需要另一篇文章来分析了。 如果你是一个软件开发人员,那么你可能一直在免费享受桌面计算机性能提升的大餐。某些操作会成为应用程序性能的瓶颈?“你过虑了”,我们对这样的回答耳熟能详,“未来处理器将更为强劲,而现在的应用程序速度倒是日益被非CPU吞吐量和内存速度因素扼杀,比如I/O、网络和数据库等等。”真的是这样么? 要在过去,这的确没错。但在以后,就完全不对了。 我有两个消息要告诉大家。第一个是好消息,处理器性能仍然会不断提高。第二个则是坏消息,至少在短时间内,处理器性能的提升,不再能像以往那样让现在的应用程序继续免费获益。 过去30年里,CPU设计者主要从三个方面提高CPU性能,头两个就是从线性执行流程上考虑的: 1、时钟速度 2、执行优化 3、缓存 提升时钟速度将增大单位时间的时钟周期数。让CPU跑得更快,就意味着能让同样工作或多或少更快完成。 优化指令执行,可以在每个时钟周期内完成更多工作。目前的CPU中,一些指令被不同程度地做了优化,如管线、分支预测、同一时钟周期内执行更多指令,甚至指令流再排序支持乱序执行等[译注5]。引入这些技术的目的是让指令流更好、更快执行,降低延迟时间,挖掘每一时钟周期内芯片的工作潜能。 在这里,有必要对指令再排序作个简单说明。我刚才提到的部分指令优化手段,其实已远非普通意义上的优化。这些优化可能改变程序原意,造成程序不响应程序员的正常要求。这可是个大问题。CPU设计师都是心智健全且经过严格训练的好同志,正常情况下,他们连苍蝇都不愿伤害,自然也无意破坏你的程序。而在最近几年里,尽管知道指令重组有破坏程序语义的风险,但为了提升每个时钟周期内的工作效率,他们已经习惯于积极开展这类有风险的优化工作。难道海德先生[译注6]复活了?当然不是。这种积极性清楚表明,芯片设计师承受了交付速度更快CPU的巨大压力;在这种压力下,为了让软件跑得更快,他们不得不冒改变程序意思,甚至应用崩溃的风险。拿两个有名的例子来说——写操作再排序和读操作再排序[译注7]。允许处理器对写操作再排序是非常令人吃惊的,让大多数程序员意外,一般来说这个特性必须关闭,因为在写操作被处理器武断地再排序条件下,程序员很难保证程序正确执行。读操作再排序也有明显的问题,但大多数情况下这个特性是开启的;因为相对来说它更容易把握一些,而且人们对性能的要求,让操作系统和操作环境 设计师只能选择让程序员在一定程度吃点苦头,毕竟,这比直接放弃性能优化机会的罪责小一些。 第三个是增大与RAM分离的片内高速缓存。RAM一直比CPU慢很多,因此让数据近可能靠近处理器就很重要——当然那就是片内了[译注8]。片内缓存持续飚升了很多年,现在的主流芯片商出售的CPU都带有2M甚至更高的二级缓存。值得一提的是,今后,三种提升CPU性能的传统手段里,增加缓存将硕果仅存。我会在后面更详细说明缓存的重要性。 我写这么多的意思是什么呢? 最重要的是我们必须认识到,传统性能提升方法与并发没有直接关系。过去任何方法带来的速度提升,无论是顺序(非并行的单线程或单进程)、还是并发执行的程序,都能直接受益。这点很重要,我们目前大量的程序都是单线程的,而且在未来仍然有重要的存在价值。 当然,适当时候,我们重新编译程序,可以利用CPU的新指令(如MMX、SSE[译注9])和新特性提升系统性能。但总的来说,即使放弃使用新指令和新特性,不做任何更改,老程序在新CPU也会跑得更快,让人心花怒放。 曾经的世界是这般美好,可如今,她就要变了颜色。 为什么我们今天没有10GHz芯片
其实,CPU性能提升在两年前就开始碰壁,但大多数人到了最近才有所觉察。 我这里有份来自Intel的数据(当然你可以从其他厂商得到类似数据)。图中反映了Intel芯片的时钟速度和晶体管集成规模演变历史。晶体管集成数至少就目前而言仍在继续上升,但时钟速度的情况就不同了。 我们从图中可以看到,大概在2003年初,一路高歌猛进的CPU时钟速度突然急刹车。受制于一些物理学问题,如散热(发热量太大且难以驱散)、功耗(太高)以及泄漏问题[译注10]等,时钟速度的提升已经越来越难。 你目前在工作站上用的CPU时钟速度是多少?10GHz么? 2001年8月Intel芯片就达到2GHz,按照2003年前的CPU发展趋势推算,到2005年初,我们就能拥有第一块10GHz的Pentium芯片。但实际上没办到。而且情况好像越来越糟——我们根本就不知道到底在什么时候这样的芯片可以出现。 那么放低期望,4GHz又如何呢?目前我们已到3.4GHz——那么4GHz已经不远了吧?唉,好像4GHz也遥不可及。可能你知道,Intel首先于2004年中将4GHz芯片的发布时间推迟到2005年,而到了2004年秋季,则彻底取消了4GHz计划[译注11]。在本文写作的同时,Intel宣布计划到2005年早期,实现到3.73GHz(即图中的右上最高处)的微量提升。所以,至少就目前来说,时钟速度的竞赛实际上结束了,Intel和其他大多数处理器厂商将把旺盛的精力投入到多核等方向去。 也许,我们某天在主流PC里能装上4GHz的CPU,但2005年别想。Intel实验室里的确已经有运行在更高速度的芯片——不过代价是惊人的,比如庞大数量的冷却装置。你想不久在你的办公室里就有这样的冷却设备,坐飞机的时候,就把它们放在你膝盖上?别做梦了! 莫尔定律与新一代处理器“没有免费的午餐。”——摘自R. A. Heinlein的小说《The Moon Is a Harsh Mistress》。 莫尔定律玩完了?这个问题很有趣,严格地讲,还不能这么说。尽管和所有的指数式增长方式一样,莫尔定律总有一天会走到尽头,但最近这些年,还没有这样的危险。芯片工程师在榨取时钟周期内剩余价值时的确碰了壁,不过晶体管集成量仍在暴涨,所以从这个角度说,CPU近期仍将遵循莫尔定律,系统吞吐量继续提高。 关键的变化,即本文的中心,是今后几代处理器性能提升所走的道路将完全不同。同时,大多数现在的应用软件将不再可能不作大规模重构,就能像过去那样从处理器免费获益。 接下来数年里,新型芯片的性能提升将主要从三个方面入手,其中仅有一个沿袭是过去的: 1、超线程 2、多核 3、缓存 超线程,是指在单个CPU内,并行两个或多个线程。超线程CPU已经发布了,支持并行执行一些指令。不过这种CPU还是存在短板,虽然给它增加了部分硬件如寄存器,但它和绝大多数普通CPU一样,缓存、整数和浮点运算器仍然是唯一的。有资料表明,写得较好的多线程应用,在超线程CPU上能获得5%-15%的性能提升;假设趋于理想状态,即多线程程序写得好到极点,那么性能可以提高40%。不错了,不过还是做不到成倍提升,而且对单线程应用毫无帮助。 多核,主要是指在一块芯片上运行两个或多个处理器。部分芯片如Sparc和PowerPC目前已经推出了多核版本。Intel和AMD也计划在2005年内初步实现,具体时间取决于它们的系统集成水平,功能则是一样的。AMD初期在性能设计可能更具优势,如更好的支持功能单片内集成,而Intel基本上就打算将两颗Xeon胶合在一块片子上了事。所以刚开始的时候,这种双核芯片与一个真正的双CPU系统在性能几乎没有差别,仅仅在价格上前者更为便宜,毕竟它的主板上不需要两个插槽和额外胶合件;另外,即便理想状态,这种架构也无法达到双倍速度,且无益于单线程应用,而只有写得较好的多线程应用能得到好处。 最后一个是片内缓存,还能像预期那样在近期继续上升。三个方法中,仅有这个可以让现有应用全面受益。片内缓存有令人难以置信的重要性和对大多数现有应用的超高价值,原因很简单,那就是“空间就是速度”。CPU和主存交互的代价是巨大的,如果能避免,那就尽量不要和它打交道。在目前的系统里,从主存获取数据所花时间,通常是从缓存获得数据的10到50倍。很让人吃惊吧,因为很多人都以为内存已经足够快。其实这不过是与硬盘和网络相比,而不是运行在更高速度的片内缓存。应用程序的工作与缓存间的适配程度,和我们是荣辱与共的。很多年来,不重构程序,仅仅提高缓存大小就拯救了现有应用,给它们带来新生。软件操纵的数据和为新增功能而加入的代码越来越多,性能敏感的操作必须继续与缓存适配。套用经济大萧条时期老人常念叨的一句话:“缓存为王。” 顺带说件发生在我的编译器小组的趣事,算是“空间就是速度”的一个佐证。32位和64位编译器将同样的代码分别编译成32位和64位程序。64位CPU有多得多的寄存器和其他代码优化特性,因此运行其上的64位编译器先天的获得极大性能提升。这当然很好。而数据的情况又如何呢?换到64位平台上,内存中绝大部分数据的大小并未发生变化,唯一例外的就是指针,指针占用了两倍于以前的空间。因此,我们的编译器和绝大多数32位应用相比,挥舞指针就费力得多。现在的指针耗用8个而不是4个字节,空间净增加,结果我们发现64位编译器的工作集[译注12]大小显著增加。工作集增大导致性能下降,差不多抵消了更快的CPU和更多寄存器带来的性能优势。就在我写这篇文章的时候,64和32位编译器正以同样的速度运行,尽管程序代码完全一样而且64位处理器先天能力更强。这就是“空间就是速度”。 缓存能,但超线程和多核CPU对现在的绝大多数应用,几乎不会有任何影响。 综上所述,硬件的变化到底会给软件开发方式带来怎样的影响呢?你可能已经有了初步答案了。让我们更深入研究,明白其厉害所在。 对软件来说,这意味一次巨变上世纪90年代初,我们开始学着理解对象。在主流软件开发领域里,从结构化到面向对象编程是过去20甚至可以说30年来最重要的变革。这期间也发生了其他一些变化,例如近来诞生的的确让人着迷的WebServices,但我们中绝大多数人在职业生涯里从未有过见识像面向对象那样基础而深刻改变软件开发方式的机会。 现在,机会来了。 也从现在开始,性能大餐就不再免费了。虽然托缓存增大的福,我们还能在半路上捡到普通的性能提升丸,但如果你希望你的应用程序在新的处理器里能继续获得爆炸性的性能提升,那就需要你好好编写并发程序了(通常是多线程的)。说比做容易啊,也不是所有问题都天生可以通过并行解决,而且并发编程的难度也是很大的。 肯定有人嚷嚷开了:“并发?并不是什么新鲜玩意嘛!人们不早就在写这样的程序了么?”是的,小部分程序员的确写过。 别忘了,至少从上世纪60年代晚期的Simula开始,人们就在写面向对象程序。但到了90年代,面向对象才成功发动革命并夺取统治地位。为什么呢?工业是受现实需求驱动的,为了解决越来越大的问题,必须编写越来越大的系统,这样的系统需要越来越强劲的CPU和存储设备支持,硬件系统也恰逢其时地逐步提供了这样的支持。面向对象编程擅长抽象和依赖管理,所以成为了开发经济、可靠和可重用的大型软件的必备利器。 并发编程差不多也有同样漫长的历史可以追溯,很早的时候,我们就开始编写协程、管程[译注13]以及其他与并发有关的东西。近10年来,我们也发现有越来越多的程序员在编写并发应用(有多线程的,也有多进程的)系统。但是发生整体转向性的巨变,目前还不具备条件,需假以时日。现有大量的单线程应用,仍然有巨大的存在价值,这点我会在后面说明。 说点题外话,当前“下一次软件开发革命”这样的词语多如牛毛,让大家眼花缭乱,其实这是商家宣传自己新技术所作的广告,不要理睬它。新技术通常都很吸引人,有时候也很有用,但软件开发方式的重大变革必然来源于在真正得到爆发式广泛应用前就存在并经过多年缓慢成长、先进而稳定的技术。这个过程是绕不掉的。变革所依赖的基础技术必须足够成熟(包括有固定的厂商和工具支持);通常,这个成熟稳定过程至少要花费7年的时间,新技术在广泛应用时才不会有潜在的性能悬崖和陷阱。所以,像面向对象这样的软件开发变革,也必须在各项技术经过多年甚至几十年磨砺后才能发生。即便在好莱坞,绝大多数的一夜成名,也仍然是多年努力后发生重大突破的表面象征。 并发将是软件开发史上的又一个重大变革。很多专家仍然在这个变革是否比面向对象还大的问题上争论不休。这样的争论最好还是留给学问家吧。技术工作者最感兴趣的是和面向对象一样,编程方式的变化程度、编程技术的复杂性和学习曲线问题。 并发之正反二面并发技术(特别是多线程)在主流软件里大多应用在两个方面。第一类是天然就彼此独立的、逻辑上分离的控制流程,比如在我设计的数据库复制服务器里,每个复制Session都放在各自的线程里,彼此完全独立的,不会工作于同一条数据记录上。第二类不像第一类那么常见。为了系统提升性能,像利用多CPU平台的能力,挖掘应用程序其他部分的潜能等,我们也会编写并发代码。在我的数据库复制服务器里,多个独立的线程在多CPU平台上就工作得很好。 然而,并发编程也是要付出代价的。一些很明显的问题相对来说无关紧要,比如锁定。对资源的锁定降低了系统的性能,但如果你能找到办法最小化甚至消除资源共享,让操作真正并行,从而明智得当地使用锁,那么从并发执行得到的收益,要远大于在同步上蒙受的损失。 更重要的问题,大概就是并非所有应用都适用并行。这点我会在后面说明。 应该说,最大的问题,就是并发编程本身的难度了。程序员必须将脑子里的编程模型转化为可靠的程序,这比实现顺序执行的传统程序难得多。 任何学习过并发的人都认为自己已经理解并发,早早结束寻找他们认为不可能但实际潜在的竞争冲突和他们其实仍没闹明白的问题。如果开发人员认真学习和思考并发编程,就会发现通过合理组织的内部测试能发现大多数的竞争冲突问题,这个时候,无论是在知识水平还是心情愉悦度上,他们都能达到一个新的高度。不过,除了经过理解为什么和怎么进行真正压力测试的行家测试过的、已经正式发布的软件,都会存在部分在普通测试中无法捕获的潜伏并发问题。这些问题只有在真正的多处理器系统上才会暴露出来,因为在这样的环境里,多个线程不是在单处理器上切换,而是真正的并发运行,大量新问题就会涌现。而偏偏又有很多人自以为已经真正理解如何编写并发程序,真是让人忐忑不安啊。我见过不少项目组,他们的程序在很多用户那里即便施以极端苛刻的压力测试,都能出色工作,但某天一个用户部署了真正的多处理器机器后,深层次的竞争冲突甚至程序崩溃问题马上出现。CPU发展到今天,重构你的应用,让它们多线程运行在真正的多核计算机上,的确像逼迫初学游泳的人一下子跳入深水——直达终点,似乎有点残忍,但只有真正并行的环境,才能更容易暴露出你的问题。再说了,即使你组织了一个能真正编写可靠并行代码的团队,也不能说就不会出现问题。例如,并发代码运行可能非常安全,但(在多核的机子上)却不比在单核的机子上跑得快。其典型原因就是线程未被合理分离,共享了单一资源,造成程序执行顺序化。这类问题是相当微妙而复杂的。 结构化程序员学习面向对象(什么是对象?什么是虚函数?我如何使用继承?知道“是什么”和“怎么办”外,还得问一句:“如何保证理论上正确的设计在实践中的正确性?”)是一个飞跃,同样的,顺序思维的程序员学习并发(什么是竞争冲突?什么是死锁?它是怎么出现的,我如何避免它?什么样的构造在我看来是并行的但实际上顺序化了程序?在“是什么”和“怎么办”外,还要回答同样的问题:“如何保证理论上正确的设计在实践中的正确性?”)也是一个飞跃。 现在的大量程序员并没有真正理解并发,就像15年前大量程序员没有真正理解对象一样。但并发编程模式是可以学习的,尤其是我们要坚持基于消息和锁的编程;一旦真正理解了并发,就会发现它并不比面向对象难多少,很容易觉得那是自然而然的。我们需要为我们自己和我们的团队做好训练投资和时间的准备。 有必要说明一点,我在上面故意将并发编程模式限定在消息和锁基础上。其实也有在语言级就直接支持的无锁编程,比如Java5和很流行的C++编译器。但对于程序员来说,无锁比有锁并发编程难得多。大多数情况下,只需要系统和库编写者理解无锁编程就行了,虽然事实上每个人都可以从无锁系统和库获益。老实说,即便有锁编程,也有点碰运气的味道呢。 对我们来说这到底意味着什么好了,回到正题,将问题归纳一下。 1、我们已经讨论清楚的、最重要结论是:如果应用程序想充分利用CPU吞吐增加量,那它们就必然日益需要并发,这种形势逐渐明朗,并将在接下来的数年里深入发展。Intel已经扬言未来他们会推出集成100颗内核的芯片,那么单线程应用最多就只能利用这种芯片1/100的潜在生产力。“哦,性能没那么重要吧,计算机总是跑得越来越快”的论调已经变得天真而可疑,甚至在未来不久将完全错误。 目前,并不是所有的应用都需要(或者更准确的说,只有应用中重要的作业才需要)并行。像编译这类的问题,是必须要考虑并行的,而其他则不一定。请看这个有趣的例子:一个女人需要九个月才能生产一个小孩,并不代表九个女人能花一个月生出一个孩子。你以前可能接触过类似的推导,但有没有感觉这个问题意犹未尽呢?如果有人再和你就此讨论,我向你推荐一个刁钻的问题:从这个命题你能断定“女人-小孩”是一个非并行问题么?通常,人们会下意识地认为它天然就不是一个并行问题,但实际上并不完全是这样。如果目的是生一个小孩,它的确是一个非并行问题;但如果是生产多个小孩,那么它就是一个标准的并行问题了!所以说,目标不同,结论就大相径庭。在考虑你的软件是否和如何使用并行时,千万别忘了面向目标原则。 2、可能不那么明显的结论是:CPU将很可能日益成为应用程序性能的瓶颈。当然,不是所有应用都会这样,目前还未明显受制于CPU能力的应用在未来虽然可能受到CPU影响,CPU也不会一夜之间成为它们的镣铐。但“I/O、网络和数据库瓶颈”似乎快走到谷底,因为在这些领域,条件仍在迅速改善(如GB级WiFi网络等等);而与此形成对比的是,CPU性能提升技术已走到峰点。请注意,我们的CPU目前在3GHz徘徊。所以,除了指望缓存在未来继续扩大(这可真是一个大好消息),现在的单线程应用不太可能跑得更快。其他方面的性能提升手段,虽然未来还可能继续发挥作用,不过已经无法和过去相提并论了。芯片设计师正在拼命寻找新办法提高管线利用率,降低数据加载延迟,但在这些领域,长在低枝的果子早已被摘光。而应用程序新需求的增加不但不稍事休息,反而神经质地加速猛冲。我们只好逼迫程序做更多的事情,而程序则只有威逼CPU,压垮它,除非程序能并发执行。 应对如此巨变,我们现在有两条路可以走。一是面向并发重构应用,二是勤俭持家,小心规划代码,让它吃更少的食,干更多的活。这就引出了第三个有趣的话题。 3、提升程序效率、优化其性能将越来越重要,而不是反道而行。已经高度重视性能优化的语言将获得重生,其他的语言赶紧奋起直追,朝着效率和优化努力吧。面对长期增长的需求,希望面向性能努力的语言和系统能为我们分忧。 4、最后一点,编程语言和系统将不得不尽快做好面向并发的准备。Java语言从一开始就支持并发编程,虽然还存在不少问题以致不得不发布多个后续版本提升并发程序的正确性和效率。C++长期以来被用于编写大型多线程系统,但它却没有对并发的标准支持(ISO C++标准甚至有意未提及线程[译注14] ),因此,并发目前只能在一些不可移植的特定平台和库基础上实现(而且实现还不够完善,比如静态变量只能初始化一次,这就要求编译器自动加锁,但很多C++的实现里并不生成锁)。另外,目前还存在多种并行编程标准,例如pthreads和OpenMP[译注15] ,其中一些支持隐式并行,另一些显式支持。让编译器分析单线程程序并自动生成并行代码的隐式并行方式当然美妙而优雅,不过这类自动转化工具的结果代码质量还无法与人工编写的显式并行代码媲美。目前的并发编程主要以锁为基础,这种方式也很难把握,常常要赌几分运气。总之,我们迫切需要一个比目前语言提供的更高抽象层次的、统一的并发编程模式。关于这点,以后我还有更多的话要说。 总结如果你以前对并发未加注意,那么现在是时候了,仔细分析应用的设计,挑出现在和不久就可能过于依赖CPU能力的操作,研究这些部分如何从并发得益。你和你的团队,现在也该深入学习和了解并发编程的要求、不足、风格和专业概念了。 少部分应用天然适用于并行,但大多数不是的。即便你知道程序受制于CPU的位置,可能也很难找到将这部分操作并行化的办法。所有这些问题,要求我们加快对并行的思考和研究。隐式并行编译器能帮点小忙,但不能指望太多,它不可能比得上尽你所能将顺序化程序转化为显式并行和多线程版本后的效果的。 感谢仍未停止的缓存扩大和管线少量优化,免费饭菜在今后还能有一点,不过从今天开始,餐馆无偿提供的只有小菜和饭后小点心了。菜谱上仍然有优质可口的鱼片,但现在要享受它就得付费——设计精细化、代码更复杂,而且要加倍测试。对于多数应用来说,这是个好消息,尽管要辛勤耕耘,但回报是丰厚的,因为并发可以让应用继续从处理器能力暴增中充分受益。 译注1 处理器发展历史上,精简指令集(RISC,Reduced Instruction Set Computer)阵营曾向微特尔(Wintel,Microsoft+Intel)阵营发动过三波声势浩大的微电脑盟主争霸战。 上世纪70年代以来,对处理器的要求更为全面,不单是提升速度就能满足,比如低耗能、小体积,加强数值运算、支持多媒体功能等。复杂指令集(CISC,Complex Reduced Instruction Set Computer)的微指令多且长度不统一,造成解码器线路复杂;加上受当时制造工艺的限制,如果在芯片上直接集成高速缓存和其他部件,体积和价格都将变得难以想象。 RISC应运而生。因为RISC指令精简、线路精简,所以芯片体积和能耗降低,腾出空间也可容纳更多寄存器;另外指令定长,通过硬件加速,还可提升效能。RISC学术界从一开始就分为两派:Berkeley RISC(伯克利大学RISC计划)和Stanford MIPS(斯坦福大学MIPS计划。MIPS,Microprocessor without Interlocked Pipeline Stages,无内部互锁流水级的微处理器。我国龙芯即MIPS架构)。 Sun公司引进了RISC技术,在此基础上制定了Sparc(Scalable Processor Architecture,可扩展处理器体系结构)微处理器体系结构规范,并于1985年推出了相应处理器。1988年,Sun领头组建了Sparc联盟,口号是“RISC + UNIX vs CISC + DOS”,藉此发动第一次争霸冲击。后因市场迟迟未见销量,加上Sun在技术上又留了一手,导致联盟最后只剩Sun自己和德州仪器。 1990年,MIPS研究开花结果,开发团队成立了同名公司MIPS,并于1991年建立了ACE(Advanced Computing Environment)联盟,主要成员有Digital(迪吉多)、SGI(Silicon Graphics,视算科技)和Compaq(康柏)。Compaq当时市场低迷,且Intel对Compaq展开了游说,因此Compaq最先退出了联盟;而Digital又忙于Alpha芯片的开发。主要成员离心离德,1992年,第二次联盟以SGI收购MIPS草草收场。 IBM从1975年开始精简指令集芯片研究,也就是后来的PowerPC。1992年,IBM与Motorola(摩托罗拉)、Apple Computer(苹果)宣布合组Power联盟。刚开始,Power联盟引用学界评比论文,直指x86复杂指令集结构无法应付未来信息需求,而媒体也纷纷发难斥责x86电脑效能低劣,操作介面不便,都表示支持大胆改革的Power联盟。形势危机,当时Intel的总裁葛洛夫甚至认为Intel已陷入死亡之谷,如果应对失措,“Intel”将成为历史名词。Power联盟洋洋得意。 但在接下来的三年里,Power联盟迟迟不能统一平台标准,操作系统开发进度也严重滞后。到1994年,Intel推出了Pentium芯片,微软的Windows 95也大功告成。尽管当时的Pentium逊于PowerPC,Windows 95界面也不如Macintosh,但前二者相互支援,前后兼容,不断改进,而后二者却不够统一,用户没有安全感。形势急转直下,Power联盟大跌眼镜。 第一波攻击,Sun退回到工作站;第二波攻击,SGI退回绘图工作站;第三波攻击至今,IBM还在服务端处理器领域靠PowerPC苦撑。 顺带说一句,Intel和AMD主要走的都是CISC路线。但处理器发展到现在,各方面技术已经相互融合,不存在绝对CISC或者RISC的芯片了。 译注2 In-Stat:www.instat.com,全球著名的行业研究机构,Reed Business Information出版集团成员公司之一,Reed Elsevier的战略组成部分;涉及半导体、电信和电子消费品等领域的研究、评估与预测。 MDR:MicroDesign Resources,原属美国Ziff-Davis电子出版集团,1999年被Reed Elsevier收购。 In-Stat/MDR主办《Microprocessor Report》(《微处理器报告》)杂志,三周刊;Microprocessor Forum(微处理器论坛)每年10月在加利福尼亚州San Jose举行。 译注3 原句为:Andy giveth, and Bill taketh away. Andy:Andy Grove,安迪·格鲁夫,1968年和罗伯特·诺宜斯(Robert Noyce)、戈登·摩尔(Gordon Moore)共同创立Intel。 Bill:Bill Gates,比尔·盖茨,1975年和保罗·艾伦(Paul Allen)创立Microsoft。 译注4 大概是2001年看到过一则新闻,到网上搜了一下,内容大致如下: 澳大利亚教授韦伯领导的研究小组利用位于夏威夷的世界最大的天文望远镜“凯克”观测17颗不同的类星体。由于这些类星体距离地球120亿光年,它们在宇宙形成初期发出的光线到今天才抵达地球。在长途旅行中,部分光线被星系间的气云吸收。光线的吸收情况既能反映星系气云的性质,也能反映出光的变化情况,这其中就包括光的速度以及决定光速的光谱线精细结构常数。结果,韦伯等人发现,精细结构常数发生了微小的变化。从理论上说,这意味着光速有可能发生过改变。消息公布后,不少物理学家对此发现持谨慎态度。韦伯及同事希望用位于智利的另一个大型天文望远镜来证实他们的结果,据称要得出最终结论尚需2至3年的时间。 不过到目前为止,似乎还没看到他们的最终结论。 译注5 管线:pipelining,或流水线。CPU的管线并不是数据输入输出使用的物理线路,而是指指令执行的流程。一条指令必须被分解为多个执行步骤,每个步骤占用一个时钟周期。例如最基础的管线是5级的:(1)取指令,(2)对指令译码,(3)演算出操作数,(4)执行指令,(5)将结果存储到高速缓存。前三步由指令控制器(ICU)完成,后两步由运算器(ALU或FPU)完成。管线可以细化,例如苹果的G4处理器采用了7级管线,AMD 2500+处理器10级,Intel公司的P3到10级,P4到20级,P4-E甚至高达31级。管线加长,则每级任务量减小,执行所需时间缩短,因此时钟周期可以缩短,即时钟速度加快。设管线为N级,时钟速度为TMIPS(T百万次/秒),那么平均完成一条指令所花费时间为N/T(当然要求芯片的确在每个周期内能完成各管线级的任务),因此理论上只要时钟速度加快,则芯片处理能力上升。但问题是管线执行时总有出错(如分支预测失败)的可能,一旦出错,整个管线就要全部清空,然后从第一级重新执行,在这种情况下,长管线的全部花费时间通常比短管线多。这就是部分AMD芯片比Intel相同甚至更高主频的芯片实际速度要快的原因。 分支预测:branch prediction。解决管线中条件转移引起管线停顿的问题。例如第一条指令是条件转移,那么需要等其判断结果出来后才能执行下一条指令,分支预测可预测判断结果,然后尽快执行其他指令,从而不致管线停顿。当然预测也有出错的时候,预测失败将导致管线清空,从头执行。目前预测准确度可达90%,进一步提高分支预测准确率是正在研究的重要课题。 乱序执行:out-of-order execution。解决管线中指令相关引起管线停顿的问题。后序指令需要正在执行指令的结果,因此无法立即处理后序指令,这叫做指令相关,造成其他处理单元的停顿,白白损失时钟周期。解决办法是立即找出其他不相关指令来执行,最后由重新排列单元将各执行单元的结果按原来指令顺序重新排列。很显然,乱序执行是有风险的。 译注6 出自美国电影《化身博士》(Dr. Jekyll and Mr. Hyde,有1931和1941两个版本)。故事讲述哲基尔医生相信每个人都同时拥有两极化的个性——好的一面与邪恶的一面。如果将这两种个性分开成为截然不同的两个人,这两个灵魂都将获得释放。他随后成功地用化学实验将自身邪恶的一面转化成为海德先生,此先生犯下了可怕的罪行。但当他想要停止用药时,却惊恐的发现一切为时已晚…… 译注7 读/写操作再排序都属乱序执行。看下面一段原始指令代码: (p1)br label //分支判断。若为true则跳转到label,否则继续执行 ld8 r9 r5 //从r5所指地址空间读取8个字节到r9 add r2 r9 r3 //将r9和r3中值求和,并存入r2 其中ld,load;r,register。 因为load操作较耗时间,通常花费几个时钟周期才能完成。因此从提高效率的角度看,应该在处理器空闲的情况下,尽早加载此操作。比如简单再排序优化后: ld8 r9 r5 (p1)br label add r2 r9 r3 如果分支判断结果为false,流程发生跳转,那么可以直接舍弃r9的结果值,即ld8白做了;但如果未发生跳转,则ld尽早执行,提高了流程整体效率。 当然,CPU实际工作远比上面例子复杂。比如将ld提前,但如果ld失败怎么办?有依赖关系的指令呢,能否乱序?在多线程应用里,乱序还可能引起其他一些问题,比如Java中的Double-Checked Locking失败。 不光CPU支持乱序执行,现在的很多编译器也开始做乱序优化,而且重心有逐渐从硬件转到软件的趋势。 有两篇资料可以参看:Scaling Itanium® Architecture for Higher Performance(特别是其中的如何处理指令依赖值得了解)和Verified Optimizations for the Intel IA-64 Architecture。 译注8 片内,英文为on the die或On-Die。Die:裸芯;Chip:包装后的芯片。类似有On-Chip、Off-Die/Chip。 译注9 MMX:MultiMedia eXtension,多媒体扩展。Intel在1996年3月份正式公布了MMX技术的细节,并于1997年1月正式向全球推出基于MMX技术的166MHz和200MHz Pentium芯片,AMD也几乎在同时推出了支持MMX技术的第六代处理器AMD K6。 MMX技术是Intel针对×86体系的一次重大扩充,使计算机同多媒体相关任务的综合处理能力提高了1.5~2倍。它不仅是Intel自 i386面世以来对CPU体系结构的一次显著改进,同时也是IT界对多媒体数据处理等专用芯片及功能板卡的一次成功挑战。 SSE:Streaming SIMD(单指令多数据) Extensions,是Intel针对AMD K6-2引入的“3D NOW!”技术,于l999年在Pentium3中引入的SIMD扩展指令集,业界也称为MMX2,在多媒体数据处理(特别是3D)和浮点运算等能力上全面加强。 译注10 目前频率最高的处理器是Intel P4 570J,3.8GHz,上升非常缓慢。 CPU频率越高,所需电能和发热量越多;而晶体管越小,耗电和热量越低。制造工艺进步,可能让晶体管更小,从而让CPU在相同或一定程度内提高的能耗下达到更高频率。从这个角度说,提升CPU频率的瓶颈是制造工艺。 于是,Intel推出了了90nm工艺的Prescott核心Pentium4,其理论频率将能达到6GHz!然而世事难料,在90nm工艺晶体管里,由于电介质厚度太低无法阻挡电子的穿越,造成了严重的电流泄漏问题,随之带来的就是大量的电能消耗和废热。如果强行提升频率,则发热激增,CPU经不起如此的高烧。 泄漏电流问题并非不可解决,但绝不能在短时间办到。至此,芯片厂商提升系统性能的思路开始发生重大变化,即转向多核。 有关电流泄漏和应对策略的详情,可参看:NetBurst的继承者 Core微处理器架构技术解析。 译注11 在IDF05(Intel Developer Forum 2005)上。Intel首席执行官Craig Barrett就取消4GHz芯片计划一事,半开玩笑当众单膝下跪致歉。
译注12 Working Set,记录了操作系统为进程提交的内存的总量。 译注13 并发编程语言(Concurrent Language)中的术语。 协同程序(coroutines),或协程。用以实现协作式多任务,于上世纪60年代提出。同属一个协程的多个进程,在同一时刻只能有一个处于运行状态。协程属于一种并发进程创建方式,其他方式还有Fork/join、Cobegin/coend和进程显式申明(Process declarations)等。 并发进程之间的通信方式主要有两种方式:变量共享(shared variables)和消息传递(message passing)。其他还包括抽象于更高层次的远程过程调用(remote procedure call,RPC)等。 通信就离不开同步。同步方法主要包括:信号量(semaphores)、条件临界区(conditional critical regions)、管程(monitors)、互斥(mutual exclusion)、路径表达式(path expressions)、原子事务(atomic transactions)和汇集(rendezvous)等。 其中的管程是位于低级同步控制手段之上的一种对象化管理工具。信号量的使用是无结构的,很不方便;条件临界区相对于信号量更结构化,但同步控制代码仍然非常分散,不利于管理。因此引入了管程,它实现了共享资源的集中管理,封装了共享资源以及施于其上的操作。 译注14 因为某些原因(如竞争条件下静态变量初始化问题),线程还未被列入ISO C++标准。目前在不同的平台上,都有线程的专门实现,短时间内难以完全统一。 不过Boost线程库目前差不多具有准标准身份。 译注15 并行编程中必须考虑的两个问题是被处理数据和任务间通讯。经过用户的选择与市场的淘汰,现在的并行编程标准基本上趋向以下三种: 1、数据并行。特点,各任务处理的数据彼此分离,任务间通过消息传递进行通讯;数据分离和消息传递工作由编译器完成。 HPF(High Performance Fortran,高性能Fortran)是典型的数据并行编程语言。因为目前的编译器技术对实际应用中各种不规则问题的解决方案仍不够理想,加上专注于数据并行,因此HPF未获广泛应用。 2、消息传递。特点,各任务处理的数据彼此分离,任务间通过消息传递进行通讯;数据分离和消息传递工作由程序员和用户完成,因此对程序员要求很高。这种模式非常适用于消息传递的体系结构(如机群系统),用户和程序员主要需考虑的是通讯同步和通讯性能问题。 并行虚拟机(PVM,Parallel Virtual Machine)和消息传递接口(MPI,Message Passing Interface)是两种广泛使用的消息传递并行编程标准。其中PVM侧重异构环境下的可移植性和互操作性;MPI更强调性能,但在异构环境下有不同的实现。几乎所有的高性能计算系统都支持PVM和MPI。 3、共享内存。特点,各任务处理的数据实现内存共享,任务间也通过共享数据实现通讯;数据共享可由程序员或编译器完成。共享内存并行编程主要应用在对称多处理器(SMP ,Symmetric Multi Processors)系统上。 OpenMP(Open MultiProcessing由X3H5发展而来)和PThread(POSIX Thread)都是共享内存并行编程的实现。 OpenMP由1993年建立的X3H5标准发展而来,目前已成共享内存并行编程的实际工业标准,得到DEC、Intel、IBM和Sun等厂商广泛支持。它在Forthan、C/C++得到了实现,主要支持隐式并行编程,即编译器实现并行。 PThread主要在Unix系统上使用。Unix的实现系统很多,比如Linux、FreeBSD、Solaris、Mac OS X等。要在众多“类UNIX”上开发跨平台的多线程应用,绝非易事,因此制定了POSIX Thread标准。David R. Butenhof(Boost库发起者之一,ISO C++标准委员会成员)的《Programming with POSIX Threads》这本书,可以说是Unix上编写多线程应用的必备参考书。对其他平台并行程序开发也有很高参考价值。 总的来说,共享内存并行编程与目前大多数的多线程程序员思维习惯最为接近,是程序员从单核转向多核系统需付代价最小的方案。但专家仍有不同意见,比如Herb Sutter就不看好OpenMP,因为共享内存并行编程本质上并没有太多改进,仍然依赖数据资源的锁定,这会带来性能问题。消息传递并行有性能优势,但对程序员的要求又太高了。所有这些难题,还需要研究并行和各种标准、库的专家继续努力解决。 December, 2007 父母最不该对孩子说的10句蠢话!一家三口的生活往往就是“你我他”的不停欢喜,摩擦和助长。作为儿女的你,看看以下的文字,你是否熟悉?作为父母的您,看看这些文字,是否似曾相识?没有偏见,没有纷争,也没有挑拨离间,只是对中国式教育的一种分析和不解。 1 "我们是不行了,孩子,就看你的了!" 2 "你看看人家谁谁谁!" 2 "没时间管你,不挣钱怎么过日子啊?" 3 "宝宝,爸爸不听话,打他!" 4 "进了前三名,妈妈给你买...!" 5 "没有原因,我说不行就不行!" 6 "你爱怎么招就怎么找吧,谁管的了你啊?" 7 "孩子是我的,我想怎样就怎样!" 8 "你怎么这么笨?" 9 "如果爸爸妈妈离婚,你要爸爸还是要妈妈?" 10 "你看看人家谁谁谁!" December, 2007 java正则表达式; regular expression(转)概要 JAVA中正则表达式的应用 (一)(转)正则表达式是一种可以用于模式匹配和替换的强有力的工具,一个正则表达式就是由普通的字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式,它描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。 正则表达式在字符数据处理中起着非常重要的作用,我们可以用正则表达式完成大部分的数据分析处理工作,如:判断一个串是否是数字、是否是有效的Email地址,从海量的文字资料中提取有价值的数据等等,如果不使用正则表达式,那么实现的程序可能会很长,并且容易出错。对这点本人深有体会,面对大量工具书电子档资料的整理工作,如果不懂得应用正则表达式来处理,那么将是很痛苦的一件事情,反之则将可以轻松地完成,获得事半功倍的效果。 由于本文目的是要介绍如何在JAVA里运用正则表达式,因此对刚接触正则表达式的读者请参考有关资料,在此因篇幅有限不作介绍。
在JDK1.3或之前的JDK版本中并没有包含正则表达式库可供JAVA程序员使用,之前我们一般都在使用第三方提供的正则表达式库,这些第三方库中有源代码开放的,也有需付费购买的,而现时在JDK1.4的测试版中也已经包含有正则表达式库---java.util.regex。 故此现在我们有很多面向JAVA的正则表达式库可供选择,以下我将介绍两个较具代表性的 Jakarta-ORO和 java.util.regex,首先当然是本人一直在用的 Jakarta-ORO:
Jakarta-ORO是最全面以及优化得最好的正则表达式API之一,Jakarta-ORO库以前叫做OROMatcher,是由Daniel F. Savarese编写,后来他将其赠与Jakarta Project,读者可在Apache.org的网站 下载该API包。 许多源代码开放的正则表达式库都是支持Perl5兼容的正则表达式语法,Jakarta-ORO正则表达式库也不例外,他与Perl 5正则表达式完全兼容。 ★PatternCompiler对象: PatternCompiler compiler=new Perl5Compiler(); ★Pattern对象: Pattern pattern=null;
try {
pattern=compiler.compile("s[ahkl]y ");
} catch (MalformedPatternException e) {
e.printStackTrace();
}
在默认的情况下,编译器会创建一个对大小写敏感的模式(pattern)。因此,上面代码编译得到的模式只匹配"say"、"shy"、 "sky"和"sly",但不匹配"Say"和"skY"。要创建一个大小写不敏感的模式,你应该在调用编译器的时候指定一个额外的参数: Pattern对象创建好之后,就可以通过PatternMatcher类用该Pattern对象进行模式匹配。 ★PatternMatcher对象: PatternMatcher对象依据Pattern对象和字符串展开匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现,它根据Perl 5正则表达式语法进行模式匹配: PatternMatcher对象提供了多个方法进行匹配操作,这些方法的第一个参数都是需要根据正则表达式进行匹配的字符串:
但以上三种方法只会查找输入字符串中匹配正则表达式的第一个对象,如果当字符串可能有多个子串匹配给定的正则表达式时,那么你就可以在调用上面三个方法时用PatternMatcherInput对象作为参数替代String对象,这样就可以从字符串中最后一次匹配的位置开始继续进行匹配,这样就方便的多了。 用PatternMatcherInput对象作为参数替代String时,上述三个方法的语法如下:
★Util.substitute()方法: public static String substitute(PatternMatcher matcher,
Pattern pattern,Substitution sub,String input,
int numSubs)
前两个参数分别为PatternMatcher和Pattern对象。而第三个参数是个Substiution对象,由它来决定替换操作如何进行。第四个参数是要进行替换操作的目标字符串,最后一个参数用来指定是否替换模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只进行指定次数的替换。 在这里我相信有必要详细解说一下第三个参数Substiution对象,因为它将决定替换将怎样进行。 Substiution: StringSubstitution: StringSubstitution()->缺省的构造方法,初始化一个包含零长度字符串的替换对象。 StringSubstitution(java.lang.String substitution)->初始化一个给定字符串的替换对象。 Perl5Substitution: 它有三个构造器: Perl5Substitution() Perl5Substitution(java.lang.String substitution) Perl5Substitution(java.lang.String substitution, int numInterpolations) 前两种构造方法与StringSubstitution一样,而第三种构造方法下面将会介绍到。 在 Perl5Substitution的替换字符串中可以包含用来替代在正则表达式里由小扩号围起来的匹配组的变量,这些变量是由$1, $2,$3等形式来标识。我们可以用一个例子来解释怎样使用替换变量来进行替换: 假设我们有正则表达式模式为b\d+:(也就是b[0-9]+:),而我们想把所有匹配的字符串中的"b"都改为"a",而":"则改为"-",而其余部分则不作修改,如我们输入字符串为"EXAMPLE b123:",经过替换后就应该变成"EXAMPLE a123-"。要做到这点,我们就首先要把不做替换的部分用分组符号小括号包起来,这样正则表达式就变为"b(\d+):",而构造Perl5Substitution对象时其替换字符串就应该是"a$1-",也就是构造式为Perl5Substitution("a$1-"),表示在使用Util.substitute()方法时只要在目标字符串里找到和正则表达式" b(\d+): "相匹配的子串都用替换字符串来替换,而变量$1表示如果和正则表达式里第一个组相匹配的内容则照般原文插到$1所在的为置,如在"EXAMPLE b123:"中和正则表达式相匹配的部分是"b123:",而其中和第一分组"(\d+)"相匹配的部分则是"123",所以最后替换结果为"EXAMPLE a123-"。 有一点需要清楚的是,如果你把构造器Perl5Substitution(java.lang.String substitution,int numInterpolations) 中的numInterpolations参数设为INTERPOLATE_ALL,那么当每次找到一个匹配字串时,替换变量($1,$2等)所指向的内容都根据目前匹配字串来更新,但是如果numInterpolations参数设为一个正整数N时,那么在替换时就只会在前N次匹配发生时替换变量会跟随匹配对象来调整所代表的内容,但N次之后就以一致以第N次替换变量所代表内容来做为以后替换结果。 举个例子会更好理解: 假如沿用以上例子中的正则表达式模式以及替换内容来进行替换工作,设目标字符串为"Tank b123: 85 Tank b256: 32 Tank b78: 22",并且设numInterpolations参数为INTERPOLATE_ALL,而Util.substitute()方法中的numSub变量设为SUBSTITUTE_ALL(请参考上文Util.substitute()方法内容),那么你获得的替换结果将会是: 但是如果你把numInterpolations设为2,并且numSubs依然设为SUBSTITUTE_ALL,那么这时你获得的结果则会是: 你要注意到最后一个替换所用变量$1所代表的内容与第二个$1一样为"256",而不是预期的"78",因为在替换进行中,替换变量$1只根据匹配内容进行了两次更新,最后一次就使第二次匹配时所更新的结果,那么我们可以由此知道,如果numInterpolations设为1,那么结果将是: 刚好前段时间公司准备出一个《伊索预言》的英语学习互动教材,其中有电子档资料的整理工作,我们就以此为例来看一下Jakarta-ORO与JDBC2.0 API结合起来对数据库内的资料进行简单提取与整理的实现。假设由录入部的同事送过来的存放在MS SQLSERVER 7数据库里的电子档的表结构如下(注:或许在不同的DBMS中有相应的正则表达式的应用,但这不在本文讨论范围内): 表名:AESOP, 表中每条记录包含有三列: 其中CONTENT列中内容的格式如下: 如对应单词Kevin,CONTENT中的内容如下: 我们的例子主要针对CONTENT列中内容进行字符串处理。 ★查找单个匹配:
在这个处理中我是用toString()方法来取得结果,但是如果正则表达式里是用了分组符号(圆括号),那么就可以用group(int gid)的方法来取得相应各组匹配的结果,如正则表达式改为" (\[[^]]+\])",那么就可以用以下方法来取得结果:pure=result.group(0); 用程序验证,输出结果同样为:kevin的音标为['kevin] 而如果正则表达式为(\[[^]]+\])(\[[^]]+\]),则会查找到两个连续的方括号所包含的内容,也就找到[音标] [词性]两项,但是两项的结果分别在两个组里面,分别由下面语句获得结果: result.group(0)->返回[音标] [词性]两项内容,也就是与整个正则表达式相匹配的结果字符串,在这里也就为['kevin] [名词] result.group(1) ->返回[音标]项内容,结果应是['kevin] result.group(2) ->返回[词性]项内容,结果应是[名词] 继续用程序验证,发现输出并不正确,主要是当内容有中文时就不能成功匹配,考虑到可能是Jakarta-ORO正则表达式库版本不支持中文的问题,回看一下原来我一直用的还是2.0.1的老版本,马上到Jakarta.org上下载最新的2.0.4版本装上再用程序验证,得出的结果就和预期一样正确。 ★查找多个匹配: 首先我们先试个简单的应用,假设我们想把CONTNET字段内容里所有用方括号包起来的字串都找出来,很清楚地,CONTNET字段的内容里面就只有两项匹配的内容:[音标]和 [词性],刚才我们其实已经把它们分别找出来了,但是我们所用的方法是分组方法,把"[音标] [词性]"作为一整个正则表达式匹配的内容先找到,再根据分组把[音标]和 [词性]分别挑出来。但是现在我们需要做的是把[音标]和[词性]分别做为与同一个正则表达式匹配的内容,先找到一个接着再找下一个,也就是刚才我们的表达式为(\[[^]]+\])(\[[^]]+\]),而现在应为" \[[^]]+\] "。 我们已经知道在匹配操作的三个方法里只要用PatternMatcherInput对象作为参数替代String对象就可以从字符串中最后一次匹配的位置开始继续进行匹配,实现的程序片段如下: PatternMatcherInput input=new PatternMatcherInput(content);
while (matcher.contains(input,pattern)) {
result=matcher.getMatch();
System.out.println(result.group(0))
}
输出结果为:['kevin] 接着我们来做复杂一点的处理,就是我们要先把下面内容: 第一步当然是要定出相应的正则表达式,需要有两个,一是和整个例句部分(也就是由大括号包起来的部分)匹配的正则表达式:"\{.+\}", 另一个则要和每个例句部分匹配(也就是小括号中的内容),:\(([^)]+\) 而且由于要把例句的各项分离出来,所以要再把里面的各部分用分组的方法匹配出来:" ([^(]+)/(.+)/(.+):([^)]+) "。 为了简便起见,我们不再和从数据库里读出,而是构造一个包含同样内容的字符串变量,程序片段如下: try{
String content="['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词:凯文) (Kevin is living in ZhuHai now./凯文现住在珠海/名词: 凯文)}";
String ps1="\\{.+\\}";
String ps2="\\([^)]+\\)";
String ps3="([^(]+)/(.+)/(.+):([^)]+)";
String sentence;
PatternCompiler orocom=new Perl5Compiler();
Pattern pattern1=orocom.compile(ps1);
Pattern pattern2=orocom.compile(ps2);
Pattern pattern3=orocom.compile(ps3);
PatternMatcher matcher=new Perl5Matcher();
//先找出整个例句部分
if (matcher.contains(content,pattern1)) {
MatchResult result=matcher.getMatch();
String example=result.toString();
PatternMatcherInput input=new PatternMatcherInput(example);
//分别找出例句一和例句二
while (matcher.contains(input,pattern2)){
result=matcher.getMatch();
sentence=result.toString();
//把每个例句里的各项用分组的办法分隔出来
if (matcher.contains(sentence,pattern3)){
result=matcher.getMatch();
System.out.println("英文句: "+result.group(1));
System.out.println("句子中文翻译: "+result.group(2));
System.out.println("词性: "+result.group(3));
System.out.println("意思: "+result.group(4));
}
}
}
}
catch(Exception e) {
System.out.println(e);
}
输出结果为: ★查找替换: 例如我现在想把第二个例句进行改动,换为:Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。 也就是把 改为: 之前,我们已经了解Util.substitute()方法与Substiution接口,以及Substiution的两个实现类StringSubstitution和Perl5Substitution,我们就来看看怎么用Util.substitute()方法配合Perl5Substitution来完成我们上面提出的替换要求,确定正则表达式: 我们要先找到其中的整个例句部分,也就是由大括号包起来的字串,并且把两个例句分别分组,所以正则表达式为:"\{(\([^)]+\))(\([^)]+\))\}",如果用替换变量来代替分组,那么上面的表达式可以看为"\{$1$2\}",这样就可以更容易看出替换变量与分组间的关系。 根据上面的正则表达式Perl5Substitution类可以这样构造: Perl5Substitution("{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}") 再根据这个Perl5Substitution对象来使用Util.substitute()方法便可以完成替换了,实现的代码片段如下: try{
String content="['kevin] [名词](人名凯文){(Kevin loves comic.
/凯文爱漫画/名词: 凯文)(Kevin lives in ZhuHai now./凯文现住在珠海/名词: 凯文)}";
String ps1="\\{(\\([^)]+\\))(\\([^)]+\\))\\}";
String sentence;
String pure;
PatternCompiler orocom=new Perl5Compiler();
Pattern pattern1=orocom.compile(ps1);
PatternMatcher matcher=new Perl5Matcher();
String result=Util.substitute(matcher,
pattern1,new Perl5Substitution(
"{$1( Kevin has seen《LEON》seveal times,because it is a good film./
凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}",1),
content,Util.SUBSTITUTE_ALL);
System.out.println(result);
}
catch(Exception e) {
System.out.println(e);
}
输出结果是正确的,为: 至于有关使用numInterpolations参数的构造器用法,读者只要根据上面的介绍自己动手试一下就会清楚了,在此就不再例述。
本文首先介绍了Jakarta-ORO正则表达式库的对象与方法,并且接着举例让读者对实际应用有进一步的了解,虽然例子都比较简单,但希望读者们在看了该文后对Jakarta-ORO正则表达式库有一定的认知,在实际工作中有所帮助与启发。 其实在Jakarta org里除了Jakarta-ORO外还有一个百分百的纯JAVA正则表达式库,就是由Jonathan Locke赠与Jakarta ORG的Regexp,在该包里面包含了完整的文档以及一个用于调试的Applet例子,对其有兴趣的读者可以到此 下载。
December, 2007 ITSM网上资源导航组织机构 ITSM带来的收益 关于ITIL和ITSM进入中国之后风靡南北的盛况,我们已经了解很多,但是采用ITSM相关方法之后,企业具体可以在哪些方面得到收益,特别是能够数量化的一些收益指标,由于我们起步较晚,全面应用的先例并不多见,所以这方面的资料也鲜有报道。不过,中国自古就有“他山之石,可以攻玉”的说法,因此,国外的数字也可以作为我们的参考。下图是国际知名咨询公司Gartner Group对美国ITSM市场上,经过ITSM相关流程梳理和改造之后,每个用户每年成本节约得出的平均统计数字: November, 2007 在Spring2.0中集成dwr1、在web.xml中增加如下配置段: <servlet> <servlet-mapping>
没什么特殊的,不过要注意一下那个Servlet org.directwebremoting.spring.DwrSpringServlet 2、现在可以不再使用dwr.xml而是直接使用spring的配置,暂时只有一种复杂的方法,增加applicationContext_dwr.xml名字就可以看出:在spring中配置dwr了,内容如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC <beans default-lazy-init="false"> 与普通spring配置一样,需要注意的是 <bean id="__dwrConfiguration" class="org.directwebremoting.spring.SpringConfigurator"> 3、验证结果: http://localhost:8080/framework/dwr,出来三个连接jdbcTemplateKey, frameworkViewService, frameworkMetaManager,点击进入,现在你可以测试你配置dwr过程中暴露出来的method了 November, 2007 在Spring2.0中集成DWR2.0(转)前些日子看了一篇关于在Spring 2中整合DWR 2的文章《AJAX, DWR and Spring》。最近,想动手试一下,就下载其源代码回来看看,依葫芦画瓢做了一遍。在运行时,得到XML验证错误。经过一翻折腾,终于把问题解决。 Spring 2基于XML Schema的配置众所周知,Spring 2通过XML Schema配置方式极大地简化的其配置,而且使得第三方扩展变为可能。配置如下代码所示: <? xml version="1.0" encoding="UTF-8" ?> 清单1 applicationContext.xml
< beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" > <!-- <bean /> definitions here --> </ beans > 不知大家有没有想过spring-beans-2.0.xsd位置在那里?其实,大家可以用Eclipse打开Spring的jar包,展开META-INF,并双击打开其中的spring.schemas文件,内容如下: http\://www.springframework.org/schema/beans/spring-beans- 2.0 .xsd = org/springframework/beans/factory/xml/spring-beans- 2.0 .xsd 清单2 spring.schemas
http\://www.springframework.org/schema/tool/spring-tool- 2.0 .xsd = org/springframework/beans/factory/xml/spring-tool- 2.0 .xsd http\://www.springframework.org/schema/util/spring-util- 2.0 .xsd = org/springframework/beans/factory/xml/spring-util- 2.0 .xsd http\://www.springframework.org/schema/aop/spring-aop- 2.0 .xsd = org/springframework/aop/config/spring-aop- 2.0 .xsd http\://www.springframework.org/schema/lang/spring-lang- 2.0 .xsd = org/springframework/scripting/config/spring-lang- 2.0 .xsd http\://www.springframework.org/schema/tx/spring-tx- 2.0 .xsd = org/springframework/transaction/config/spring-tx- 2.0 .xsd http\://www.springframework.org/schema/jee/spring-jee- 2.0 .xsd = org/springframework/ejb/config/spring-jee- 2.0 .xsd http\://www.springframework.org/schema/beans/spring-beans.xsd = org/springframework/beans/factory/xml/spring-beans- 2.0 .xsd http\://www.springframework.org/schema/tool/spring-tool.xsd = org/springframework/beans/factory/xml/spring-tool- 2.0 .xsd http\://www.springframework.org/schema/util/spring-util.xsd = org/springframework/beans/factory/xml/spring-util- 2.0 .xsd http\://www.springframework.org/schema/aop/spring-aop.xsd = org/springframework/aop/config/spring-aop- 2.0 .xsd http\://www.springframework.org/schema/lang/spring-lang.xsd = org/springframework/scripting/config/spring-lang- 2.0 .xsd http\://www.springframework.org/schema/tx/spring-tx.xsd = org/springframework/transaction/config/spring-tx- 2.0 .xsd http\://www.springframework.org/schema/jee/spring-jee.xsd = org/springframework/ejb/config/spring-jee- 2.0 .xsd 从以上的文件中,可以看出XML Sechema文件在类包中位置。 DWR 2.0 RC 2中的XML Schema文件根据上面的描述,我打开DWR的jar包中spring.schemas文件,内容如下: http\://www.directwebremoting.org/schema/spring-dwr- 2.0 .xsd = org/directwebremoting/spring/spring-dwr- 2.0 .xsd
然后,按照上面的路径打开spring-dwr-2.0.xsd文件,内容如下: <? xml version="1.0" encoding="UTF-8" standalone="no" ?> 清单3 spring-dwr-2.0.xsd
<!-- 省略了版权信息 --> < xsd:schema xmlns ="http://www.directwebremoting.org/schema/spring-dwr" xmlns:xsd ="http://www.w3.org/2001/XMLSchema" targetNamespace ="http://www.directwebremoting.org/schema/spring-dwr" elementFormDefault ="qualified" attributeFormDefault ="unqualified" > <!-- 省略了具体的定义 --> </ xsd:schema > 文件spring-dwr-2.0.xsd告诉我们,其名称空间应为“http://www.directwebremoting.org/schema/spring-dwr”,所以我们在配置Spring 2时,应使用以上的名称空间,如下面的代码片段所示: <? xml version="1.0" encoding="UTF-8" ?> 清单3 ajaxContext.xml
< beans xmlns ="http://www.springframework.org/schema/beans" xmlns:dwr ="http://www.directwebremoting.org/schema/spring-dwr" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd" > < dwr:configuration > < dwr:convert class ="net.blogjava.max.pws.domain.Album" type ="bean" > < dwr:exclude method ="photos" /> </ dwr:convert > < dwr:convert class ="net.blogjava.max.pws.domain.Photo" type ="bean" > < dwr:exclude method ="original, poster, thumb, full, album" /> </ dwr:convert > </ dwr:configuration > < bean id ="ajaxFacade" class ="net.blogjava.max.pws.web.ajax.AjaxFacade" > < dwr:remote javascript ="AjaxFacade" /> < property name ="personalWebSite" ref ="personalWebSite" /> </ bean > </ beans > WEB-INF/web.xml配置通过上面的配置,我们可以省去dwr.xml配置,不过在web.xml配置dwr的Servlet时,要使用新的Servlet类。配置代码片段如下: < servlet > 清单4 web.xml
< servlet-name > dwr </ servlet-name > < servlet-class > org.directwebremoting.spring.DwrSpringServlet </ servlet-class > < init-param > < param-name > debug </ param-name > < param-value > true </ param-value > </ init-param > < load-on-startup > 1 </ load-on-startup > </ servlet > < servlet-mapping > < servlet-name > dwr </ servlet-name > < url-pattern > /dwr/* </ url-pattern > </ servlet-mapping > 总结通过在Spring 2整合DWR 2配置,可以集中管理应用程序的配置,从一定程度上解决JavaEE开发中的配置文件泛滥的问题。 November, 2007 解决Hibernate java.io.EofException Stream count invalidate header exception整个系统无端的有一个类的查询方法总是会出现java.io.Eof 异常或者Stream count, invalid header等的错误,查网上,有人说mysql 的驱动问题或者网络错误,但是没理由的,其他的都正常,以为是数据库的表可能有问题,将表drop后再create,错误依旧 上述结果都是在web应用中报出的,转移测试方法,写了一个Tester的Junit类,运行错误出现了,根本不是mysql 等的错误,报异常org.hibernate.type.SerializationException,重新检查hbm配置文件,未发现任何异常,逐个的删除hbm中配置的属性,发现删除一个叫做creator的属性后可以正常跑起来,此属性使用many-to-one关系与对象关联,类结构如下 IbaseBO | ICat BaseBO | | | Cat 在IBaseBO和ICat中申明过getCreator setCreator,在BaseBO中实现过,于是把ICat中的声明删除,ok整个都跑起来了 不得解,作为经验记录 October, 2007 dwr异常解决环境dwr2.0 原因是据说dwr2.0的异常处理支持比1.0好很多
定义异常处理函数:
function errorHandler(errorString, exception){
alert(exception.message); } function exceptionHandler(exceptionString, exception){
alert(exception.message); } 设置通用的异常处理函数:
dwr.engine.setExceptionHandler(exceptionHandler)
dwr.engine.setErrorHandler(errorHandler);
在dwr.xml中配置异常类转换器:
<convert converter="exception" match="com.jeyo.monica.service.OrderServiceException">
<param name='include' value='message,lineNumber'/> </convert> <convert converter="exception" match="java.lang.Exception"> <param name='include' value='message,lineNumber'/> </convert> 业务处理过程中一定需要将对应的异常转换为上面定义的异常才可以被dwr捕捉的
如下:
try{
}catch(Exception e){
throw new OrderServiceException(e);
}
.....
自定义的异常处理
调用dwr远程调用时多带若干参数:
如下格式:
OrderService.create(order,
{ callback:finishSaveOrderTicket, errorHandler:errorHandler, exceptionHandler:exceptionHandler } ); ok,上面的处理完成后,发生异常时就会把你的异常中的message 使用alert函数打印出来 August, 2007 WEB互动的革命 - JSF框架中使用的设计模式介绍(转)设计模式可以帮助用户在更高层次上抽象细节,更好地理解体系结构。如果比较熟悉 GoF 设计模式和 JavaServer Faces (JSF) 框架,本文可以帮助您洞察 JSF 框架中使用的设计模式,深入理解其工作原理。 本文探讨了 JSF 框架中使用的设计模式。详细讨论的设计模式包括 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 模式。 设计模式和 JavaServer Faces (JSF) 技术 首先简要地介绍一下模式和 JSF 框架。
现在我们来讨论 JSF 体系结构中的各种设计模式。本文将详细讨论 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 设计模式。我将分析每种模式的用途及其在 JSF 框架中的作用。 Singleton 模式 Singleton 模式的目的是保证类只有一个实例被加载,该实例提供一个全局访问点。当启动具有 JSF 支持的 Web 应用程序时,Web 容器初始化一个 FacesServlet 实例。在这个阶段,FacesServlet 对每个 Web 应用程序实例化 Application 和 LifeCycle 实例一次。这些实例就采用众所周知的 Singleton 模式,通常只需要该类型的一个实例。 使用 JSF 的 Web 应用程序只需要 Application 和 LifeCycle 类的一个实例。LifeCycle 管理多个 JSF 请求的整个生命期。因为其状态和行为在所有请求之间共享,这些对象采用 Singleton 模式合情合理。LifeCycle 维护的 PhaseListeners 也是 Singleton 模式的。PhaseListeners 由所有 JSF 请求共享。在 JSF 框架中可以广泛使用 Singleton 模式,以减少内存占用和提供对象的全局访问。NavigationHandler(用于确定请求的逻辑结果)和 ViewHandler(用于创建视图)也是使用 Singleton 模式的例子。 Model-View-Controller (MVC) MVC 模式的目的是从数据表示(View)中将数据(即 Model)分离出来。如果应用程序有多种表示,可以仅替换视图层而重用控制器和模型代码。类似的,如果需要改变模型,可以在很大程度上不改变视图层。控 制器处理用户动作,用户动作可能造成模型改变和视图更新。当用户请求一个 JSF 页面时,请求发送到 FacesServlet。FacesServlet 是 JSF 使用的前端控制器 servlet。和其他很多 Web 应用程序框架一样,JSF 使用 MVS 模式消除视图和模型之间的耦合。为了集中处理用户请求,控制器 servlet 改变模型并将用户导航到视图。 FacesServlet 是 JSF 框架中所有用户请求都要经过的控制器元素。FacesServlet 分析用户请求,使用托管 bean 对模型调用各种动作。后台(backing)或托管(managed)bean 就是该模型的例子。JSF 用户界面(UI)组件是视图层的例子。MVC 模式把任务分解给具有不同技能的开发人员,使这些任务能够同时进行,这样 GUI 设计人员就可以使用丰富的 UI 组件创建 JSF 页面,同时后端开发人员可以创建托管 bean 来编写专门的业务逻辑代码。 Factory Method 模式 Factory Method 模式的目的是定义一个用于创建对象的接口,但是把对象实例化推迟到子类中。在 JSF 体系结构中,Factory Method 模式被用于创建对象。LifeCycleFactory 是一个创建和返回 LifeCycle 实例的工厂对象。LifeCycleFactory 的 getLifeCycle (String LifeCycleId) 方法采用 Factory Method 模式,根据 LifeCycleId 创建(如果需要)并返回 LifeCycle 实例。自定义的 JSF 实现可以重新定义 getLifeCycle 抽象方法来创建自定义的 LifeCycle 实例。默认的 JSF 实现提供默认的 LifeCycle 实例。此外,对于每个 JSF 请求,FacesServlet 都从 FacesContextFactory 得到 FacesContext。FacesContextFactory 是一个抽象类,公开了 getFacesContext API,JSF 实现提供了 FacesContextFactory 和 getFacesContext API 的具体实现。这是另外一个使用 Factory Method 模式的例子,具体的 FacesContextFactory 实现创建 FacesContext 对象。 State 模式 State 模式的目的是在表示状态的不同类之间分配与状态有关的逻辑。FacesServlet 对 LifCycle 实例调用 execute 和 render 方法。LifeCycle 协调不同的 Phrase 以便执行 JSF 请求。在这里 JSF 实现就遵循了 State 模式。如果没有使用这种模式,LifeCycle 实现就会被大量的条件(即 “if” 语句)搅得一塌糊涂。JSF 实现为每个状态(或阶段)创建单独的类并调用 step。phase 是一个抽象类,定了每个 step 的公共接口。在 JSF 框架中定义了六个 phrase(即 step):RestoreViewPhase、ApplyRequestValues、ProcessValidationsPhase、 UpdateModelValuesPhase、InvokeApplicationPhase 和 RenderResponsePhase。 在 State 模式中,LifeCycle 把 FacesContext 对象传递给 phase。每个阶段或状态改变传递给它的上下文信息,然后设置 FacesContext 本身中的标志表明下一个可能的步骤。JSF 实现在每个步骤中改变其行为。每个阶段都可以作为下一个阶段的起因。FacesContext 有两种标志 renderResponse 和 responseComplete 可以改变执行的顺序。每个步骤执行完成后,LifeCycle 检查上一阶段是否设置了这些标志。如果设置了 responseComplete,LifeCycle 则完全放弃请求的执行。如果经过某个阶段后设置了 renderResponse 标志,JSF 就会跳过剩下的阶段而直接进入 Render Response 阶段。如果这两个标志都没有设置,LifeCycle 就会按顺序继续执行下一步。 Composite 模式 Composite 模式让客户代码能够统一处理复合对象和基本对象。复合对象是基本对象的容器。在第一阶段(Restore View 阶段)和最后一个阶段(Render Response 阶段),使用 JSF UI 组件构造 UI View。UIComponentBase 就是 Composite 模式中 Component 抽象类的一个例子。UIViewRoot 是 Composite 类,而 UIOutput(比方说)就是叶子(或者基本类)。UIComponentBase 类定义了叶子和复合对象的公共方法,如编码/解码值和子节点管理函数。子节点管理函数,如 getChildren,对于叶子节点返回空列表,对于复合节点则返回其子节点。 Decorator 模式 Decorator 模式的目的是不通过子类化动态扩展对象的行为。JSF 框架有很多扩展点(即可插入机制)。JSF 实现可使用 Decorator 模式替换默认的 PropertyResolver、VariableResolver、ActionListener、NavigationHandler、 ViewHandler 或 StateManager。通常自定义实现接受通过构造函数传递给它的默认实现的引用。自定义实现仅仅改写功能的一个子集,而将其他功能委托给默认实现。 如果希望实现自定义的 ViewHandler,改写默认 ViewHandler 实现的 calculateLocale 方法,可以像 清单 1 那样编写 CustomViewHandler 类: 清单 1. CustomViewHandler 片段
Strategy 模式 Strategy 模式的目的是封装不同的概念。JSF 框架采用 Strategy 模式使用委托实现模型呈现 UI 组件。JSF 技术支持两种呈现模型。在直接实现模型中,UI 组件对收到的请求中的数据进行解码,然后编码这些数据进行显示。在委托实现模型中,解码和编码操作委托给和组建关联的专门呈现器。后一种模型利用了 Strategy 设计模式,比直接实现更灵活。在 Strategy 模式中,将不同的算法封装在单独的对象中,从而可以动态地改变算法。JSF 实现可以用已有的 renderkit 实例注册另外的呈现器,当应用程序启动的时候,JSF 实现读取配置文件将这些呈现器和 UI 组件联系在一起。 Template Method 模式 Template Method 模式的目的是将变化的步骤推迟到子类中,而在父类中定义那些固定的算法步骤。JSF 框架通过 PhraseListeners 展现了 Template Method 模式提供的功能。采用 Template Method(或者 “hook”)使得 Web 作者可以为不同阶段之间的可选步骤提供实现,而主要阶段仍然和 JSF 框架的定义一致。JSF 框架提供了 PhaseListeners,概念上类似于 Template Method 模式中的可变步骤。JSF 框架有六个预定义的阶段,在每个阶段之间,Web 作者可以实现 PhaseListeners 来提供类似于 Template Method hook 的 hook。事实上,这种结构比 Template Method 模式更具有扩展性。可以通过注册 PhraseId 为 ANY_PHRASE 的 PhaseListener 在每个阶段后提供 hook。如果 PhaseId 是 ANY_PHASE,JSF 实现就会在每个阶段之前和之后调用该 PhaseListener。JSF 框架中的实现略有不同,因为可以根本没有 PhaseListener,但是在 Template Method 模式中,子类通常重新定义父类中抽象的可变步骤。 Observer 模式 Observer 模式的目的是当目标对象的状态改变时自动通知所有依赖的对象(即观察器)。JSF 在 UI 组件中实现了 Observer 模式。JSF 有两类内建事件:ActionEvent 和 ValueChangedEvent。ActionEvent 用于确定用户界面组件(如按钮)的激活。当用户单击按钮时,JSF 实现通知添加到该按钮上的一个或多个动作监听程序。于是该按钮被激活,或者说按钮(主体)的状态改变了。添加到按钮上的所有监听程序(即观察器)都收到通 知该主体状态已经改变。类似的,当输入 UI 组件中的值改变时,JSF 实现通知 ValueChangeListener。 结束语 JSF 框架利用了 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 设计模式。因为它的体系结构建立在已经验证的设计模式的基础上,这是一个健壮的框架,模式在 JSF 框架中得到了很好的利用。 Java抽象类和接口的区别来源:JavaEye技术社区(fzfx88) abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。 abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于 abstract class和interface的选择显得比较随意。 其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。 一、理解抽象类 abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢? 在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是 这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领 域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。 比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形 这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题 领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。 在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行 为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可 以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读 者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。 二、从语法定义层面看abstract class和interface 在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。使用abstract class的方式定义Demo抽象类的方式如下: abstract class Demo { abstract void method1(); abstract void method2(); … } 使用interface的方式定义Demo抽象类的方式如下: interface Demo { void method1(); void method2(); … } 在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的 不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊 形式的abstract class。 从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。 首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。 其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。 在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的 麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时 间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。 同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类 的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。 三、从设计理念层面看abstract class和interface 上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本文将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。 前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使 论述便于理解,下面将通过一个简单的实例进行说明。 考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示: 使用abstract class方式定义Door: abstract class Door { abstract void open(); abstract void close(); } 使用interface方式定义Door: interface Door { void open(); void close(); } 其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。 如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在 本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)下面将罗列出可能的解决方案,并从设计理念层面对这些不 同的方案进行分析。 解决方案一: 简单的在Door的定义中增加一个alarm方法,如下: interface Door { void open(); void close(); void alarm(); } 那么具有报警功能的AlarmDoor的定义方式如下: class AlarmDoor extends Door { void open() { … } void close() { … } void alarm() { … } } 或者 class AlarmDoor implements Door { void open() { … } void close() { … } void alarm() { … } } 这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅 依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。 解决方案二: 既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它 们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。 显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。 如果两个概念都使用interface方式来定义,那么就反映出两个问题: 1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器? 2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现 AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用 interface方式定义)反映不出上述含义。 如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同 时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定 义。如下所示: abstract class Door { abstract void open(); abstract void close(); } interface Alarm { void alarm(); } class AlarmDoor extends Door implements Alarm { void open() { … } void close() { … } void alarm() { … } } 这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计 意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有 Door的功能,那么上述的定义方式就要反过来了。 abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概 念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法。 |
|
|||||
|
|