2007年10月26日星期五

AOP技术基础

1、引言

  软件设计因为引入面向对象思想而逐渐变得丰富起来。“一切皆为对象”的精义,使得程序世界所要处理的逻辑简化,开发者可以用一组对象以及这些对象之间的关系将软件系统形象地表示出来。而从对象的定义,进而到模块,到组件的定义,利用面向对象思想的封装、继承、多态的思想,使得软件系统开发可以向搭建房屋那样,循序渐进,从砖石到楼层,进而到整幢大厦的建成。应用面向对象思想,在设计规模更大、逻辑更复杂的系统时,开发周期反而能变的更短。自然其中,需要应用到软件工程的开发定义、流程的过程控制,乃至于质量的缺陷管理。但从技术的细节来看,面向对象设计技术居功至伟。然而,面向对象设计的唯一问题是,它本质是静态的,封闭的,任何需求的细微变化都可能对开发进度造成重大影响。

  可能解决该问题的方法是设计模式。GOF将面向对象软件的设计经验作为设计模式纪录下来,它使人们可以更加简单方便地复用成功的设计和体系结构,帮助开发人员做出有利于系统复用的选择。设计模式解决特定的设计问题,使面向对象设计更灵活、优雅,最终复用性更好。然而,设计模式虽然给了我们设计的典范与准则,通过最大程度的利用面向对象的特性,诸如利用继承、多态,对责任进行分离、对依赖进行倒置,面向抽象,面向接口,最终设计出灵活、可扩展、可重用的类库、组件,乃至于整个系统的架构。在设计的过程中,通过各种模式体现了对象的行为,暴露的接口,对象间关系,以及对象分别在不同层次中表现出来的形态。然而鉴于对象封装的特殊性,“设计模式”的触角始终在接口与抽象中大做文章,而对于对象内部则无能为力。

  Aspect-Oriented Programming(面向方面编程,AOP)正好可以解决这一问题。它允许开发者动态地修改静态的OO模型,构造出一个能够不断增长以满足新增需求的系统,就象现实世界中的对象会在其生命周期中不断改变自身,应用程序也可以在发展中拥有新的功能。AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

  面向方面编程(AOP)是施乐公司帕洛阿尔托研究中心(Xerox PARC)在上世纪90年代发明的一种编程范式。但真正的发展却兴起于近几年对软件设计方兴未艾的研究。由于软件系统越来越复杂,大型的企业级应用越来越需要人们将核心业务与公共业务分离。AOP技术正是通过编写横切关注点的代码,即“方面”,分离出通用的服务以形成统一的功能架构。它能够将应用程序中的商业逻辑同对其提供支持的通用服务进行分离,使得开发人员从重复解决通用服务的劳动中解脱出来,而仅专注于企业的核心商业逻辑。因此,AOP技术也就受到越来越多的关注,而应用于各种平台下的AOP技术也应运而生。但由于AOP技术相对于成熟的OOP技术而言,在性能、稳定性、适用性等方面还有待完善,同时AOP技术也没有形成一个统一的标准,这使得AOP技术的研究更具有前沿性的探索价值。


2、AOP技术基础

  2.1 AOP技术起源


  AOP技术的诞生并不算晚,早在1990年开始,来自Xerox Palo Alto Research Lab(即PARC)的研究人员就对面向对象思想的局限性进行了分析。他们研究出了一种新的编程思想,借助这一思想或许可以通过减少代码重复模块从而帮助开发人员提高工作效率。随着研究的逐渐深入,AOP也逐渐发展成一套完整的程序设计思想,各种应用AOP的技术也应运而生。

  AOP技术在Java平台下是最先得到应用的。就在PARC对于面向方面编程进行研究的同时,美国Northeastern University的博士生Cristina Lopes和其同事也开始了类似的思考。最终,美国国防先进技术研究计划署(Defense Advanced Research Projects Agency即DARPA)注意到了这项工作,并提供了科研经费,鼓励将二者的工作成果结合起来。他们通过定义一套Java语言的扩展系统,使开发者可以方便的进行面向方面的开发,这套扩展系统被称为AspectJ。之后,AspectJ在2002年被转让给Eclipse Foundation,从而成为在开源社区中AOP技术的先锋,也是目前最为流行的AOP工具。

  AspectWerkz则是基于Java的动态的、轻量级AOP框架。AspectWerkz仍然是开源社区中的产品,由BEA System提供赞助,开发者则是BEA的两名员工Jonas Bonér和Alexandre Vasseur。最近版本是AspectWerkz 2.0。2005年1月,AspectJ和AspectWerkz达成协议,同意将二者的成果综合到一起,取其精华创建一个单一的工具。他们合作的第一个发布版本为AspectJ 5,它扩展了AspectJ语言,以支持基于Annotation开发风格而又支持类似AspectJ代码风格。AspectJ 5也为Java 5的语言特性提供完全的AOP支持。

  在Java阵营中,商用软件制造商JBoss在其2004年推出的JBoss 4.0中,引入了AOP框架和组件。在JBoss 4.0中,用户可以在JBoss应用服务器外部单独使用JBoss AOP,该版本为JBoss AOP 1.0,是在2004年10月发布的。在2005年,JBoss AOP框架又发布了1.3.0版本,新版本对加载期织入(Weev)和切点(point cut)匹配的性能做了很大的优化,使应用程序的启动时间大大缩短。

  作为轻型的Framework,Spring在开发轻量级的J2EE时,应用是非常广泛的。它通过IoC模式(Inversion of Control,控制反转模式)来实现AOP,通常被称为Spring AOP。在2004年,被作为Spring框架的扩展而发布,目前版本已更新到1.1.3。Spring AOP作为一种非侵略性的,轻型的AOP框架,开发者无需使用预编译器或其他的元标签,在Java程序中应用AOP。目前,AOP的功能完全集成到了Spring事务管理、日志和其他各种特性的上下文中。

  在.Net的阵营中,AOP技术的应用远不如Java阵营对AOP的关注。2005年1月,微软发布的Enterprise Library提供了7种不同的“应用程序块(application blocks)”。有个别专家认为,这些组件可以被认为是方面。但该观点并未得到一致的认同。事实上,在.Net平台下,推动AOP技术发展的原动力并非微软,而是开源社区。虽然,微软的技术专家们亦然听到了在.Net Framework中增加AOP技术的群众呼声,但作为如此巨大的软件公司,要让它灵活地转变战略方向,显然是不太现实的。正因为此,才赐予了开源社区在AOP技术的研究与探索上一个巨大的发展空间。

  与Java阵营中的AOP技术不同,目前在.Net平台下的各种AOP工具,基本上还停留在实验室阶段。但一些在技术上领先且逐渐成熟的AOP产品,也在开源社区中渐露峥嵘。这其中主要包括Aspect#,AspectDNG,Eos AOP等。

  Aspect#是基于Castle动态代理技术来实现的。Castle源于Apache Avalon项目,其目的在于实现一个轻量级的IoC容器。Aspect#于2005年6月被收录为Castle的其中一个子项目。它是针对CLI(.Net和Mono)实现的AOP框架,利用了反射、代理等机制。目前的Aspect#版本为2.1.1。

  AspectDNG目前的版本为0.7,仍然处于beta版的阶段。它的实现技术是基于rail的静态织入。Rail属于IL级别下的代码织入,它自定义的一套xml格式的ILML语言,能够将原有的程序集拆散成ILML格式,以便于对静态程序集进行修改和扩展,从而达到静态织入的目的。因为AspectDNG是属于IL级别下的代码织入,因此在.Net平台下,并不受具体的编程语言限制。

  Eos AOP与AspectDNG一样,仍然采用静态织入的方式,但从语法定义上,它更近似于AspectJ关于AOP的实现。它扩展了C#语法,引入了aspect、introduce、before、after等关键字,并且提供了专用的Eos编译器。Eos项目是于2004年9月开始启动,2005年6月推出的0.3.3版本为最新版本,主要的开发人员为Hridesh Rajan和Kevin Sullivan。前者为Virginia大学计算机系的研究生,Eos项目最初是由Hridesh Rajan提出的;而后者则为该计算机系的副教授(Associate Professor)。所以自Eos诞生之初,就带有浓厚的学院派特色。

  从AOP技术的整体发展来看,高性能、稳定、可扩展、易用的AOP框架是其趋势与目标。从上述对各种AOP技术的分析来看,AOP技术无疑是具有共同特点的,而各种实现技术就是围绕着这些共性深入与延伸。接下来,我将概要地介绍AOP的本质,以及它的技术要素。


  2.2 AOP技术本质
  2.2.1 技术概览

  AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

  而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

  使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

  实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。然而殊途同归,实现AOP的技术特性却是相同的,分别为:

  1、join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
  2、point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。
  3、advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。
  4、aspect(方面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。
  5、introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。

  上述的技术特性组成了基本的AOP技术,大多数AOP工具均实现了这些技术。它们也可以是研究AOP技术的基本术语。


  2.2.2 横切技术

  “横切”是AOP的专有名词。它是一种蕴含强大力量的相对简单的设计和编程技术,尤其是用于建立松散耦合的、可扩展的企业系统时。横切技术可以使得AOP在一个给定的编程模型中穿越既定的职责部分(比如日志记录和性能优化)的操作。

  如果不使用横切技术,软件开发是怎样的情形呢?在传统的程序中,由于横切行为的实现是分散的,开发人员很难对这些行为进行逻辑上的实现或更改。例如,用于日志记录的代码和主要用于其它职责的代码缠绕在一起。根据所解决的问题的复杂程度和作用域的不同,所引起的混乱可大可小。更改一个应用程序的日志记录策略可能涉及数百次编辑——即使可行,这也是个令人头疼的任务。

  在AOP中,我们将这些具有公共逻辑的,与其他模块的核心逻辑纠缠在一起的行为称为“横切关注点(Crosscutting Concern)”,因为它跨越了给定编程模型中的典型职责界限。


  2.2.2.1 横切关注点

  一个关注点(concern)就是一个特定的目的,一块我们感兴趣的区域,一段我们需要的逻辑行为。从技术的角度来说,一个典型的软件系统包含一些核心的关注点和系统级的关注点。举个例子来说,一个信用卡处理系统的核心关注点是借贷/存入处理,而系统级的关注点则是日志、事务完整性、授权、安全及性能问题等,许多关注点——即横切关注点(crosscutting concerns)——会在多个模块中出现。如果使用现有的编程方法,横切关注点会横越多个模块,结果是使系统难以设计、理解、实现和演进。AOP能够比上述方法更好地分离系统关注点,从而提供模块化的横切关注点。

  例如一个复杂的系统,它由许多关注点组合实现,如业务逻辑、性能,数据存储、日志和调度信息、授权、安全、线程、错误检查等,还有开发过程中的关注点,如易懂、易维护、易追查、易扩展等,(图)演示了由不同模块实现的一批关注点组成一个系统。



把模块作为一批关注点来实现

  通过对系统需求和实现的识别,我们可以将模块中的这些关注点分为:核心关注点和横切关注点。对于核心关注点而言,通常来说,实现这些关注点的模块是相互独立的,他们分别完成了系统需要的商业逻辑,这些逻辑与具体的业务需求有关。而对于日志、安全、持久化等关注点而言,他们却是商业逻辑模块所共同需要的,这些逻辑分布于核心关注点的各处。在AOP中,诸如这些模块,都称为横切关注点。应用AOP的横切技术,关键就是要实现对关注点的识别。

  如果将整个模块比喻为一个圆柱体,那么关注点识别过程可以用三棱镜法则来形容,穿越三棱镜的光束(指需求),照射到圆柱体各处,获得不同颜色的光束,最后识别出不同的关注点。如图所示:



关注点识别:三棱镜法则

  上图识别出来的关注点中,Business Logic属于核心关注点,它会调用到Security,Logging,Persistence等横切关注点。

public class BusinessLogic
{
public void SomeOperation()
{
//验证安全性;Securtity关注点;
//执行前记录日志;Logging关注点;

DoSomething();

//保存逻辑运算后的数据;Persistence关注点;
//执行结束记录日志;Logging关注点;
}
}


  


  


  


  


  


  


  


  


  


  


  


  


  

2007年9月24日星期一

JNDI 在 J2EE 中的角色

来源: IBM DW 网址: http://www.ibm.com/developerworks/cn/java/j-jndi/index.html

  掌握 J2EE 是件令人生畏的事,因为它包含的技术和缩略语在不断地增长。Java 命名和目录接口(Java Naming and Directory Interface,JNDI)从一开始就一直是 Java 2 平台企业版(JEE)的核心,但是 J2EE 开发新手经常用不好它。本文将消除 JNDI 在 J2EE 应用程序中所扮演角色的神秘性,并展示它如何帮助应用程序从部署细节中解脱出来。

  虽然 J2EE 平台提高了普通企业开发人员的生活水平,但是这种提高是以不得不学习许多规范和技术为代价的,这些规范和技术则是 J2EE 为了成为无所不包的分布式计算平台而整合进来的。Dolly Developer 是众多开发人员中的一员,她已经发现了一个特性,该特性有助于缓解随企业级应用程序部署而带来的负担,这个特性就是 JNDI,即 Java 命名与目录接口(Java Naming and Directory Interface)。让我们来看看 Dolly 在没有 JNDI 的时候是怎么做的,以及她是如何正确地应用 JNDI 来改善其状况的。

所有人都非常熟悉的旅程

  Dolly Developer 正在编写使用 JDBC 数据源的 Web 应用程序。她知道自己正在使用 MySQL,所以她将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并通过使用适当的 JDBC URL 连接到其 Web 应用程序中的数据库。她认识到数据库连接池的重要性,所以她包含了一个连接池包,并把它配置成最多使用 64 个连接;她知道数据库服务器已经被设置成最多允许 128 台客户机进行连接。

Dolly 在走向灾难

  在开发阶段,每件事都进行得很顺利。但是,在部署的时候,开始失控。Dolly 的网络管理员告诉她,她不能从她的桌面机访问生产服务器或登台服务器(staging server),所以她不得不为每个部署阶段开发不同的代码版本。因为这种情况,她需要一个新的 JDBC URL,所以还要为测试、阶段和生产进行独立的部署。(一听到要在每个环境中建立单独部署,熟悉配置管理的人会战战兢兢的,但是既然这是种非常普遍的情况,所以他们也只好硬着头皮上了。)

  就在 Dolly 认为通过不同的 URL 建立彼此独立的部署已经解决了自己的配置问题时,她发现她的数据库管理员不想在生产环境中运行 MySQL 实例。他说,MySQL 用作开发还可以,但是对于任务关键型数据而言,业务标准是 DB2®。现在她的构建不仅在数据库 URL 方面有所不同,而且还需要不同的驱动程序。

  事情越变越糟。她的应用程序非常有用,并且变得非常关键,以致于它从应用服务器那里得到了故障恢复的能力,并被复制到 4 个服务器集群。但是数据库管理员提出了抗议,因为她的应用程序的每个实例都要使用 64 个连接,而数据库服务器总共只有 200 个可用连接 —— 全部都被 Dolly 的应用程序占用了。更麻烦的是,DBA 已经确定 Dolly 的应用程序只需要 32 个连接,而且每天只有一个小时在使用。随着她的应用程序规模扩大,应用程序遇到了数据库级的争用问题,而她的惟一选择就是改变集群的连接数量,而且还要做好准备,在集群数量增长或者应用程序复制到另一个集群时再重复一次这样的操作。看来她已经决定了如何配置应用程序,应用程序的配置最好是留给系统管理员和数据库管理员来做。



J2EE 的角色

  如果 Dolly 在开发应用程序时了解 J2EE 所扮演的角色,那么她就可能避免遭遇这种困境。J2EE 规范把职责委托给多个开发角色:组件提供者(Component Provider)、应用程序组装者(Application Assembler)、部署人员(Deployer)和系统管理员(System Administrator)。(在许多公司中,组件提供者和组件组装者的角色是融合在一起的,部署人员和系统管理员的角色是融合在一起的。)在真正了解 J2EE 中的 JNDI 角色之前,掌握 J2EE 角色的作用非常重要。

组件提供者

  这个角色负责创建 J2EE 组件,J2EE 组件可以是 Web 应用程序、企业级 JavaBean(EJB)组件,或者是应用程序客户机(例如基于 Swing 的 GUI 客户机应用程序)。组件提供者包括:HTML 设计师、文档编程人员以及其他开发人员角色。大多数 J2EE 开发人员在组件提供者这一角色上耗费了相当多的时间。

应用程序组装者

  这个角色将多个 J2EE 模块捆绑成一个彼此结合的、可以部署的整体:企业归档(EAR)文件。应用程序组装者要选择组件,分清它们之间的交互方式,配置它们的安全性和事务属性,并把应用程序打包到 EAR 文件中。许多 IDE,例如 WebSphere® Studio、IDEA、JBuilder、WebLogic Workshop 和其他 IDE,都可以帮助应用程序组装者以交互方式配置 EAR 文件。

部署人员(Deployer)

  这个角色负责部署,这意味着将 EAR 安装到 J2EE 容器(应用服务器)中,然后配置资源(例如数据库连接池),把应用程序需要的资源绑定到应用服务器中的特定资源上,并启动应用程序。

系统管理员(System Administrator)

  这个角色负责保证容器需要的资源可用于容器。

角色实战

  假设有一个企业应用程序,该应用程序包含一个 Web 应用程序,还有一个负责业务逻辑和持久性的 EJB 组件。开发这个应用程序的组件供应商可能有许多,但是在许多情况下,可以由一个人来承担全部职责。组件可以包含数据传输对象(一个 JAR 文件)、EJB 接口(另一个 JAR 文件)、EJB 实现本身(另一个 JAR 文件),以及用户界面组件 —— servlet、JSP、HTML 页面和其他静态 Web 内容。用户界面组件被进一步打包成 Web 应用程序,其中包含 servlet 类、JSP 文件、静态内容,以及其他必需组件的 JAR(包括 EJB 接口)。

  这听起来好像用到的组件太多了,几乎超出了人的想像范围,尤其是在考虑构建一个典型的 Web 应用程序需要使用多少个 JAR 文件的时候。但是,重要的是认识到在这里必须小心地管理依赖性。接口和传输对象是 Web 应用程序和 EJB 实现可以依赖的对象,但是依赖性的方向应该是相同的;还要避免产生循环依赖。J2EE 组件(例如 WAR 文件和 EJB JAR 文件)必须在它们的部署单元之外声明它们在资源上的依赖性。

  应用程序组装者负责把 Web 应用程序中的依赖内容包含进来,并把它们整体打包成单个企业应用程序。工具在这里帮助很大。IDE 可以协助创建反映模块和 JAR 依赖性的项目结构,还允许您随意指定包含或排除的模块。

  部署人员负责确保部署环境中存在组件所需的资源,并将组件绑定到平台的可用资源上。例如,Web 应用程序中的外部 EJB 引用(部署描述符中的 ejb-ref)就是在此时被绑定到实际部署的 EJB 组件 —— 而且是立即绑定。

外部资源的后绑定

  任何不平凡(nontrivial)的 J2EE 应用程序都需要访问描述它期望使用环境的信息。这意味着开发和测试组件时,为了临时测试代码,开发人员要承担一些部署方面的职责。重要的是要理解:这么做的时候,您就走出了开发人员的领域。否则,可以试着依靠 JDBC 驱动程序,或 URL、JMS 队列名称,或者其他具有无意识的、偶尔可能是灾难性暗示的机器资源。



JNDI 前来援助

  Dolly 的问题的解决方案是从她的应用程序中清除所有对数据存储的直接引用。没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或连接管理。Dolly 需要编写代码来忽略将要访问的特定外部资源,只需要知道其他人会提供使用这些外部资源所需的链接即可。这允许部署人员(任何处在这个角色的人)把数据库连接分配给 Dolly 的应用程序。Dolly 没有必要参与其中。(从数据库安全性到遵守 Sarbanes-Oxley 法案,她都没有参与进来,她这样做也有充足的业务理由。)

  许多开发人员知道:代码和外部资源之间的紧密耦合是潜在的问题,但是在实践中却经常忘记角色的划分。在小型开发工作中(指的是团队规模或部署规模),即使忽视角色划分也能获得成功。(毕竟,如果应用程序只是个人的应用程序,而且您不准备依靠它,那么把应用程序锁定在特定的 PostgreSQL 实例上也挺好的。)

  J2EE 规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI 供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也可以用作这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。

Dolly 的情况更糟了

  现在我们重新来看一下 Dolly 的情况。在其简单的 Web 应用程序中,她直接从应用程序代码中使用了一个 JDBC 连接。参见清单 1,我们可以看出,Dolly 显式地把 JDBC 驱动程序、数据库 URL 以及她的用户名和口令编码到了 servlet 中:

清单 1. 典型(但是不好)的 JDBC 用法



Connection conn=null;
try {
Class.forName("com.mysql.jdbc.Driver",
true, Thread.currentThread().getContextClassLoader());
conn=DriverManager.getConnection("jdbc:mysql://dbserver?user=dolly&password=dagger");
/* use the connection here */
c.close();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) {}
}
}

  如果不用这种方式指定配置信息,Dolly(以及她的同伴们)使用 JNDI 来查找 JDBC DataSource 会更好一些,如清单 2 所示:

清单 2. 使用 JNDI 得到数据源



Connection conn=null;
try {
Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:comp/env/jdbc/mydatasource");
DataSource ds=(Datasource)datasourceRef;
Connection c=ds.getConnection();
/* use the connection */
c.close();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) { }
}
}

  为了能够得到 JDBC 连接,首先要执行一些小的部署配置,这样我们才可以在本地组件的 JNDI 下文中查找 DataSource。这可能有点烦琐,但是很容易学。不幸的是,这意味着即使是为了测试组件,开发人员也必须涉足部署人员的领地,并且还要准备配置应用服务器。

配置 JNDI 引用

  为了让 JNDI 解析 java:comp/env/jdbc/mydatasource 引用,部署人员必须把 <resource-ref> 标签插入 web.xml 文件(Web 应用程序的部署描述符)。 <resource-ref> 标签的意思就是“这个组件依赖于外部资源”。清单 3 显示了一个示例:

清单 3. resource-ref 入口



<resource-ref>
  <description>Dollys DataSource</description>
  <res-ref-name>jdbc/mydatasource</res-ref-name>
  <res-ref-type>javax.sql.DataSource</res-ref-type>
  <res-auth>Container</res-auth>
</resource-ref>

  <resource-ref> 入口告诉 servlet 容器,部署人员要在 组件命名上下文(component naming context) 中设置一个叫做 jdbc/mydatasource 的资源。组件命名上下文由前缀 java:comp/env/ 表示,所以完全限定的本地资源名称是: java:comp/env/jdbc/mydatasource.

  这只定义了到外部资源的本地引用,还没有创建引用指向的实际资源。(在 Java 语言中,类似的情况可能是: <resource-ref> 声明了一个引用,比如 Object foo,但是没有把 foo 设置成实际引用任何 Object。)

  部署人员的工作就是创建 DataSource(或者是创建一个 Object 对象,让 foo 指向它,在我们的 Java 语言示例中就是这样)。每个容器都有自己设置数据源的机制。例如,在 JBoss 中,是利用服务来定义数据源(请参阅 $JBOSS/server/default/deploy/hsqldb-ds.xml,把它作为示例),它指定自己是 DataSource 的全局 JNDI 名称(默认情况下是 DefaultDS)。在创建资源之后,第三步仍然很关键:把资源连接或者绑定到应用程序组件使用的本地名称。在使用 Web 应用程序的情况下,是使用特定于供应商的部署描述符扩展来指定这个绑定,清单 4 中显示了一个这样的例子。(JBoss 用称为 jboss-Web.xml 的文件作为特定于供应商的 Web 应用程序部署描述符。)

清单 4. 用特定于供应商的部署描述符将资源绑定到 JDI 名称



<resource-ref>
  <res-ref-name>jdbc/mydatasource</res-ref-name>
  <jndi-name>java:DefaultDS</jndi-name>
</resource-ref>

  这表明应该将本地资源引用名称( jdbc/mydatasource)映射到名为 java:DefaultDS 的全局资源。如果全局资源名称出于某种原因发生了变化,而应用程序的代码无需变化,那么只需修改这个映射即可。在这里,有两个级别的间接寻址:一个定义并命名资源( java:DefaultDS),另一个把特定于本地组件的名称( jdbc/mydatasource)绑定到命名的资源。(实际上,当您在 EAR 级别上映射资源时,可能还存在第三级别的间接寻址。)

超越数据源

  当然,J2EE 中的资源并不局限于 JDBC 数据源。引用的类型有很多,其中包括资源引用(已经讨论过)、环境实体和 EJB 引用。特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一项关键角色:查找其他应用程序组件。

  试想一下这种情况:当一家公司从 Order Ontology Processing Services(OOPS)购买了一个可部署的 EJB 组件来处理客户订单时,会发生什么。为了便于举例说明,我们把它叫做 ProcessOrders V1.0。ProcessOrders 1.0 有两部分:一组接口和支持类(home 和 remote 接口,以及支持的传输类);实际 EJB 组件自身。选择 OOPS 是因为它在这个领域的专业性。

  该公司遵照 J2EE 规范,编写使用 EJB 引用的 Web 应用程序。公司的部署人员把 ProcessOrders 1.0 绑定到 JNDI 树中,将它用作 ejb/ProcessOrders/1.0,并解析 Web 应用程序的资源名称,以指向这个全局 JNDI 名称。目前为止,这些都是 EJB 组件非常普通的用法。但是,当我们考虑公司的开发周期与公司供应商之间的交互时,事情就变得复杂起来。在这里,JNDI 也能帮助我们。

  我们假设 OOPS 发布了一个新版本,即 ProcessOrders V1.1。这个新版本有一些新功能,公司内部的一个新应用程序需要这些新功能,而且很自然地扩展了 EJB 组件的业务接口。

  在这里,公司有以下几个选择:可以更新所有应用程序来使用新版本,也可以编写自己的版本,或者使用 JNDI 的引用解析,允许每个应用程序在不影响其他应用程序的情况下使用自己的 EJB 组件版本。立刻更新所有应用程序对维护来说是一场噩梦,它要求对所有组件都进行完整的回归测试,这通常是一项艰巨的任务,而且如果发生任何功能测试失败的话,那么还要进行另一轮调试。

  编写内部(in-house)组件常常是没有必要的重复工作。如果组件是由在这个业务领域内具有专业知识的公司编写的,那么给定的 IT 商店不可能像专业的组件供应商那样精通业务功能。

  正如您可能已经猜到的那样,最好的解决方案是用 JNDI 解析。EJB 的 JNDI 引用非常类似于 JDBC 资源的引用。对于每个引用,部署人员都需要把新组件按特定的名称(比如说 ejb/ProcessOrders/1.1)绑定到全局树中,对于需要 EJB 组件的其他每个组件,还要为组件在部署描述符中解析 EJB 引用。依赖于 V1.0 以前的应用程序不需要进行任何修改,也不需要重新测试,这缩短了实现的时间、降低了成本并减少了复杂性。在服务趋于转换的环境中,这是一种很有效的方法。可以对应用程序架构中所得到的所有组件进行这类配置管理,从 EJB 组件到 JMS 队列和主题,再到简单配置字符串或其他对象,这可以降低随时间的推移服务变更所产生的维护成本,同时还可以简化部署,减少集成工作。

结束语

  有一个古老的计算机科学笑话:每个编程问题都可以仅仅用一个抽象层(或间接的)来解决。在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,但还没有紧到无法让人很容易地把它们分开并重新装配。JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。实际上,它要比许多人想像的容易得多。

2007年8月21日星期二

关于浏览器的"内壳"和"外壳"

什么是浏览器的“内壳”和“外壳”
  “内核”只是一个通俗的说法,其英文名称为“Layout engine”,翻译过来就是“排版引擎”,也被称为“页面渲染引擎”(下文中各种说法通用)。它负责取得网页的内容(HTML、XML、图像等等)、整理信息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要排版引擎。
  而浏览器的“外壳”便很好理解了,它是一个面向用户的界面,也就是网页浏览器为我们实现的各种功能。

浏览器的内核有哪些
  常见的有Trident、Gecko、WebCore、Presto等。

Trident内核
  Trident(又称为MSHTML),是微软开发的一种排版引擎。它的第一个版本随着1997年10月Internet Explorer第四版的发布而发布。随后,Trident不断地被更新和完善:
  Trident II(IE5)——增进对CSS1.0的支持及对CSS2重大的变更
  Trident III(IE5.5)——修正部分CSS的排版控制
  Trident IV(IE6)——修正了一部分box-model的错误以及增加了“兼容模式(Quirks Mode)”切换功能,以增加对文件类型描述(Document Type Definition,DTD)的支持
  Trident V(IE7)——修正许多CSS排版处理上的错误以及增加对PNG格式alpha通道(半透明)的支持
  不光是IE,Windows中的许多地方也使用了Trident的技术,包括从Windows 98到Windows Server 2003所有版本的Windows Explorer、Windows 98及其后续所有版本的视窗操作系统内的Windows Help程序。此外,像RealPlayer、Windows Media Player、Windows Live Messenger、Outlook Express等等也使用了Trident技术。也正因为如此,Trident无法从Windows系统中彻彻底底地卸载掉,就算是彻底卸载了,Windows的许多功能也会出现问题。
使用Trident内核的网页浏览器有:AOL Explorer、Avant、Internet Explorer、Maxthon、Netscape Browser (v.8)、Sleipnir、TheWorld、腾迅TT等。

Gecko内核
  Gecko是套开放源代码的、以C++编写的网页排版引擎。这软件原本是由网景通讯公司开发的,现在则由Mozilla基金会维护。
由于Gecko是开源的,所以使用Gecko的网页浏览器也很多,其中的领军人物便是Mozilla Firefox。此外还有AOL for Mac OS X、Camino、Epiphany、Flock、Galeon、Netscape Browser (v.8)、Sleipnir、Maxthon等。

WebCore内核
  WebCore是苹果公司开发的排版引擎,它是在另外一个排版引擎“KHTML”的基础上而来的。苹果电脑于2002年采纳了KHTML,作为开发Safari浏览器之用,并发布所修改的最新及过去版本源代码。后来发表了开放源代码的WebCore及WebKit引擎,它们均是KHTML的衍生产品。使用WebCore的主要有Safari,此外还有OmniWeb、Shiira、Swift等。

Presto内核
  Presto是一个由Opera Software开发的浏览器排版引擎,供Opera 7.0及以上使用。此外,Macromedia Dreamweaver (MX版本及以上)和Adobe Creative Suite 2也使用了Presto的内核。

浏览器的内核和外壳的联系
  浏览器的内核负责处理网页的内容,而外壳负责提供给用户一些功能。外壳的某些功能是需要联系到内核的,比如保存网页上的图片;而有些功能是不需要经过内核的,比如前进后退功能(当然,在载入了URL以后还是需要经过内核的)。
  相信大家都使用过XP中控制面板里的“用户帐户”功能,这实际上可以给我们一个更加感性的认识。用户帐户的操作界面实际上就是一个小型且功能单一的网页浏览器,它通过调用IE内核来显示主界面,而上方的“上一步”“下一步”等按钮便是浏览器外壳所提供的功能。
  说到这里,我们可以回看文章开头提到的一些网友的言论。有些人认为,Maxthon仅仅是附庸在IE之上的一个浏览器,根本不值得和IE、Firefox相提并论。这其实并不妥当。首先,Maxthon是支持Trident和Gecko双内核的(支持Gecko内核时需要单独安装Gecko组件,而且目前的2系列貌似没有Gecko内核支持),所以它并不是“IE的附庸”。此外,我们已经知道,浏览器分有内核和外壳两部分。以这个眼光来看,Maxthon是Maxthon Shell+Trident(或Gecko),而Internet Explorer是IE Shell+Trident,Firefox是FF Shell+Gecko,这三款浏览器的地位实际上是一样的。只不过IE Shell和Trident都是微软开发,而FF Shell和Gecko都是Mozilla基金会在维护。同时,说IE是“IE内核”,FF是“FF内核”也是不准确的,毕竟弄IE Shell和Trident的肯定不是微软的同一个小组,而FF Shell和Gecko肯定也不是Mozilla基金会的同一个小组在维护(更何况Gecko原来也不是Mozilla开发的,原先Mozilla只是网景公司的非正式组织)。当然不可否认,拥有了内核和外壳的微软和Mozilla,在开发浏览器的能力方面肯定是要高于Maxthon的,Maxthon不可能掌握到关键技术。

各种内核哪一个更好一点呢
  哪一个更好,这个问题的确很难回答,应该说是各有侧重点。Trident内核使用最多,因此很多网页甚至只兼容Trident内核,而导致使用Gecko时会出现各种各样的问题。但是Trident也是最“乱”的一个内核。而Gecko、WebCore、Presto则更符合各种国际标准,显得更加“严谨”。

2007年5月23日星期三

JSON

介绍 JSON
  JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScript Programming Language(http://www.crockford.com/javascript), Standard ECMA-262 3rd Edition - December 1999(http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf)的一个子集。JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。

JSON建构于两种结构:
  “名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
  值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。
  这些都是常见的数据结构。事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在同样基于这些结构的编程语言之间交换成为可能。

JSON具有以下这些形式:

  对象是一个无序的“‘名称/值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个“名称”后跟一个“:”(冒号);“‘名称/值’ 对”之间使用“,”(逗号)分隔。
  数组是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。
  值(value)可以是双引号括起来的字符串(string)、数值(number)、 ture、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。
  字符串(string)是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。
  除去一些编码细节,以下描述了完整的语言。
  字符串(string)与C或者Java的字符串非常相似。除去未曾使用的八进制与十六进制格式,数值(number)也与C或者Java的数值非常相似。
  空白可以加入到任何符号之间。

2007年5月15日星期二

Servlet的事件监听应用

Servlet的事件监听应用

  在Servlet2.3 规范中添加了一些监听Web应用中重要事件的能力。这项功能可以让我们根据事件的状态更有效的对资源进行管理和自动化进行。这部分描述了servlet的事件监听,包含以下部分:
  1.事件的分类和Listener接口
  2.典型的事件监听过程
  3.事件监听的声明和调用
  4.事件监听编码和发布向导
  5.事件监听的方法和相关的类

  事件的分类和Listener接口
  Servlet事件有两个级别:
  1.Application级别事件
  包含着运行应用程序的虚拟机级别的相关资源和状态,即和servlet的Context对象相关。
  2.Session级别的事件
  包含着一个单一用户的session的一系列请求的相关资源和状态,即Http的Session对象。

  在上面两个级别的事件,又可分别分为两种:
  1.生命周期的改变
  2.属性的改变
  你可以为上面四种事件创建一个或多个监听类。一个单一的监听类可以监视多种事件。
  创建一个事件类可以从javax.servlet包或javax.servlet.http包中实现合适的接口。下表中列出了四种事件相关的接口。
事件种类事件描述接口
Context生命周期的改变context的建立和即将关闭contextJavax.servlet.ServletContextListener
Context属性值的改变添加,删除,修改context的属性值Javax.servlet.ServletContextAttributeListener
Session生命周期的改变Session的创建,注销,超时Javax.servlet.http.HttpSessionListener
Session属性值的改变添加,删除,修改session的属性值Javax.servlet.htpp.HttpSessionAttributeListener


  典型的事件监听过程
  考虑一个web应用是由一组访问数据库的servlet组成的。一个典型的事件监听机制是这样的,创建一个context生命周期的事件来管理数据库连接,这个监听器可以有如下的功能:
  1.这个监听器监视着应用程序的启动
  2.这个应用程序写入日志到数据库中并且把连接对象存储在context中
  3.Servelt使用连接对象来执行SQL
  4.监听器监听应用程序的即将关闭
  5.在关闭应用程序之前,先关闭连接对象

  事件监听的声明和调用
  事件监听的声明在应用程序的web.xml里,用<listener>元素,该元素是<web-app>的子元素。每个监听器都对应一个<listener> ,有一个<listener-class>子元素用来指定对应的类名。在每种事件中,你需要指定你想调用的顺序。
  在应用程序启动之后,并且在第一次请求之前,servlet容器会创建并注册每个监听类的实例。每种事件,监听器是按照他们声明的顺序来注册的。然后,当应用程序开始运行,每种事件监听器安装他们的顺序调用。在最后一次请求之前,所有的监听器都保持活动状态。
  一旦应用程序关闭,session事件首先发生,以他们声明的顺序相反。然后context事件发生也是以声明的顺序相反。
  下面是一个例子:
<web-app>
  <listener>
    <listener-class>myproject.ApplicationListener</listenerclass>
  </listener>
  <listener>
    <listener-class>myproject.SessionListener</listener-class>
  </listener>
  <listener>
    <listener-class>myproject.RequestListener</listener-class>
  </listener>
</web-app>
  假设ApplicationListener是实现ServletContextListener接口,SessionListener是实现了HttpSessionListener接口,RequestListener是实现了ServletRequestListener接口
当应用程序运行,ApplicationListener监听器监听context生命周期事件,SessionListener监听器会监听session生命周期,RequestListener监听器会监听request生命周期。

  事件监听编码和发布向导
  1.在多线程的应用程序中,属性可能同时改变。这是不需要Servlet容器来同步结果,在这种情况下监听类本身负责保持数据的完整性。
  2.每个监听类都必须有一个public的零参数的构造函数。
  3.每个监听类文件必须打包到WAR文件,也可以是在/WEB-INF/classes或是包含在/WEB-INF/lib下的JAR文件中。
  (注:在分布式的环境中,事件监听类的作用域是包含这个部署描述文件的虚拟机。不需要分布式的Web容器来传递servlet的context事件或是 session事件到其他的虚拟机。这个在Sun Microsystem的Java Servlet 规范,2.3版本。)

  事件监听的方法和相关的类
  当servlet的context事件或是servlet的session事件发生时,容器将会调用他们。这些方法的输入的事件对象的类型不一样,因此一下讨论事件类和他们的方法。
  ServletContextListener方法,ServletContextEvent类
  ServletContextListener接口规范以下的方法:
    void contextInitialized(ServletContextEvent sce)
    servlet容器调用这个方法来通知监听器,servlet的context已经建立并且应用程序准备处理请求。
    void contextDestory(ServletContextEvent sce)
    servlet容器调用这个方法来通知监听器应用程序即将关闭。
  Servlet容器创建一个 java.servlet.ServletContextEvent对象作为调用ServletContextListener方法的输入。ServletContextEvent类包含以下方法,你的监听器可以调用。
    ServletContext getServletContext()
    用这个方法返回已创建的或是将要销毁的servlet context对象,从中你可以得到你想要的信息。

2007年5月14日星期一

Servlet、JSP、Taglib 概述

Java Web 技术概述

Servlet
  Servlet是运行在称为servlet/JSP容器或Web容器的特殊Web服务器中的Java类。在编写servlet程序时,使用以下两个包中的类和接口:

  javax.servlet其中包含用于servlet编程的基本类型。
  javax.servlet.http其中包含扩展自javax.servlet包中的相应类型的接口和类。
  这两个包中最重要的类型是javax.servlet.Servlet接口,所有的servlet都要实现此接口(或是从实现了此接口的类继承)。

使用Servlet接口
  javax.servlet.Servlet接口定义了3个生命周期方法:init、service和destroy。servlet容器会在servlet的生存期间调用这些方法。(注:直接实现Servlet接口的另一个方法是扩展javax.servlet.GenericServlet类或是javax.servlet.http.HtpServlet类)。

1、init方法
  第一次请求servlet时,会调用init方法。它仅被调用一次,以通知servlet其已经被装入服务中,可以重写此方法,为其提供只需执行一次的初始化代码,比如初始化某些值、装入数据库驱动程序等。
  此方法原型是:
  public void init(ServletConfig config) throws ServletException

2、service方法
  每次调用servlet时,servlet容器就会调用相应servlet的service方法。对于每个HTTP请求,servlet容器会创建一个请求对象(实现javax.servlet.ServletRequest或  javax.servlet.http.HttpServletRequest)和一个响应对象(实现javax.servlet.ServletResponse或javax.servlet.http.HttpServletResponse),并将它们传给servlet的service方法。请求对象封装了从HTTP请求里解析出来的、对servlet有用的信息。比如请求的URL、请求头、cookie及请求的参数等。当请求处理完毕以后,servlet使用响应对象将响应发送回Web客户端。
  此方法原型是:
  public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException

3、destroy方法
  在删除一个servlet实例前,servlet容器会调用其destroy方法,这种情况一般发生在servlet容器需要被关闭或释放内存时。可以使用此方法来清除占用的资源(如内存、文件句柄及线程等)或确保某些持续状态与内存中的servlet的当前状态一致等。
  此方法原型是:
  public void destory()

部署描述符
  部署描述符是一个XML文件,所以可以使用文本编辑器来编辑它。
  符合servlet 2.3规范的应用部署描述符是以如下形式开始的:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

1、Servlet映射
  在Web浏览器中访问servlet之前,需要先在部署描述符里将其映射到一个路径
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <servlet>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>myproject.MyServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/myservlet</url-pattern>
  </servlet-mapping>
</web-app>
  使用URL来访问该servlet:
  http://localhost:8080/MyProject/myservlet

2、定义和获取上下文参数
  在部署描述符节(web.xml 文件)里,可以为其所描述的应用定义设置。可以使用此方法来定义初始的上下文参数(这些参数对于应用中所有的servlet/JSP页面都可用)、注册servlet、注册监听器、把资源映射到URL等。
  使用上下文参数,不必再将某些信息硬编码在servlet的代码里。这样,如果需要改变这些信息,就无需重新编译servlet。
  可以使用content-param元素指定可用于应用中所有servlet/JSP的上下文参数名/值对。
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <content-param>
    <param-name>userName</param-name>
    <param-name>budi</param-name>
  </content-param>
  <content-param>
    <param-name>password</param-name>
    <param-name>secret</param-name>
  </content-param>
</web-app>

  为了获取部署描述符里的上下文参数,首先需要通过ServletConfig对象的getServletContext获得ServletContext对象,然后再调用ServletContext接口的两个方法:
  getInitParameterNames方法,此方法无需传入参数,并且返回一个包含所有上下文参数名的枚举类型(java.util.Enumeration)。
  getInitParameter方法,此方法需要传入一个String类型的参数,包含所要获取的上下文参数的名称,并且返回包含参数值的String。

监听应用事件
  javax.servlet包中提供了两个监听器接口,利用它们的支持,可在ServletContext对象的状态发生改变时得到通知,它们是ServletContextListener接口和ServletContextAttributeListener接口。
  使用ServletContextListener接口可监听其言观其行ServletContext对象的生命周期事件,它有以下两个方法:
  contextInitialized方法。当Web应用准备好提供服务时。此方法会被调用,Servlet容器在自身的初始化工作完成以后会自动调用它,您可以在这里编写需要在应用初始化时执行的代码,比如加载JDBC(Java数据库连接)驱动程序、创建数据库Connection对象或是给全局变理赋初始化值等。
  contextDestroyed方法。当servlet上下文将要关掉的时候会调用此方法,可以在这里编写需要在应用关闭时执行的代码,比如关闭数据库连接或是写入到日志等。
  这两个方法的原型是:
  public void contextInitialized(ServletContextEvent sce)
  public void contextDestroyed(ServletContextEvent sce)
  为了使监听器实现类(如:ApplicationListener实现了ServletContextListener接口)工作,需要在部署描述符里注册。
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <listener>
    <listener-class>ApplicationListener</listener-class>
  </listener>
</web-app>

  每次应用启动时(当一个servlet或JSP页面第一次被调用时)调用contextInitialized方法,每次应用停止服务时调用contextDestroyed方法。

JSP
  Sun公司在1996年引入servlet,它很快就流行起来,因为它比公共网关接口(Common Geatway Interface, CGI,第一种用来编写动态Web应用的技术)要快。但Sun意识到编写servlet太麻烦,特别是当需要发送一个长的HTM页面时。
  Sun对此问题的解决方案是JSP。按照Sun的网站所说的:"JSP技术是servlet技术的扩展,用来支持编写HTML和XML页面"。用JSP可以容易地混合固定的、静态的模板数据与动态的内容。用它开发比只用servlet开发要快,因为它允许直接将HTML标记放在Java代码里,而且无需编译。当JSP页面第一次被调用时,servlet/JSP容器自动编译它。
  JSP并设让servlet变得过时。事实上,在很多Web应用里都同时有JSP页面和servlet。而且,不要忘记JSP是servlet的扩展。
  在JSP容器里有一个特殊的servlet,叫作页面编译器,servlet容器被配置成将带有匹配*.jsp文件扩展名的URL和HTTP请求转发给此页面编译器。就是这个页面编译器将servlet容器变成了JSP容器。当一个JSP页面第一次被调用的时候,页面编译器解析此JSP页面并试图将其编译成一个servlet类,如查编译成功,则此servlet类被载入内存。
  在后续的调用里,对应该JSP页面的servlet已经在内存里,但是JSP页面可能已被更新过,所以,页面编译器servlet会比较JSP servlet和JSP页面的时间戳,如果JSP页面比较新,则需重新编译。通过此过程,一旦部署完成,JSP页面只需经历一次费时的编译过程。

JSP和JavaBeans
  为了加快JSP应用的开发过程,需要将界面设计人同的工作与Java程序设计人员的工作分开,使用JavaBeans可以做到这一点。在这种方式下,Java程序设计人员负责把应用所需的所有功能都写进JavaBeans里。与此同时,界面设计人员可进行页面设计,当JavaBeans完成以后,页面设计人员使用与HTML相似的标记来从JSP页面中调用JavaBeans的方法或获取其属性。
  事实上,在JSP应用的开发过程中使用bean是一种很常用的方法,这种方法流行的原因在于它的可读性。
  其实,JavaBeans就是一个Java类,您无需扩展特定的基类或实现接口来生成一个JavaBeans。但为了成为一个bean,Java类必须遵守JavaBeans规范指定的特定规则。比如,bean类必须有一个无参数的构造函数。

  1、设置和获取JavaBean的属性
  bean可以有一个公有方法来设置属性的值,此方法称为setter或mutator,它不返回任何值,名称以set打头,所跟属性名。此方法的原型为:
  public void setPropertyName(PropertyType value);
  比如,属性operand的setter方法名必须是setOperand。
  同样作为一种选择,bean可以有一个公有方法来获取属性值。此方法称为getter或accessor,它的返回值类型是属性的类型,名称以get开头,后跟属性名。此方法的原型为:
  public propertyType getPropertyName();
  比如,属性operand的getter方法名必须是getOperand。
  setter和getter方法统称为访问方法(acces method)。在JSP中,jsp:getProperty和jsp:setProperty动作元素就分别用来调用getter和setter方法。

  2、从JSP页面中调用bean
  在可以从JSP页面中使用一个bean之前,必须先使用jsp:useBean动作元素使其可用。该元素有用于设置bean属性值的特性。
  jsp:useBean元素的语法:
  
  下面是可用的jsp:useBean动作元素的5个特性(attribute):
  • id 特性定义了bean的惟一标识符,此标识符在整个页面范围内都可用,可以将其看作是bean的对象引用。id 特性的值必须满足当前所用脚本语言的变量命名规则。
  • class特性指明了JavaBean类的完全限定的名称,但如果bean所在的包已经使用page指令导入过,则无需完全限定的名称。
  • type特性,如果此特性出现在jsp:useBean元素中,则它指明JavaBean类的类型。bean的类型可以是类的类型,也可以是其超类的类型,或是bean类实现的接口。一般来说,不常用这个特性,而是用class特性。
  • scope特性定义了bean的可访问性和生命期限。此特性的值可以是page、request、session及application中的一个。其默认值是page。scope特性的值控制bean能够存在多长时间,具体如下所述:
    • page bean 仅在当前页面中使用jsp:useBean动作元素定义它的位置后可用。每次请求这个页面都生成bean的一个新实例,并在超出JSP页面范围以后自动销毁此实例,比如在控制转移到其他页面时,如果您使用jsp:include或jsp:forward标记,那么在包含的页面或是转发的页面里不能访问这个实例。
    • request bean的可访问性扩展到jsp:include或jsp:forward动作元素所引用的包含或转发的页面,包含及转发的页面无需 jsp:useBean动作元素即可使用bean。比如,可以在一个包含或是转发的页面里使用jsp:getProperty及jsp:setProperty动作元素访问原页面中实例化的bean。
    • session bean的作用域为用户的会话(session)对象,只要用户会话对象还存在,bean的实例就一直存在。换句话说,bean的可访问性扩展到了其他页面。但如果实例化bean的页面没有加入到JSP容器的会话管理,那么就不能使用这个作用域。因为bean的实例被放入到会话对象中。
    • application 在该作用域下,bean的存在时限与JSP容器一样长,在应用的任何资源中都可以访问bean的实例。
  • beanName特性是指实例化bean时java.beans.Beans类的实例化方法所需的bean名称。


Taglib 定制标记
  使用JavaBeans可以将JSP页面的表示部分从Java代码里分离出来,但只有3个动作元素java:useBean、jsp:getProperty和jsp:setProperty可用来访问bean。所以在某此情况下,我们还是要求助于在JSP页面中使用Java代码。认识到JavaBeans并不是分离表示与业务规则实现的良好方案,JSP1.1定义了一种新功能:可用来执行自定义动作的定制标记。

  1、定制标记和JavaBeans的比较
  与JavaBeans相比,定制标记有一些好处:
  • 定制标记可访问JSP页面里所有对象。
  • 定制标记可通过特性来定制。

  但是,定制标记也有缺点,它的建立和使用要比JavaBeans稍难一点。有时,由于其可得用性,JavaBeans更适合一些。

  2、开发和使用定制标库
  开发定制标记库包含3个主要步骤:
  • 编写标记处理程序。标记处理程序是一个包含了定制标记的逻辑Java类。每当在JSP页面里遇到一个定制标记时,JSP容器就找到与此定制标记对应的标记处理程序并执行它。
  • 编写标记库描述符(tag library descriptor, TLD)。TLD是一个定义了标记库及其标记的XML文件。
  • 编辑Web应用的部署描述符(web.xml文件)。要使用定制标记,需要在部署描述符里指定一个taglib元素。
  • 编写包含taglib指令的JSP页面。要在JSP页面使用定制标记,需要使用taglib指令来标识TLD和标记。


  <1>、编写标记处理程序
  通常使用javax.servlet.jsp.tagext包中的接口和类来编写标记处理程序。
  标记处理程序必须实现Tag、IterationTag或BodyTag接口,或者是从实现其中某个接口的类继承。


  Tag 接口
    Tag接口包含下列方法:doStartTag、doEndTag、getParent、setParent、setPageContext及release。JSP容器与Tag接口交互的过程如下:
    (1)、JSP容器实例化标记处理程序或从池中取出一个标记处理程序的实例,并且调用其setPageContext方法,传入一个表示定制标记所在JSP页面的PageContext对象。
    (2)、JSP容器调用setParent方法,传入一个表示最近的、包含当前标记处理程序的标记对象。如果当前标记不在任何标记内,此对象为null。
    (3)、JSP容器设置定制标记的所有特性(如果有的话)。标记特性的处理方法与JavaBean属性的处理方法相似,也是通过getter和setter方法处理。
    (4)、JSP容器调用doStartTag方法。此方法可返回Tag.SKIP_BODY或Tag.EVAL_BODY_INCLUDE。如果返回Tag.SKIP_BODY,JSP容器就不再处理标记的主体内容;如果返回Tag.EVAL_BODY_INCLUDE,则还会处理标记的主体内容(如果有的话)。
    (5)、不管doStartTag方法返回什么,JSP容器都会接着调用doEndTag方法。此方法可返回Tag.SKIP_PAGE或tag.EVAL_PAGE,如果返回Tag.SKIP_PAGE,JSP容器或不再处理此JSP页面的剩余部分;如果返回Tag.EVAL_PAGE,则继续处理页面的剩余部分。
    (6)、JSP容器调用release方法,可以在此方法的实现中编写清除代码。
    (7)、JSP容器将标记处理程序的实例放回池中以备后面使用。
  IterationTag 接口
    IterationTag接口扩展Tag接口,它额外还有一个doAfterBody方法和一个静态的final整形变量EVAL_BODY_AGAIN。JSP容器调用标记处理程序的doAfterBody方法实现,并且在调用doStartTag方法以后实现IterationTag。doAfterBody方法可返回Tag.SKIP_BODY或IterationTag.EVAL_BODY_AGAIN,如果返回IterationTag.EVAL_BODY_AGAIN,JSP容器还会再次调用doAfterBody方法;如果返回Tag.SKIP_BODY,则会结束标记和处理,并且JSP容器将传而调用doEndTag方法。
  BodyTag 接口
    BodyTag接口实现Tag接口或IterationTag接口不能访问定制标记的主体内容。如果需要操作标记的主体内容,必须实现BodyTag接口。BodyTag接口扩展了IterationTag接口,并且添加了doInitBody和setBodyContent两个方法,以及EVAL_BODY_BUFFERED和EVAL_BODY_TAG两个静态的final整形变量。JSP容器在调用doStartTag方法以后会调用setBodyContent方法。而doInitBody方法则在调用setBodyContent方法以后被调用。
    实现BodyTag接口的标记处理程序可在doStartTag方法中返回SKIP_BODY、EVAL_BODY_INCLUDE或EVAL_BODY_BUFFERED。如果返回EVAL_BODY_BUFFERED,则会生成一个表示定制标记主体内容的BodyContent对象。
    doInitBody方法可用来为对标记主体的处理进行准备。通常,此方法在setBodyContent方法后调用。但如果定制标记没有主体,或是doStartTag方法返回了SKIP_BODY或EVAL_BODY_INCLUDE,则doInitBody方法不会被调用。
  BodyContent类
    BodyCOntent类是一个扩展javax.servlet.jsp.JspWriter类的抽象类。它表示定制标记的主体内容(如果有的话)。可以从BodyTag接口的setBodyContent方法中获得主体内容。
  TagSupport和BodyTagSupport类
    javax.servlet.jsp.tagext包还提供了一些支持类,您可以扩展这些类来创建标记处理程序。扩展这些类而不直接实现接口的好处是可以仅为需要重写的方法提供实现,这样就只需要更少的代码。TagSupport类实现了IterationTag接口,BodyTagSupport类则实现了BodyTag接口。

  <2>、编写标记库描述符(TLD)
  TLD是一个描述标记库的XML文档。对它的验证是基于一个DTD文件,是前DTD的最新版本是1.2。TLD必须通过以下报头作为开始。
  <?xml version="1.0" encoding="ISO-8859-1" ?>
    <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Jsp Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
  TLD文件根元素是<taglib>。taglib可包含的子元素如下:
  <!ELEMENT taglib (tlib-version, jsp-version, short-name, uri?,display-name?, small-icon?, large-icon?, description?, validator?, listener*, tag+) >
  taglib必须包含下列元素:
  • tlib-version元素:指明标记库实现的版本。
  • jsp-version元素:定义了标记库可在其上工作的JSP版本。
  • short-name元素:定义了一个惟一的标记库名称。
  • tag元素:说明标记库里的一个定制标记。


  <3>、编写标记库描述符(TLD)
  要在JSP页面中使用定制标记,需要熟悉JSP里的taglib指令。Taglib指令的语法如下:
  <%@ taglib uri="tagLibraryURL" prefix="tagPrefix" %>
  uri特性指明明了惟一确定与此前缀相关联的TLD文件的绝对URL或相对URI,而prefix特性则定义一个用来区别该定制标记库的前缀字符串。
  有了taglib指令以后,可以通过格式使用不带主体内容的定制标记:<prefix:tagName />,或都用以下形式使用带有主体内容的定制标记:<prefix:tagName>Body</prefix:tagName>(注:tagName代表定制标记名称)。
  下面的例子是一个叫作myTag的定制标记。它的前缀是m,有两个特性:特性numbe的值是12,特性power的值是13。
  <m:myTag number="12" power="13" />

2007年2月9日星期五

面向对象程序员 JavaScript指南

JavaScript不是Java
  Java和JavaScript的名字中蕴含着的是大量的市场考虑,而实质相对很对。JavaScript由“livescript”改名而来,是Netscape市场部在最后一刻决定的,现在这个名字已经被广与为接受。与一般的理解相反,JavaScript并不源自C系列语言,它的思想大多来源于类似Scheme和Self的函数式语言(functional language),与Python也有很多共同之处。很遗憾,它的名字以Java来命名,从语法样式上很像Java。在一些场合,它的行为与Java相同;但是在其他很多场合,它的行为与Java并不相同。
JavaScript的关键特征及其含义
特征含义
变量是弱类型的(loosely typed)变更仅仅声明为变量,而不是整数、字符串或者特定类的对象,在JavaScript中,给同一个变量分配不同的类型是合法的
代码是动态解释的与需要预编译的语言(例如Java、C、C#)相比,在运行时,JavaScript代码以文本形式保存并且在程序运行时解释为机器指令,Web网站的用户通常可以看到Ajax应用的源代码。而且,这使得通过其他代码动态生成代码成为了可能,而无需求助于特殊的字节码生成器
JavaScript函数是正常对象Java对象的方法与拥有它的对象绑在一起,只能通过该对象来调用。JavaScript函数可以附加到对象上,使得它们的行为类似于方法。但是它们也可以在其他上下文中调用,并且在运行时附加到其他对象上
JavaScript对象是基于prototype(原型)的Java、C++或者C#对象有一种定义类型:超类、虚的越类(即接口)。这严格定义了它的功能。任何JavaScript对象仅仅是一个对象,它仅仅是一个化装的关联数组。prototype在JavaScript中可以用来模拟Java风格的类型,但这只是表面相似

  这些特征使JavaScript能够以不同的方式来使用。同时也为很多古怪的技巧创造了机会,这些技巧值得经验丰富的Lisp黑客研究一番。如果你是一位聪明并受过专业训练的编码人员,可以利用这些技巧来做一些非凡的事情。甚至可能只用几百行代码就做到这些事情。另一方面。如果你自认为确实聪明并受过专业训练,那么你可能很快就会感到羞愧的。

JavaScript中的对象
  JavaScript不要求使用对象,甚至不要求使用函数。可以将JavaScript程序编写成一个文本流,当解释器读取它时直接执行。随着程序逐渐变大,函数和对象成为组织代码的极其有用的方式,我们建议两者都使用。
  创建一个新的JavaScript对象的最简单的方法是调用Object类内建的构造函数:

var myObject = new Object();

  这里对象myObject初始化为“空”,即它不包含属性或者方法。

创建即时对象
  前面曾以提到过,JavaScript对象本质上是一个关联数组,由以名称作为键的字段和方法组成。在上面修饰了一层类似于C语言的语法,使得它对于C系列的程序员更加熟悉,但是底层实现可以以其他方式使用。我们以每次一行的方式建立复杂的对象,对我们所认为的方式添加新的变量和函数。
  有两种以即时的方式创建对象的方法。第一种是直接使用JavaScript来创建对象。第二种是使用JSON来创建对象。
使用JavaScript语句
  在复杂的代码中,我们可能希望给一些对象属性赋值。JavaScript对象属性是可读/可写的,可以使用 = 操作符来赋值。我们将一个属性添加到刚才创建的简单对象上:

myObject.shoeSize = "12";

  在面向对象语言中,我们需要定义一个类来声明属性shoeSize,否则就会出现编译错误。对于JavaScript这是不必要的。事实上,仅仅为了强调对象类似于数组的本性,我们也可以使用数组的语法来引用属性:

myObject["shoeSize"] = "12";

  这种写法除了一个优点(即数组索引是一个JavaScript表达式,这提供了一种运行时反射功能)以外,对于变通的使用来说显得很笨拙。
  可以给对象动态添加一个新的函数:

myObject.speakYourShoeSize = function(){
alert("shoe size : " + this.shoeSize);
}

  或者借用一个预先定义的函数:

function sayHello(){
alert("hello, my shoeSize is " + this.shoeSize);
}
...
myObject.sayHello = sayHello;

  当分配预先定义的函数时,我们省略了圆括号。

myObject.sayHello = sayHello();

  那么将执行sayHello函数,并且用它的返回值来给myObject的sayHello属性赋值,在这里 null。
  可以将对象附加到其他对象上,从而创建复杂的数据模型:

var myLibrary = new Object();
myLibrary.books = new Array();
myLibrary.books[0] = new Object();
myLibrary.books[0].title = "Turnip Cultivation through the Ages";
myLibrary.books[0].authors = new Array();
var jim = new Object();
jim.name = "Jim Brown";
jim.age = 9;
myLibrary.books[0].authors[0] = jim;


使用JSON
  JSON是语言的一个核心特征,它提供了一种创建数组和对象图(object graph)的简单机制。
  JavaScript有一个内建的Array类,可以使用new关健字初始化:

myLibrary.books = new Array()

  数组有按照数字来分配的值,非常像传统的C或Java数组。

myLibrary.book[4] = somePredefineBook;

  数组也可以使用一个键值来关联,像是Java的Map或者Python的Dictionary。实际上这可以应用于任何JavaScript对象:

myLibrary.books["BestSeller"] = somePredefineBook;

  这种语法对于微调很有利,但是作为首选来创建一个大型的数组或者对象就很乏味了。创建一个数字索引的数组的快捷方法是使用方括号,将所有的成员写成一个用逗号分隔的值的列表。

myLibrary.books = {predefineBook1,predefineBook2,predefineBook3};

  为了创建JavaScript对象,我们使用花括号,将每个值写成“键:值”对的形式:

myLibrary.books = {
bestSeller : predefineBook1,
cookbok : predefineBook2,
spaceFiller : predefineBook3
};

  在两种符号中,会忽略额外的空白。键的内部可以有空白,可以在JSON符号中使用引号来引用。

"Best Seller" : predefineBook1

  可以通过嵌套JSON符号来创建复杂对象层次的单行定义:

var myLibrary = {
location : "my house",
keywords : {"root vegetables","turnip","tedium"},
books : [
{
title : "Turnip Cultivation through the Ages",
authors : {
{name: "Jim Brown", age: 9},
{name: "Dick Turnip", age: 312 }
},
publicationData : "long ago"
},
{
title : "Turnip Cultivation through the Ages, vol. 2",
authors : [
{name: "Jim Brown", age: 35}
],
publicationData : new Date(1605,11,05)
}
]
};

  给myLibrary对象分配了3个属性,location是一个简单字符串,keywords是一个按数字索引的字符串列表,books是一个按数字索引的对象列表,每个对象有标题(字符串)、发布日期(代表JavaScript Data对象的字符串)和作者列表(数组)。每个作者由name和age参数来代表。
通过内建的JavaScript Object和Array类以及JSON符号,可以创建我们喜欢的任意复杂的对象层次,我们不再需要其他任何东西。JavaScript也提供了创建对象的方法,为面向对象程序员提供了令人舒适的类定义的相似性。

构造函数、类和原型
  在面向对象编程中,我们通常使用希望实望实例化的类的声明来创建对象。Java和JavaScript都支持new关键字,允许我们创建预先定义类别的对象的实例,在这里两者是相似的。
在Java中,所有的东西都是一个对象,最后都继承自java.lang.Object类。java虚拟机对于类、字段和方法具有内建的理解。
  JavaScript也有对象和类的概念,但是没有内建继承的概念。事实上,每个JavaScript对象是相同基类(一个有能力在运行时将成员字段和函数与自己绑在一起的类)的实例。所以,有可能在运行时给对象分配任意的属性。

MyJavaScriptObject.completelyNewProperty = "something";

  可以通过使用一个原型把这种完全自由的状态组织为低层次面向对象开发者更加熟悉的东西。当使用一个特定的函数来构造对象时,原型定义了将自动绑定在对象上的属性和函数。有可能编写基于对象的JavaScript,而不使用原型,但是当开发复杂的富客户端应用时,原型在一定程序上为面向对象开发都提供了他们非常想要的规律和熟悉感。
  在JavaScript中,我们就可以编写一些看起来与Java声明很相似的东西。

var myObj = new MyObject();

  但是我们没有定义类MyObject,而是定义了一个同名的函数。这里是一个简单的构造函数:

function MyObject(name,size){
this.name = name;
this.size = size;
}

  随后可以像这样来调用它:

var myObj = new MyObject("tiddles","7.5 meters");
alert("size of " + myObj.name + " is " + myObj.size);

  在构造函数中,设置为this的属性的任何东西随后都可以作为对象的一个成员来使用。我们也许还希望初始化对alert()的调用,这样tiddlers可以自己负责告诉我们它有多大。一种通常的习惯做法是在构造函数中声明这个函数。

function MyObject(name,size){
this.name = name;
this.size = size;
this.tellSize = function(){
alert("size of " + this.name + " is " + this.size);
}
}
var myObj = new Object("tiddles","7.5 meters");
myObj.tellSize();

  这段代码可以工作,但是在两个方面不尽理想。首先,每当创建一个MyObject的实例时,我们都会创建一个新的函数,这样会存在内存泄漏问题。其次,我们在这里无意中创建一个闭包(closure),一旦在构造函数中包括了DOM节点,可以会遇到更加严重的问题。可是考察一下更加安全的替代品:原形(prototype)。
  原型是JavaScript对象的一个属性,在面向对象语言中没有对等物。函数和属性可以与构造函数的原型关联起来。然后原型和new关键字协同工作,当使用new调用函数时,函数原形的所有属性和方法会附加到结果对象上。

function MyObject(name,size){
this.name = name;
this.size = size;
}

MyObject.prototype.tellSize = function(){
alert("size of " + this.name + " is " + this.size);
}

var myObj = new MyObject("tiddles","7.5 meters");
myObj.tellSize();

  我们像以前那样声明了构造函数,然后向原型添加函数。当我们创建一个对象实例时,函数附加在上面。关键字this在运行时确定为对象的实例。
  在声明构造函数之后,我们才能引用原形,对象只继承那些在调用构造函数之前已经添加到原型上的东西。原型可以在两次调用构造函数之间进行修改,并且可附加任何东西到原型上:

MyObject.prototype.color = "red";
var obj1 = new MyObject();

MyObject.prototype.color = "blue";
MyObject.prototype.soundEffect = "bo000oing!!";
var obj2 = new MyObject();

  obj1将红色的且没有声音效果,obj2将是蓝色的且有着令人愉快的声音效果!以这种方式在运行时修改原型通常价值不大。知道发生这种事情是很有用的,但是使用原型来为JavaScript对象定义类似于类的行为,是一条安全和可靠的路径。

扩展内建类
  JavaScript用来嵌入在那些能够向脚本环境暴露自己本地对象的程序中。在Web浏览器中,DOM节点在IE浏览器中不能扩展,但是其他的核心类在所有主要的浏览器中都是可以扩展的。

Array.prototype.indexOf = function(obj){
var result = -1;
for(var i=0; i<this.length; i++){
if(this[i] == obj){
result = i;
break;
}
}
return result;
}

  为Array对象提供了一个额外的函数,它返回一个给定数组中的对象的数字索引,如查数组不包含这个对象就返回-1。我们可以在这个基础上更进一步,编写一个方便的方法来检查数组是否包含对象:

Array.prototype.contains = function(obj){
return(this.indexOf(obj) >= 0);
}

  然后添加另外一个函数,在经过了可选的重复检查之后添加新的成员:

Array.prototype.append = function(obj,nodup){
if(!(nodup && this.contains(obj))){
this[this.length] = obj;
}
}

  任何在这些函数声明之后创建的Array对象,无论是使用new操作符或者作为JSON表达式的一部分来创建,都能够使用这些函数。

var numbers = [1,2,3,4,5];
var got8 = numbers.contains(8);
numbers.append("cheese",true);

  对于用户定义对象的原型而言它们能够操作在多个对象的创建过程之间,但是我通常建议原型只在程序的开始修改一次,以便避免不必要的混乱,特别是当一个程序员团队共同工作的时候。

DWR -- Easy AJAX for JAVA

DWR是一个Java开源库,帮助你实现Ajax网站。


它可以让你在浏览器中的Javascript代码调用Web服务器上的Java,就像在Java代码就在浏览器中一样。



DWR主要包括两部分:



  • 在服务器上运行的Servlet来处理请求并把结果返回浏览器。

  • 运行在浏览器上的Javascript,可以发送请求,并动态改变页面。DWR会根据你的Java类动态的生成Javascript代码。这些代码的魔力是让你感觉整个Ajax调用都是在浏览器上发生的,但事实上是服务器执行了这些代码,DWR负责数据的传递和转换。


这种Java和Javascript之间的远程调用会让DWR用户感觉像是曾经习惯使用的RMI或SOAP的RPC机制。而且这一过程还不需要额外的浏览器插件。



Java是同步的,而Ajax是异步的。所以当你调用一个远程方法时,你要给DWR一个回调函数,当数据从网络上回来时,DWR会调用这个函数。



这个图表现了DWR是如何在onclick事件中改变下拉列表的内容的。



DWR动态为服务端AjaxService类(Java)生成了一个相应的客户端AjaxService类(Javascript)。这个类被eventHandler调用。DWR就会去处理整个远程调用的细节,包括在Javascript和Java之间转换参数和返回值。然后在这里例子中,它会执行你提供的回调函数(populateList),这个函数再利用DWR提供的工具函数来更改页面内容。

2007年2月7日星期三

Struts Validator验证插件

Struts框架的一个主要好处是它提供了对接收到的表单数据进行验证的内置界面。如果有任何验证失败,则应用程序都会重新显示HTML表单,这样就可以改正无效的数据了。如果验证成功,则处理过程会继续进行。Struts框架的简单验证界面会减少与处理数据验证有关的令人头疼的事情,这样你就可以把精力集中到验证代码上,而不是放到捕获数据、重新显示不完整或无效数据的技巧上。但是,Struts内置的验证界面也有缺点。例如,在整个应用程序中验证代码常常会大量重复,因为许多域需要相同的验证逻辑。对一些相似字段的验证逻辑进行任何修改都要求在几个地方修改代码,还要重新编译受影响的代码。为了解决这个问题并增强Struts验证界面的功能,作为Struts的第三方附加件创建了Validator框架。后来,Validator被集成到核心Struts代码库中,并从Struts中分离出来,现在它是一个独立的Jakarta Commons项目。虽然Validator是一个独立的框架,但它仍能与其他程序封装在一起后提供,并与Struts无缝集成。


Validator


没有Validator,你就不得不编写验证表单数据所需的全部代码,并把它放入Form Bean对象的validate()方法中。对于想在其上进行数据验证的每个Form Bean域来说,都需要编写逻辑代码来实现验证。此外,你还必须编写代码来存储验证失败时的出错消息。有了Validator,你就不必在Form Bean中编写用于验证或存储错误消息的任何代码。相反,Form Bean提供了Validator的一个ActionForm子类,它提供验证或存储错误消息的功能。


可把Validator框架作为一个可用于Form Bean验证的可插入的验证例行程序系统来进行安装。每个验证例行程序都只是一个Java方法,负责执行特定类型的验证任务,验证可能通过,也可能失败。 默认情况下,Validator与几个有用的验证例行程序封装在一起来提供,这些例行程序能满足大多数情况下的验证要求。但是,如果Validator框架没有提供你需要的验证例行程序,那么你可以自己创建定制的验证例行程序,并将它插入到该框架中。此外,Validator还支持服务器端和客户端(JavaScript)的验证,而Form Bean只提供服务器端验证界面。


Validator使用两个XML配置文件来分别确定安装哪个验证例行程序和如何将它们应用于给定的应用程序。第一个配置文件validator-rules.xml说明应该被插入到框架中的验证例行程序,并提供每个验证的逻辑的名称。validator-rules.xml文件还定义了每个验证例行程序的客户端JavaScript代码。可以配置Validator让它把这个JavaScript代码发送到浏览器上,这样验证就可以在客户端和服务器端进行了。


第二个配置文件validation.xml确定哪个验证例行程序应用到哪个Form Bean。文件中的定义使用struts-config.xml文件给出的Form Bean的逻辑名称以及validator-rules.xml文件给出的验证例行程序的逻辑名称,以便把二者关联起来。使用Validator框架包括启用Validator插件、配置Validator的两个配置文件,以及创建提供Validator的ActionForm子类的Form Beans。



启用Validator插件


虽然Validator框架是与Struts封装在一起提供的,但在默认状况下Validator并不被启用。为了启用Validator,要向你的应用程序的struts-config.xml文件中添加下面的插件定义。



<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>

该定义告诉Struts为你的应用程序加载并初始化Validator插件。在初始化时,该插件装入由路径名属性指定的、用逗号分隔的Validator配置文件清单。每个配置文件的路径应该用与Web应用程序的相关的路径来指定,如前面的例子所示。



配置validator-rules.xml


Validator框架可以设置为可插入系统,其验证例行程序仅仅是插入到该系统中执行具体验证的Java方法。validator-rules.xml文件说明性地插入Validator用于执行验证的验证例行程序中。Struts示例应用程序带有这个文件的预配置拷贝。在大多数情况下,你不必修改这个预配置拷贝,除非你要向该框架中添加自己定制的验证。默认情况下,Validator中包括几个基本验证例行程序,你可以用它们来处理大多数验证问题。这些例行程序具有逻辑名称,如required(用于输入要求的值)、minlength(最小值)、maxlength(最大值)、CreditCard(用于输入信用卡号码值)、email(用于输入电子邮件地址值),等等。



关于Struts中的From Bean


org.apache.struts.action.ActionForm


ActionForm是Struts表单,它本身设计为JavaBean,您要继承ActionForm来设计一个表单,并提供标准的getter与setter方法,必要时可以使用继承下来的reset()、validate()等方法供 Struts 组件使用。客户请求交给了 RequestProcessor后,RequestProcessor会从 ActionMapping 中得知所使用的ActionForm,这是在struts-config.xml中设定的,如果所需的ActionForm还不存在就生成一个,之后一直使用它,ActionMapping与ActionForm会被当作参数传递给 Action。


org.apache.struts.action.DynaActionForm


DynaActionForm扩展了ActionForm,它的用法允许通过在struts-config.xml声明建立一个表单对象。RequestProcessor用和ActionForm相同的方式建立、填充并验证了DynaActionForm。但是DynaActionForm不能用于配置Validator验证。


org.apache.struts.validator.ValidatorForm


如果您配置好验证器插件,应该扩展ValidatorForm而不是ActionForm,以便它能加载你的Validator资源。他根据struts-config.xml文件中的action的name属性为当前form的调用相应的验证器,因此在validator.xml中的form元素的名称属性应该与action的name属性值相匹配,以便Validator与From Bean相关联进行验证(它与ActionForm功能上是差不多的,只是它是相关的validator包中,是专门用于Form Bean验证的)。


org.apache.struts.validator.DynaValidatorForm


DynaValidatorForm允许在struts-config.xml声明建立一个表单对象。RequestProcessor用和ValidatorForm相同的方式建立、填充。它又可以象ValidatorForm一样进行From Bean验证,与ValidatorForm一样它根据struts-config.xml文件中action的name属性为当前form的调用相应的验证器。是一个标准的动态验证表单。


org.apache.struts.validator.ValidatorActionForm


ValidatorActionForm是Validator的一种高级应用,它的编写方式与ValidatorForm完全相同,需要继承ValidatorActionForm并提供标准的getter与setter方法。不过在validator.xml的配置中,会把一组验证映射到一个操作路径,而不是映射到Form Bean名。这样就会由操作来自己选择需要验证的Form Bean。


org.apache.struts.validator.DynaValidatorActionForm


DynaValidatorActionForm是ValidatorActionForm的一个动态实现,可以在struts-config.xml声明建立一个表单对象。validator.xml的配置中,会把一组验证映射到一个操作路径,而不是映射到Form Bean名。这样就会由操作来自己选择需要验证的Form Bean。



配置validation.xml文件


validation.xml文件用于声明将应用到Form Beans的一组验证。要验证的每个Form Bean在这个文件中都有自己的定义。在这个定义中,指定要应用到该Form Bean的各域的验证。


validation.xml文件的第一个元素是form-validation。这个元素是该文件的主元素,而且只定义一次。在form-validation元素内定义form-set元素,它包括多个表单元素。一般来说,在文件中只定义一个form-set元素,但是如果要将验证国际化,那就要在每个地方单独使用一个form-set元素。


每个表单元素使用name属性将名称与其所包含的域验证集关联起来。Validator使用这个逻辑名称将这些验证映射到在struts-config.xml文件中定义的一个Form Bean。根据要验证的Form Bean的类型,Validator力求将该名称与Form Bean的逻辑名称或操作路径相匹配。在表单元素内,field元素定义要应用到Form Bean的特定域的验证。field元素的property属性对应于特定Form Bean中的域名。depends属性利用validator-rules.xml文件指定验证例行程序的逻辑名称,这些例行程序将应用到域验证中。(在From Bean的高级应用中,表单元素使用name属性的值为一个操作路径,每个操作路径下有自己的一套字段验证)。



启用客户端验证


Validator除了提供了简化服务器端表单数据验证过程的框架外,它还提供了执行客户端验证时易于使用的方法。在validator-rules.xml文件中定义的每一个验证例行程序都可以随意指定JavaScript代码,这些代码可以在浏览器(客户端上的)中运行,从而执行与服务器端进行的验证相同的验证过程。在客户端进行验证时,除非所有表单都通过验证,否则这些表单不允许被提交。


为了启用客户端验证,必须在每个需要验证的JSP中放上Struts HTML Tag Library(标记库)的javascript标记,如:<html:javascript formName="logonForm"/>



2007年2月6日星期二

Prototype 1.3 使用指南


Prototype 使用指南


1. Programming Guide
1.1. Prototype是什么?
1.2. 关联文章
1.3. 通用性方法
1.3.1. 使用$()方法
1.3.2. 使用$F()方法
1.3.3. 使用$A()方法
1.3.4. 使用$H()方法
1.3.5. 使用$R()方法
1.3.6. 使用Try.these()方法
1.4. Ajax 对象
1.4.1. 使用 Ajax.Request类
1.4.2. 使用 Ajax.Updater 类
2. prototype.js参考
2.1. JavaScript 类的扩展
2.2. 对 Object 类的扩展
2.3. 对 Number 类的扩展
2.4. 对 Function 类的扩展
2.5. 对 String 类的扩展
2.6. 对 document DOM 对象的扩展
2.7. 对 Event 对象的扩展
2.8. 在 prototype.js 中定义的新对象和类
2.9. PeriodicalExecuter 对象
2.10. Prototype 对象
2.11. Class 对象
2.12. Ajax 对象
2.13. Ajax.Base 类
2.14. Ajax.Request 类
2.15. options 参数对象
2.16. Ajax.Updater 类
2.17. Ajax.PeriodicalUpdater 类
2.18. Element 对象
2.19. Abstract 对象
2.20. Abstract.Insertion 类
2.21. Insertion 对象
2.22. Insertion.Before 类
2.23. Insertion.Top 类
2.24. Insertion.Bottom 类
2.25. Insertion.After 类
2.26. Field 对象
2.27. Form 对象
2.28. Form.Element 对象
2.29. Form.Element.Serializers 对象
2.30. Abstract.TimedObserver 类
2.31. Form.Element.Observer 类
2.32. Form.Observer 类
2.33. Abstract.EventObserver 类
2.34. Form.Element.EventObserver 类
2.35. Form.EventObserver 类
2.36. Position 对象 (预备文档)

覆盖版本 1.3.1

Chapter 1. Programming Guide




1.1. Prototype是什么?



  或许你还没有用过它,Prototype是一个由Sam Stephenson写的JavaScript包。这个构思奇妙编写良好的一段兼容标准的一段代码将承担创造RIA(Rich Internet Application 富有客户端), 高交互性WEB应用程序的重担。轻松加入Web 2.0特性。


1.2. 关联文章


  高级JavaScript指南


1.3. 通用性方法


  这个程序包里面包含了许多预定义的对象和通用性方法。编写这些方法的明显的目的就是为了减少你大量的重复编码和惯用法。


1.3.1. 使用 $()方法



  $() 方法是在DOM中使用过于频繁的 document.getElementById() 方法的一个便利的简写,就像这个DOM方法一样,这个方法返回参数传入的id的那个元素。
  比起DOM中的方法,这个更胜一筹。你可以传入多个id作为参数然后 $() 返回一个带有所有要求的元素的一个 Array 对象。下面的例子会向你描述这些。

<HTML>
<HEAD>
<TITLE> Test Page </TITLE>
<script src="prototype-1.3.1.js"></script>

<script>
function test1()
{
var d = $('myDiv');
alert(d.innerHTML);
}

function test2()
{
var divs = $('myDiv','myOtherDiv');
for(i=0; i<divs.length; i++)
{
alert(divs[i].innerHTML);
}
}
</script>
</HEAD>

<BODY>
<div id="myDiv">
<p>This is a paragraph</p>
</div>
<div id="myOtherDiv">
<p>This is another paragraph</p>
</div>

<input type="button" value=Test1 onclick="test1();"><br>
<input type="button" value=Test2 onclick="test2();"><br>

</BODY>
</HTML>

  这个方法的另一个好处就是你可以传入id字符串或者元素对象自己,这使得在创建可以传入任何形式参数的方法的时候,它变得非常有用。


1.3.2. 使用$F()方法



  $F()方法是另一个非常受欢迎的简写。它可以返回任何输入表单控件的值,如文本框或下拉框。这个方法可以传入元素的id或者元素自己。

<script>
function test3()
{
alert( $F('userName') );
}
</script>

<input type="text" id="userName" value="Joe Doe"><br>
<input type="button" value=Test3 onclick="test3();"><br>



1.3.3. 使用$A()方法



  $A()方法把接收到的参数转换成一个Array对象。
  这个方法加上对Array 类的扩展,可以很容易的转换或者复制任意的列举列表到Array对象,一个被推荐使用的用法就是转换DOMNodeLists到一个普通的数组里,可以被更广泛高效的使用, 看下面的例子。

<script>

function showOptions(){
var someNodeList = $('lstEmployees').getElementsByTagName('option');
var nodes = $A(someNodeList);

nodes.each(function(node){
alert(node.nodeName + ': ' + node.innerHTML);
});
}
</script>

<select id="lstEmployees" size="10" >
<option value="5">Buchanan, Steven</option>
<option value="8">Callahan, Laura</option>
<option value="1">Davolio, Nancy</option>
</select>

<input type="button" value="Show the options" onclick="showOptions();" >



1.3.4. 使用$H()方法



  $H()方法把对象转化成可枚举的貌似联合数组Hash对象。

<script>
function testHash()
{
//let's create the object
var a = {
first: 10,
second: 20,
third: 30
};

//now transform it into a hash
var h = $H(a);
alert(h.toQueryString()); //displays: first=10&second=20&third=30
}

</script>



1.3.5. 使用$R()方法



  $R()方法是new ObjectRange(lowerBound, upperBound, excludeBounds)的一个简单快捷的使用方式。
  ObjectRange类文档里面有完整的解释。 同时,我们来看看一个简单的例子,来演示通过each方法遍历的用法。 那个方法的更多解释在Enumerable对象文档里面。

<script>
function demoDollar_R(){
var range = $R(10, 20, false);
range.each(function(value, index){
alert(value);
});
}

</script>

<input type="button" value="Sample Count" onclick="demoDollar_R();" >



1.3.6. 使用Try.these()方法



  Try.these() 方法使得实现当你想调用不同的方法直到其中的一个成功正常的这种需求变得非常容易,他把一系列的方法作为参数并且按顺序的一个一个的执行这些方法直到其中的一个成功执行,返回成功执行的那个方法的返回值。
  在下面的例子中,xmlNode.text在一些浏览器中好用,但是xmlNode.textContent在另一些浏览器中正常工作。 使用Try.these()方法我们可以得到正常工作的那个方法的返回值。

<script>
function getXmlNodeValue(xmlNode){
return Try.these(
function() {return xmlNode.text;},
function() {return xmlNode.textContent;)
);
}
</script>



1.4. Ajax 对象



  上面提到的共通方法非常好,但是面对它吧,它们不是最高级的那类东西。它们是吗?你很可能自己编写了这些甚至在你的脚本里面有类似功能的方法。但是这些方法只是冰山一角。
  我很肯定你对prototype.js感兴趣的原因很可能是由于它的AJAX能力。所以让我们解释当你需要完成AJAX逻辑的时候,这个包如何让它更容易。
  Ajax 对象是一个预定义对象,由这个包创建,为了封装和简化编写AJAX 功能涉及的狡猾的代码。 这个对象包含一系列的封装AJAX逻辑的类。我们来看看它们的一些。


1.4.1. 使用 Ajax.Request



  如果你不使用任何的帮助程序包,你很可能编写了整个大量的代码来创建XMLHttpRequest对象并且异步的跟踪它的进程, 然后解析出响应然后处理它。当你不需要支持多于一种类型的浏览器时你会感到非常的幸运。
  为了支持 AJAX 功能。这个包定义了 Ajax.Request 类。
  假如你有一个应用程序可以通过url http://yoursever/app/get_sales?empID=1234&year=1998与服务器通信。它返回下面这样的XML响应。

<?xml version="1.0" encoding="utf-8" ?>
<ajax-response>
<response type="object" id="productDetails">
<monthly-sales>
<employee-sales>
<employee-id>1234</employee-id>
<year-month>1998-01</year-month>
<sales>$8,115.36</sales>
</employee-sales>
<employee-sales>
<employee-id>1234</employee-id>
<year-month>1998-02</year-month>
<sales>$11,147.51</sales>
</employee-sales>
</monthly-sales>
</response>
</ajax-response>

  用 Ajax.Request对象和服务器通信并且得到这段XML是非常简单的。下面的例子演示了它是如何完成的。

<script>
function searchSales()
{
var empID = $F('lstEmployees');
var y = $F('lstYears');
var url = 'http://yoursever/app/get_sales';
var pars = 'empID=' + empID + '&year=' + y;
var myAjax = new Ajax.Request(
url,
{method: 'get', parameters: pars, onComplete: showResponse}
);


}

function showResponse(originalRequest)
{
//put returned XML in the textarea
$('result').value = originalRequest.responseText;
}
</script>

<select id="lstEmployees" size="10" onchange="searchSales()">
<option value="5">Buchanan, Steven</option>
<option value="8">Callahan, Laura</option>
<option value="1">Davolio, Nancy</option>
</select>
<select id="lstYears" size="3" onchange="searchSales()">
<option selected="selected" value="1996">1996</option>
<option value="1997">1997</option>
<option value="1998">1998</option>
</select>
<br><textarea id=result cols=60 rows=10 ></textarea>

  你看到传入 Ajax.Request构造方法的第二个对象了吗? 参数{method: 'get', parameters: pars, onComplete: showResponse} 表示一个匿名对象的真实写法。他表示你传入的这个对象有一个名为 method 值为 'get'的属性,另一个属性名为 parameters 包含HTTP请求的查询字符串,和一个onComplete 属性/方法包含函数showResponse
  还有一些其它的属性可以在这个对象里面定义和设置,如 asynchronous,可以为truefalse 来决定AJAX对服务器的调用是否是异步的(默认值是 true)。
  这个参数定义AJAX调用的选项。在我们的例子中,在第一个参数通过HTTP GET命令请求那个url,传入了变量 pars包含的查询字符串, Ajax.Request 对象在它完成接收响应的时候将调用showResponse 方法。
  也许你知道, XMLHttpRequest在HTTP请求期间将报告进度情况。这个进度被描述为四个不同阶段:Loading, Loaded, Interactive, 或 Complete。你可以使 Ajax.Request 对象在任何阶段调用自定义方法 ,Complete 是最常用的一个。想调用自定义的方法只需要简单的在请求的选项参数中的名为 onXXXXX 属性/方法中提供自定义的方法对象。 就像我们例子中的 onComplete 。你传入的方法将会被用一个参数调用,这个参数是 XMLHttpRequest 对象自己。你将会用这个对象去得到返回的数据并且或许检查包含有在这次调用中的HTTP结果代码的 status 属性。
  还有另外两个有用的选项用来处理结果。我们可以在onSuccess 选项处传入一个方法,当AJAX无误的执行完后调用, 相反的,也可以在onFailure选项处传入一个方法,当服务器端出现错误时调用。正如onXXXXX 选项传入的方法一样,这两个在被调用的时候也传入一个带有AJAX请求的XMLHttpRequest对象。
  我们的例子没有用任何有趣的方式处理这个 XML响应,我们只是把这段XML放进了一个文本域里面。对这个响应的一个典型的应用很可能就是找到其中的想要的信息,然后更新页面中的某些元素, 或者甚至可能做某些XSLT转换而在页面中产生一些HTML。
  更完全的解释,请参照 Ajax.Request 参考Ajax选项参考


1.4.2. 使用 Ajax.Updater



  如果你的服务器的另一端返回的信息已经是HTML了,那么使用这个程序包中 Ajax.Updater 类将使你的生活变得更加得容易。用它你只需提供哪一个元素需要被AJAX请求返回的HTML填充就可以了,例子比我写说明的更清楚。
<script>
function getHTML()
{
var url = 'http://yourserver/app/getSomeHTML';
var pars = 'someParameter=ABC';

var myAjax = new Ajax.Updater('placeholder', url, {method: 'get', parameters: pars});

}
</script>

<input type=button value=GetHtml onclick="getHTML()">
<div id="placeholder"></div>

  你可以看到,这段代码比前面的例子更加简洁,不包括 onComplete 方法,但是在构造方法中传入了一个元素id。 我们来稍稍修改一下代码来描述如何在客户端处理服务器段错误成为可能。
  我们将加入更多的选项, 指定处理错误的一个方法。这个是用 onFailure 选项来完成的。
  我们也指定了一个 placeholder 只有在成功请求之后才会被填充。为了完成这个目的我们修改了第一个参数从一个简单的元素id到一个带有两个属性的对象, success (一切OK的时候被用到) 和 failure (有地方出问题的时候被用到) 在下面的例子中没有用到failure属性,而仅仅在 onFailure 处使用了 reportError 方法。
<script>
function getHTML()
{
var url = 'http://yourserver/app/getSomeHTML';
var pars = 'someParameter=ABC';
var myAjax = new Ajax.Updater(
{success: 'placeholder'},
url,
{method: 'get', parameters: pars, onFailure: reportError});


}

function reportError(request)
{
alert('Sorry. There was an error.');
}
</script>

<input type=button value=GetHtml onclick="getHTML()">
<div id="placeholder"></div>

  如果你的服务器逻辑是返回JavaScript 代码而不是单纯的 HTML 标记, Ajax.Updater对象可以执行那段JavaScript代码。为了使这个对象对待响应为JavaScript,你只需在最后参数的对象构造方法中简单加入evalScripts: true属性。
  更完全的解释,请参照 Ajax.Updater 参考Ajax选项参考


Chapter 2. prototype.js参考



2.1. JavaScript 类的扩展



  prototype.js 包中加入功能的一种途径就是扩展已有的JavaScript 类。


2.2. 对 Object 类的扩展




  Table 2.1. Object 类的扩展
方法类别参数描述
extend(destination, source)staticdestination: 任何对象, source: 任何对象用从 sourcedestination复制所有属性和方法的方式 来提供一种继承机制。
extend(object)instance任何对象用从传入的 object 中复制所有属性和方法的方式来提供一种继承机制。




2.3. 对 Number 类的扩展




  Table 2.2. Number 类的扩展
方法类别参数描述
toColorPart()instance(none)返回数字的十六进制描述,当在HTML中转换为RGB颜色组件到HTML中使用的颜色。




2.4. 对 Function 类的扩展




  Table 2.3. 对 Function 类的扩展
方法类别参数描述
bind(object)instanceobject: 拥有这个方法的对象返回预先绑定在拥有该函数(=方法)的对象上的函数实例, 返回的方法将和原来的方法具有相同的参数。
bindAsEventListener(object)instanceobject: 拥有这个方法的对象返回预先绑定在拥有该函数(=方法)的对象上的函数实例,返回的方法将把当前的事件对象作为它的参数。


  让我们看看这些扩展的具体例子。

<input type=checkbox id=myChk value=1> Test?
<script>
//declaring the class
var CheckboxWatcher = Class.create();

//defining the rest of the class implmentation
CheckboxWatcher.prototype = {

initialize: function(chkBox, message) {
this.chkBox = $(chkBox);
this.message = message;
//assigning our method to the event
this.chkBox.onclick = this.showMessage.bindAsEventListener(this);
},

showMessage: function(evt) {
alert(this.message + ' (' + evt.type + ')');
}
};


var watcher = new CheckboxWatcher('myChk', 'Changed');
</script>



2.5. 对 String 类的扩展




  Table 2.4. String 类的扩展
方法类别参数描述
stripTags()instance(none)返回一个把所有的HTML或XML标记都移除的字符串。
escapeHTML()instance(none)返回一个把所有的HTML标记回避掉的字符串。
unescapeHTML()instance(none)escapeHTML()相反。




2.6. 对 document DOM 对象的扩展




  Table 2.5. document DOM 对象的扩展
方法类别参数描述
getElementsByClassName(className)instanceclassName: 关联在元素上的CSS类名返回给定的具有相同的CSS类名的所有元素。




2.7. 对 Event 对象的扩展




  Table 2.6. Event 对象的扩展
属性类型描述
KEY_BACKSPACENumber8: 常量,退格(Backspace)键的代码。
KEY_TABNumber9: 常量,Tab键的代码。
KEY_RETURNNumber13: 常量,回车键的代码。
KEY_ESCNumber27: 常量, Esc键的代码。
KEY_LEFTNumber37: 常量,左箭头键的代码。
KEY_UPNumber38: 常量,上箭头键的代码。
KEY_RIGHTNumber39: 常量,右箭头键的代码。
KEY_DOWNNumber40: 常量,下箭头键的代码。
KEY_DELETENumber46: 常量,删除(Delete)键的代码。
observers:Array缓存的观察者的列表,这个对象内部具体实现的一部分。



  Table 2.7. Event 对象的扩展
方法类别参数描述
element(event)staticevent: 事件对象返回引发这个事件的元素。
isLeftClick(event)staticevent: 事件对象如果鼠标左键单击返回true。
pointerX(event)staticevent: 事件对象返回在页面上x坐标。
pointerY(event)staticevent: 事件对象返回在页面上y坐标。
stop(event)staticevent: 事件对象用这个方法来中止事件的默认行为来使事件的传播停止。
findElement(event, tagName)staticevent: 事件对象, tagName: 指定标记的名字向 DOM 树的上位查找,找到第一个给定标记名称的元素, 从这个元素开始触发事件。
observe(element, name, observer, useCapture)staticelement: 对象或者对象id, name: 事件名 (如 'click', 'load', etc), observer: 处理这个事件的方法, useCapture: 如果true, 在捕捉到事件的阶段处理事件 那么如果 false在bubbling 阶段处理。加入一个处理事件的方法。
stopObserving(element, name, observer, useCapture)staticelement: 对象或者对象id, name: 事件名 (如 'click', 'load', etc), observer: 处理这个事件的方法, useCapture: 如果true, 在捕捉到事件的阶段处理事件 那么如果 false在bubbling 阶段处理。删除一个处理实践的方法。
_observeAndCache( element, name, observer, useCapture)static 私有方法,不用管它。
unloadCache()static(none)私有方法,不用管它。清除内存中的多有观察着缓存。


  让我们看看怎样用这个对象加入处理 window 对象的load事件的处理方法。

<script>
Event.observe(window, 'load', showMessage, false);

function showMessage() {
alert('Page loaded.');
}
</script>



2.8. 在 prototype.js中定义的新对象和类



  另一个这个程序包帮助你的地方就是提供许多既支持面向对象设计理念又有共通功能的许多对象。


2.9. PeriodicalExecuter 对象



  这个对象提供一定间隔时间上重复调用一个方法的逻辑。

  Table 2.8. PeriodicalExecuter 对象
方法类别参数描述
[ctor](callback, interval)constructorcallback: 没有参数的方法, interval: 秒数创建这个对象的实例将会重复调用给定的方法。



  Table 2.9. PeriodicalExecuter 对象
属性类型描述
callbackFunction()被调用的方法,该方法不会被传入参数。
frequencyNumber以秒为单位的间隔。
currentlyExecutingBoolean表示这个方法是否正在执行。




2.10. Prototype 对象



  Prototype 没有太重要的作用,只是声明了该程序包的版本 。

  Table 2.10. The Prototype object
属性类型描述
VersionString包的版本。
emptyFunctionFunction()空方法对象。




2.11. Class 对象



  在这个程序包中 Class 对象在声明其他的类时候被用到 。用这个对象声明类使得新类支持 initialize() 方法,他起构造方法的作用。
  看下面的例子

//declaring the class
var MySampleClass = Class.create();
//defining the rest of the class implmentation
MySampleClass.prototype = {

initialize: function(message) {
this.message = message;
},

showMessage: function(ajaxResponse) {
alert(this.message);
}
};

//now, let's instantiate and use one object
var myTalker = new MySampleClass('hi there.');
myTalker.showMessage(); //displays alert


  Table 2.11. Class 对象
方法类别参数描述
create(*)instance(any)定义新类的构造方法。




2.12. Ajax 对象



  这个对象被用作其他提供AJAX功能的类的根对象。

  Table 2.12. Ajax 对象
方法类别参数描述
getTransport()instance(none)返回新的XMLHttpRequest 对象。



2.13. Ajax.Base



  这个类是其他在Ajax对象中定义的类的基类。

  Table 2.13. Ajax.Base 类
方法类别参数描述
setOptions(options)instanceoptions: AJAX 选项设定AJAX操作想要的选项。
responseIsSuccess()instance(none)返回 true 如果AJAX操作成功,否则为 false
responseIsFailure()instance(none)responseIsSuccess() 相反。




2.14. Ajax.Request



  继承自 Ajax.Base
  封装 AJAX 操作

  Table 2.14. Ajax.Request
属性类型类别描述
EventsArraystatic在AJAX操作中所有可能报告的事件/状态的列表。这个列表包括: 'Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'
transportXMLHttpRequestinstance携带AJAX操作的 XMLHttpRequest 对象。



  Table 2.15. Ajax.Request
方法类别参数描述
[ctor](url, options)constructorurl: 请求的url, options: AJAX 选项创建这个对象的一个实例,它将在给定的选项下请求url。 重要:如果选择的url受到浏览器的安全设置,他会一点作用也不起。 很多情况下,浏览器不会请求与当前页面不同主机(域名)的url。你最好只使用本地url来避免限制用户配置他们的浏览器(谢谢Clay)
request(url)instanceurl: AJAX 请求的url这个方法通常不会被外部调用。已经在构造方法中调用了。
setRequestHeaders()instance(none)这个方法通常不会被外部调用。 被这个对象自己调用来配置在HTTP请求要发送的HTTP报头。
onStateChange()instance(none)这个方法通常不会被外部调用。 当AJAX请求状态改变的时候被这个对象自己调用。
respondToReadyState(readyState)instancereadyState: 状态数字 (1 到 4)这个方法通常不会被外部调用。 当AJAX请求状态改变的时候被这个对象自己调用。




2.15. options 参数对象



  AJAX操作中一个重要的部分就是 options 参数。本质上没有options类。任何对象都可以被传入,只要带有需要的属性。通常会只为了AJAX调用创建匿名类。

  Table 2.16. options 参数对象
属性类型Default描述
methodArray'post'HTTP 请求方式。
parametersString''在HTTP请求中传入的url格式的值列表。
asynchronousBooleantrue指定是否做异步 AJAX 请求。
postBodyStringundefined在HTTP POST的情况下,传入请求体中的内容。
requestHeadersArrayundefined和请求一起被传入的HTTP头部列表, 这个列表必须含有偶数个项目, 任何奇数项目是自定义的头部的名称,接下来的偶数项目使这个头部项目的字符串值。 例子:['my-header1', 'this is the value', 'my-other-header', 'another value']
onXXXXXXXXFunction(XMLHttpRequest)undefined在AJAX请求中,当相应的事件/状态形成的时候调用的自定义方法。 例如 var myOpts = {onComplete: showResponse, onLoaded: registerLoaded};. 这个方法将被传入一个参数, 这个参数是携带AJAX操作的 XMLHttpRequest对象。
onSuccessFunction(XMLHttpRequest)undefined当AJAX请求成功完成的时候调用的自定义方法。 这个方法将被传入一个参数, 这个参数是携带AJAX操作的 XMLHttpRequest对象。
onFailureFunction(XMLHttpRequest)undefined当AJAX请求完成但出现错误的时候调用的自定义方法。 这个方法将被传入一个参数, 这个参数是携带AJAX操作的 XMLHttpRequest对象。
insertionFunction(Object, String)null为了把返回的文本注入到一个元素中而执行的方法。 这个方法将被传入两个参数,要被更新的对象并且只应用于 Ajax.Updater 的响应文本 。
evalScriptsBooleanundefined, false决定当响应到达的时候是否执行其中的脚本块,只在 Ajax.Updater 对象中应用。
decayNumberundefined, 1决定当最后一次响应和前一次响应相同时在 Ajax.PeriodicalUpdater 对象中的减漫访问的次数,例如,如果设为2,后来的刷新和之前的结果一样, 这个对象将等待2个设定的时间间隔进行下一次刷新, 如果又一次一样,那么将等待4次,等等。 不设定这个只,或者设置为1,将避免访问频率变慢。




2.16. Ajax.Updater



  继承自 Ajax.Request
  当请求的url返回一段HTML而你想把它直接放置到页面中一个特定的元素的时候被用到。 如果url的返回<script> 的块并且想在接收到时就执行它的时候也可以使用该对象。含有脚本的时候使用 evalScripts 选项。

  Table 2.17. Ajax.Updater
属性类型类别描述
ScriptFragmentStringstatic可以判断是否为脚本的正则表达式。
containersObjectinstance这个对象包含两个属性:AJAX请求成功执行的时候用到 containers.success , 否则的话用到 containers.failure



  Table 2.18. Ajax.Updater
方法类别参数描述
[ctor](container, url, options)constructorcontainer:可以是元素的id, 也可以是元素自己, 或者可以是带有2个属性的对象 - object.success AJAX请求成功的时候用到的元素(或者id) 否则用到object.failure 中设定的元素(或id) ,url: 请求的url, options: AJAX 选项创建一个用给定的选项请求给定的url的一个实例。
updateContent()instance(none)这个方法通常不会被外部调用。 当响应到达的时候,被这个对象自己调用。 它会用HTML更新适当的元素或者调用在 insertion 选项中传入的方法-这个方法将被传入两个参数,被更新的元素和响应文本。




2.17. Ajax.PeriodicalUpdater



  继承自 Ajax.Base
  这个类重复生成并使用 Ajax.Updater 对象来刷新页面中的一个元素。或者执行 Ajax.Updater 可以执行的其它任务。更多信息参照 Ajax.Updater 参考

  Table 2.19. Ajax.PeriodicalUpdater
属性类型类别描述
containerObjectinstance这个值将直接传入Ajax.Updater的构造方法。
urlStringinstance这个值将直接传入Ajax.Updater的构造方法。
frequencyNumberinstance两次刷新之间的间隔 (不是频率) ,以秒为单位。 默认2秒。 This 当调用 Ajax.Updater 对象的时候,这个数将和当前的 decay 相乘。
decayNumberinstance重负执行任务的时候保持的衰败水平。
updaterAjax.Updater instance最后一次使用的 Ajax.Updater 对象
timerObjectinstance通知对象该下一次更新时用到的JavaScript 计时器。



  Table 2.20. Ajax.PeriodicalUpdater
方法类别参数描述
[ctor](container, url, options)constructorcontainer:可以是元素的id, 也可以是元素自己, 或者可以是带有2个属性的对象 - object.success AJAX请求成功的时候用到的元素(或者id) 否则用到object.failure 中设定的元素(或id) ,url: 请求的url, options: AJAX 选项创建一个用给定的选项请求给定的url的一个实例。
start()instance(none)这个方法通常不会被外部调用。 对象为了开始周期性执行任务的时候调用的方法。
stop()instance(none)这个方法通常不会被外部调用。 对象为了停止周期性执行任务的时候调用的方法。
updateComplete()instance(none)这个方法通常不会被外部调用。 被当前的 Ajax.Updater 使用,当一次请求结束的时候,它被用作计划下一次请求。
onTimerEvent()instance(none)这个方法通常不会被外部调用。当到下一次更新时被内部调用。




2.18. Element 对象



  这个对象提供在操作DOM中元素时使用的功能性方法。

  Table 2.21. Element 对象
方法类别参数描述
toggle(elem1 [, elem2 [, elem3 [...]]])constructorelemN: 元素对象或id切换每一个传入元素的可视性。
hide(elem1 [, elem2 [, elem3 [...]]])instanceelemN: 元素对象或id用设定它的 style.display'none'来隐藏每个传入的元素。
show(elem1 [, elem2 [, Slem3 [...]]])instanceelemN: 元素对象或id用设定它的 style.display ''来显示每个传入的元素。
remove(element)instanceelement: 元素对象或id从document对象中删除指定的元素。
getHeight(element)instanceelement: 元素对象或id返回元素的 offsetHeight
addClassName( element, className)instanceelement: 元素对象或id, className: CSS类名向元素的类名中加入给定的类名。
hasClassName( element, className)instanceelement: 元素对象或id, className: CSS类名返回 true 如果元素的类名中含有给定的类名
removeClassName( element, className)instanceelement: 元素对象或id, className: CSS类名从元素的类名中删除给定的类名。
cleanWhitespace( element )instanceelement: 元素对象或id删除该元素的所有只含有空格的子节点。




2.19. Abstract 对象



  这个对象是这个程序包中其他类的根。它没有任何属性和方法。在这个对象中定义的类可以被视为传统的抽象类。


2.20. Abstract.Insertion



  这个类被用作其他提供动态内容插入功能的类的基类,它像一个抽象类一样被使用。

  Table 2.22. Abstract.Insertion
方法类别参数描述
[ctor](element, content)constructorelement: 元素对象或id, content: 被插入的HTML创建一个可以帮助插入动态内容的对象。



  Table 2.23. Abstract.Insertion
属性类型类别描述
adjacencyStringstatic, parameter这个参数指定相对于给定元素,内容将被放置的位置。 可能的值是: 'beforeBegin', 'afterBegin', 'beforeEnd', 和 'afterEnd'.
elementObjectinstance与插入物做参照元素对象。
contentStringinstance被插入的 HTML 。




2.21. Insertion 对象



  这个对象是其他类似功能的根。它没有任何属性和方法。在这个对象中定义的类仍然可以被视为传统的抽象类。


2.22. Insertion.Before



  继承自 Abstract.Insertion
  在给定元素开始标记的前面插入HTML。

  Table 2.24. Insertion.Before
方法类别参数描述
[ctor](element, content)constructorelement: 元素对象或id, content: 被插入的HTML继承自 Abstract.Insertion. 创建一个可以帮助插入动态内容的对象。


  下面的代码

<br>Hello, <span id="person" style="color:red;">Wiggum. How's it going?</span>

<script> new Insertion.Before('person', 'Chief '); </script>

  将把 HTML 变为

<br>Hello, Chief <span id="person" style="color:red;">Wiggum. How's it going?</span>



2.23. Insertion.Top



  继承自 Abstract.Insertion
  在给定元素第一个子节点位置插入 HTML。内容将位于元素的开始标记的紧后面。

  Table 2.25. Insertion.Top
方法类别参数描述
[ctor](element, content)constructorelement: 元素对象或id, content: 被插入的HTML继承自 Abstract.Insertion. 创建一个可以帮助插入动态内容的对象。


  下面的代码

<br>Hello, <span id="person" style="color:red;">Wiggum. How's it going?</span>

<script> new Insertion.Top('person', 'Mr. '); </script>

  将把 HTML 变为
<br>Hello, <span id="person" style="color:red;">Mr. Wiggum. How's it going?</span>



2.24. Insertion.Bottom



  继承自 Abstract.Insertion
  在给定元素最后一个子节点位置插入 HTML。内容将位于元素的结束标记的紧前面。

  Table 2.26. Insertion.Bottom
方法类别参数描述
[ctor](element, content)constructorelement: 元素对象或id, content: 被插入的HTML继承自 Abstract.Insertion. 创建一个可以帮助插入动态内容的对象。


  下面的代码

<br>Hello, <span id="person" style="color:red;">Wiggum. How's it going?</span>

<script> new Insertion.Bottom('person', " What's up?"); </script>

  将把 HTML 变为

<br>Hello, <span id="person" style="color:red;">Wiggum. How's it going? What's up?</span>



2.25. Insertion.After



  继承自 Abstract.Insertion
  在给定元素结束标记的后面插入HTML。

  Table 2.27. Insertion.After
方法类别参数描述
[ctor](element, content)constructorelement: 元素对象或id, content: 被插入的HTML继承自 Abstract.Insertion. 创建一个可以帮助插入动态内容的对象。


  下面的代码

<br>Hello, <span id="person" style="color:red;">Wiggum. How's it going?</span>

<script> new Insertion.After('person', ' Are you there?'); </script>

  将把 HTML 变为
<br>Hello, <span id="person" style="color:red;">Wiggum. How's it going?</span> Are you there?



2.26. Field 对象



  这个对象提供操作表单中的输入项目的功能性方法。

  Table 2.28. Field 对象
方法类别参数描述
clear(field1 [, field2 [, field3 [...]]])instancefieldN: 元素对象或id清除传入表单中项目元素的值。
present(field1 [, field2 [, field3 [...]]])instancefieldN: 元素对象或id只有在所有的表单项目都不为空时返回 true
focus(field)instancefieldN: 元素对象或id移动焦点到给定的表单项目。
select(field)instancefieldN: 元素对象或id选择支持项目值选择的表单项目的值。
activate(field)instancefieldN: 元素对象或id移动焦点并且选择支持项目值选择的表单项目的值。




2.27. Form 对象



  这个对象提供操作表单和他们的输入项目的功能性方法。

  Table 2.29. Form 对象
方法类别参数描述
serialize(form)instanceform: 表单元素或id返回url参数格式的项目名和值的列表, 如'field1=value1&field2=value2&field3=value3'。
getElements(form)instanceform: 表单元素或id返回包含所有在表单中输入项目的 Array 对象。
getInputs(form [, typeName [, name]])instanceform: 表单元素或id, typeName: 输入项目的类型, name: 输入项目的名称返回一个 Array 包含所有在表单中的 <input> 元素。 另外,这个列表可以对元素的类型或名字属性进行过滤。
disable(form)instanceform: 表单元素或id使表单中的所有输入项目无效。
enable(form)instanceform: 表单元素或id使表单中的所有输入项目有效。
focusFirstElement(form)instanceform: 表单元素或id激活第一个表单中可视的,有效的输入项目。
reset(form)instanceform: 表单元素或id重置表单。和调用表单对象的 reset() 方法一样。




2.28. Form.Element 对象



  这个对象提供表单对象中的可视和非可视元素的功能性方法。

  Table 2.30. Form.Element 对象
方法类别参数描述
serialize(element)instanceelement: 表单元素或id返回元素的 名称=值 对, 如 'elementName=elementValue'。
getValue(element)instanceelement: 表单元素或id返回元素的值。




2.29. Form.Element.Serializers 对象



  这个对象提供了内部使用的用来协助解析出表单元素的当前值功能性方法。

  Table 2.31. Form.Element.Serializers 对象
方法类别参数描述
inputSelector(element)instanceelement: 一个带有checked属性的表单元素或id, 如 radio 或 checkbox。返回带有元素名称和值的 Array , 如 ['elementName', 'elementValue']
textarea(element)instanceelement: 一个带有value属性的表单元素或id, 如 textbox, button 或 password 项目。返回带有元素名称和值的 Array , 如 ['elementName', 'elementValue']
select(element)instanceelement: 一个<select> 元素对象或id。返回带有元素名称和所有被选择的选项的值或文本的 Array , 如 ['elementName', 'selOpt1 selOpt4 selOpt9']




2.30. Abstract.TimedObserver



  这个类是用于其它监听一个元素的值(或者任何类中涉及的属性)变化的类的基类,这个类像一个抽象类一样被使用。
  子类可以被创建来监听如输入项目值,或style属性,或表格的行数,或者其他任何对跟踪变化相关的东西。
  子类必须实现这个方法来决定什么才是被监听的元素的当前值。

  Table 2.32. Abstract.TimedObserver
方法类别参数描述
[ctor](element, frequency, callback)constructorelement: 元素对象或id, frequency: 以秒为单位的间隔, callback: 当元素改变的时候调用的方法。创建一个监听元素的对象。
registerCallback()instance(none)这个方法通常不会被外部调用。 被这个对象自己调用来开始监听那个元素。
onTimerEvent()instance(none)这个方法通常不会被外部调用。 被这个对象自己调用来周期性的检查那个元素。



  Table 2.33. Abstract.TimedObserver
属性类型描述
elementObject被监听的元素对象。
frequencyNumber每次检查中的以秒为单位的时间间隔。
callbackFunction(Object, String)只要元素改变这个方法就会被调用。 会接收到元素对象和新值作为参数。
lastValueString元素被核实的最后一个值。




2.31. Form.Element.Observer



  继承自 Abstract.TimedObserver
  Abstract.TimedObserver 的一个实现类用来监听表单输入项目的值的变化。当你想监听一个没有带报告值变化事件的元素的时候使用这个类。否则的话使用 Form.Element.EventObserver 类代替。

  Table 2.34. class="literal">Form.Element.Observer
方法类别参数描述
[ctor](element, frequency, callback)constructorelement: 元素对象或id, frequency: 以秒为单位的间隔, callback: 当元素改变的时候调用的方法。继承自 Abstract.TimedObserver. 创建一个监听元素值属性的对象。
getValue()instance(none)返回元素的值。




2.32. Form.Observer 类>


  继承自 Abstract.TimedObserver
  Abstract.TimedObserver 的一个实现类用来监听表单中任何数据项的值的变化。当你想监听一个没有带报告值变化事件的元素的时候使用这个类。 否则的话使用类 Form.EventObserver 代替。

  Table 2.35. Form.Observer
方法类别参数描述
[ctor](form, frequency, callback)constructorform: 表单对象或id,继承自 Abstract.TimedObserver. 创建一个监听表单变化的对象。
getValue()instance(none)返回所有表单数据的一系列值。




2.33. Abstract.EventObserver



  这个类被用作其他一些类的基类,这些类具有在一个元素的值改变事件发生的时候执行一个回调方法这样的功能。
  类 Abstract.EventObserver 的多个对象可以绑定到一个元素上,不是一个帮其他的擦出了,而是按照他们付给元素的顺序执行这些回调方法。
  单选按钮和复选框的触发事件是 onclick ,而文本框和下拉列表框/下拉列表框的是 onchange
  子类必须实现这个方法来决定什么才是被监听的元素的当前值。

  Table 2.36. Abstract.EventObserver
方法类别参数描述
[ctor](element, callback)constructorelement: 元素对象或id, callback: 当事件发生的时候调用的方法。创建监听元素的对象。
registerCallback()instance(none)这个方法通常不会被外部调用。 被对象调用来把自己绑定到元素的事件上。
registerFormCallbacks()instance(none)这个方法通常不会被外部调用。 被对象调用来把自己绑定到表单中的每一个数据项元素的事件上。
onElementEvent()instance(none)这个方法通常不会被外部调用。 将被绑定到元素的事件上。



  Table 2.37. Abstract.EventObserver
属性类型描述
elementObject被监听的元素对象。
callbackFunction(Object, String)只要元素改变就调用的方法。会接收到元素对象和新值作为参数。
lastValueString元素被核实的最后一个值。




2.34. Form.Element.EventObserver



  继承自 Abstract.EventObserver
  Abstract.EventObserver 的一个实现类,它在监测到表单中数据项元素的值改变的相应事件时候执行一个回调方法。 如果元素没有任何报告变化的事件,那么你可以使用 Form.Element.Observer 类代替。

  Table 2.38. Form.Element.EventObserver
方法类别参数描述
[ctor](element, callback)constructorelement: 元素对象或id, callback: 当事件发生的时候调用的方法。继承自 Abstract.EventObserver。 创建一个监听元素值属性的对象。
getValue()instance(none)返回元素的值。




2.35. Form.EventObserver



  继承自 Abstract.EventObserver
  Abstract.EventObserver 的一个实现类,监听表单对象中包含的任何对象的任何变化,用元素的事件检测值的变化。如果元素没有任何报告变化的事件, 那么你可以使用 Form.Observer 类代替。

  Table 2.39. Form.Element.EventObserver
方法类别参数描述
[ctor](form, callback)constructorform: 元素对象或id, callback: 当表单中任何数据项改变的时候调用的方法继承自 Abstract.EventObserver。 创建一个监听元素值属性的对象。
getValue()instance(none)返回所有表单数据的一系列值。




2.36. Position 对象 (预备文档)



  这个对象提供许多和元素位置相关的方法。

  Table 2.40. Position 对象 (预备文档)
方法类别参数描述
prepare()instance(none)调整 deltaXdeltaY 属性来协调在滚动位置中的变化。 记得在页面滚动之后的任何调用的withinIncludingScrolloffset 之前调用这个方法。
realOffset(element)instanceelement: 对象或id返回这个元素的正确滚动偏差的 Array 对象,包括所有影响元素的滚动偏差。结果数组类似 [total_scroll_left, total_scroll_top]
cumulativeOffset(element)instanceelement: 对象或id回这个元素的正确滚动偏差的 Array 对象,包含任何被放置的父元素强加偏差。结果数组类似 [total_offset_left, total_offset_top]
within(element, x, y)instanceelement: 对象, xy: 一个点的坐标测试给定的点的坐标是否在给定的元素的外部矩形范围之内。
withinIncludingScrolloffsets(element, x, y)instanceelement: object, x and y: coordinates of a point 
overlap(mode, element)instancemode: 'vertical' 'horizontal', element: 对象在调用这个方法之前需要调用within() 。这个方法返回0.0到1.0之间的数字,来表示坐标在元素重叠的分数。 举个例子,如果元素是一个边长是100px的正方形的DIV,并且位于(300, 300), 然后 within(divSquare, 330, 330);overlap('vertical', divSquare); 会返回0.10,意思是那个点位于DIV顶部边框以下 10% (30px) 的位置上。
clone(source, target)instancesource: 元素对象或 id, target: 元素对象或id改变目标元素的大小尺寸和位置与源元素的相同。