1、上海交通大学硕士学位论文基于元数据的Java平台契约式设计框架研究姓名:顾毅申请学位级别:硕士专业:软件工程指导教师:沈备军;高国明20081230基于元数据的 Java平台契约式设计框架研究基于元数据的 Java 平台契约式设计框架研究摘 要契约式设计方法是面向对象软件工程中用于提高可靠性的形式化方法学。然而,在主流的开发语言中,并没有内建契约式设计的支持。为了使契约式设计方法使用更为方便,就需要在语言层次中加入契约式设计的支持。本文首先探讨了在 Java平台上实现契约式设计的可选方案。目前主流的解决方案都实现了声明式编程的契约声明模型。在契约验证的方式上,有代表传统的静态工具的JML,也有
2、采用 AOP和 JSR-175等较新技术的 Contract4J。由于后者在易用性、可扩展性等方面更胜一筹,因此本文着重以后者为对象展开研究。研究发现,Contract4J 在自动处理契约继承、保存契约验证时所需的“ 旧状态”以及性能和可动态管理等方面存在一定的不足。于是,以解决上述问题为目标,设计并实现了一个采用 AspectJ和 JSR-175元数据机制的动态契约式设计框架,命名为 EasyDbC。EasyDbC利用 AspectJ和 JSR-175的特性,实现了契约的“叠加继承” ,从而解决了使用 JSR-175的契约元数据无法自动继承的问题。采用对象序列化技术解决“ 旧状态”的保存问题
3、。同时,通过增加基于 JMX的动态管理功能,使得开发及维护人员能够更有效地利用契约验证功能。EasyDbC赋予了开发人员将契约式设计以及契约验证从开发过程到正式运行环境的平滑过渡的能力,使得契约式设计方法得到更广泛的应用。关键词:契约式设计,声明式编程,面向方面编程,Java,元数据, JSR-175基于元数据的 Java平台契约式设计框架研究A RESEARCH OF THE DESIGN BY CONTRACT FRAMEWORK FORJAVA PLATFORM BASED ON METADATAAbstractDesign by Contract is a formal method
4、of the Object-Oriented software engineering,which can raise the reliability of software system. But main-stream programming languagesdont provide the language level support for Design by Contract. To provide a convenientway of using Design by Contract, the support of Design by Contract at language l
5、evel isneeded.First, the discussion is started with several methods of realizing Design by Contract onthe Java platform. Most solutions have adopted the declarative-programming style for thecontract declaration. In the methods of contract verification, there is delegate of the classicstatic tools su
6、ch as JML. And also there is Contract4J which based on the AOP and JSR-175Annotation. The latter one provides more convenient and extensibilities. So the studies arefocused on the latter one.According to the studies of Contract4J, some problems of Contract4J in automaticprocessing the contract in a
7、type hierarchy and in save the “old-state” which are used in thecontract verification has bean discovered. Also, it lacks the dynamic management features.Focusing on the solutions of these problems, with AOP and the JSR-175 Annotation, anew Design by Contract framework named EasyDbC is designed and
8、implemented. In theimplementation of EasyDbC, by taking the advantage of AspectJ and JSR-175, a solutionnamed “additional inheritance” is achieved to solve the problem of auto-processing theinheritance of contracts. And the object serialization technology is used to save the “old-state”of an object.
9、 By using JMX to provide some management functions, the developers and themaintainers can adopt Design by Contract more efficiently.With these improvements, EasyDbC can provide a seamless migration from thedeveloping phase to the after-releasing phase. And can widen the usage of Design byContract.Ke
10、ywords: Design by Contract, declaration programming, aspect-oriented programming,Java, metadata, JSR-175I基于元数据的 Java平台契约式设计框架研究上海交通大学学位论文版权使用授权书本学位论文作者完全了解学校有关保留、使用学位论文的规定,同意学校保留并向国家有关部门或机构送交论文的复印件和电子版,允许论文被查阅和借阅。本人授权上海交通大学可以将本学位论文的全部或部分内容编入有关数据库进行检索,可以采用影印、缩印或扫描等复制手段保存和汇编本学位论文。保密 ,在本学位论文属于不保密 。(请在以
11、上方框内打“”)学位论文作者签名:顾毅年解密后适用本授权书。指导教师签名:日期:年月日日期:年月日基于元数据的 Java平台契约式设计框架研究上海交通大学学位论文原创性声明本人郑重声明:所呈交的学位论文,是本人在导师的指导下,独立进行研究工作所取得的成果。除文中已经注明引用的内容外,本论文不包含任何其他个人或集体已经发表或撰写过的作品成果。对本文的研究做出重要贡献的个人和集体,均已在文中以明确方式标明。本人完全意识到本声明的法律结果由本人承担。学位论文作者签名:顾毅日期:年月日基于元数据的 Java平台契约式设计框架研究1面向对象软件工程及契约式设计基础1.1 契约式设计概述1.1.1传统面向
12、对象编程语言的不足面向对象的分析和设计方法(OOA此外,当契约验证开始时,可能仍然需要一些方法调用以帮助契约表达式求值,比如一些 get方法取得某些属性。最糟糕的情况是契约表达式中的方法形成了递归调用,使得某个契约表达式无法求值。这种情况是必须避免的,也就是说,对于契约表达式内部出现的方法调用,无论其是否含有契约声明,都应该避免对其进行验证。这一点已经体现在了 Contract4J的设计中。EasyDbC 的设计也遵循这点,利用 AspectJ中的 cflow机制实现这个功能。 32pointcut contract() :dbcMethod() 这个联接点结合了之前的 dbcMethod联接
13、点,并且使得在 ContractContext中执行的代码不进行契约验证。(ContractContext是用于契约验证的上下文,验证动作会在上下文中执行,后面会进行详细说明。)然后,利用这个链接点,声明一个用于契约检查的环绕通知。这就构成了整个契约验证方面。4.3.2采用 LTW机制织入方面EasyDbC采用 AspectJ的 LTW方式进行方面的动态编织,为了实现动态编织,需要一个配置文件 aspect.xml对编制进行描述,使用时只需修改对象包即可实现对指定包中所有的类织入契约验证方面。第19页基于元数据的 Java平台契约式设计框架研究在上面的例子中,通过 LTW机制,为 org.Ea
14、syDbC.test.subjects包中的所有的类增加了契约验证的方面。对于没有 AspectJ开发工具的用户,仅需要在 classpath中加入 aspectj-runtime.jar和EasyDbC.jar,并编辑上述的配置文件即可使用 EasyDbC进行契约式设计。4.3.3运行时取得契约在第二章中就使用元数据形式的契约的方式进行过讨论。当使用 JSR-175的元数据机制声明契约时,运行时通过反射机制获取契约是最为简便的方法。下面讨论其详细步骤。首先,方法的调用在虚拟机中会形成一个调用栈,每个线程各有一个独立的调用栈,调用栈中的一个元素包含了该次方法调用的所有信息,包括被调用的方法,该
15、方法所属的对象在当时的状态,方法参数即调用发出方发送的消息。这些信息被称为方法调用上下文,在 EasyDbC中,用一个ContractContext对象表示方法调用上下文。ContractContext用于管理一次方法调用所包含的契约验证的生命周期,包含一个契约对象和一个被调用对象。init方法用于初始化一个契约上下文,bindArguments和 bindReturnValue分别用于绑定调用时被传入该方法的参数以及方法执行完成后的返回值。 preVerify和 postVerify用于方法执行前和执行后的契约验证。这套状态流程在ContractContext内部使用状态机模式设计,严格确保
16、了状态迁移关系。图 4-1是 ContractContext的静态结构以及状态迁移。图 4-1 ContractContextFig.4-1 ContractContext第20页基于元数据的 Java平台契约式设计框架研究当某个方法的调用被契约验证的通知拦截后,首先要做的就是构造一个 ContractContext对象。通过联接点的信息,可以得到被调用的对象、被调用的方法签名和来自调用方的参数。为了便于后续使用,在设计中将把前两种信息组合在一起,作为一个 MethodSignature对象(与 AspectJ的MethodSignature不同)。图 4-2 MethodSignatureF
17、ig.4-2 MethodSignatureMethodSignature所包含的是一个方法的静态签名。然后利用 MethodSignature对象,通过契约抽取器获得 Contract对象。在 Contract4J中,契约并不以独立的对象形式存在。而是以 TestContext中的 expression属性存在。EasyDbC采用了独立的 Contract对象来表示契约后,与运行时的动态实例有关的契约上下文(ContractContext)和静态的契约(Contract)就能够彼此独立存在了,这样做就能够对每次方法调用中所抽取的契约进行缓存处理,为实现性能的优化做准备。图 4-3通过 Met
18、hodSignature构造 Contract对象Fig.4-3 Create Contract with MethodSignature第21页基于元数据的 Java平台契约式设计框架研究ContractFactory中使用了标准的 Java反射 API,用以获得对应的契约元数据。14每个 Contract都由三个表达式(Expression对象)构成。Contract 还含有一个表示父类型契约的 Contract类型的契约parentContract。当存在继承关系时,整个继承树中的契约将一次性生成。Contract对象生成后,将通过 ContractContext的 init方法初始化一
19、个契约验证上下文。所有这些步骤完成后,就可以对契约上下文进行验证了。验证契约的方法不作为本节的讨论对象。4.3.4处理契约的继承如何处理契约的继承问题,一直是在 Java中实现契约式设计的一个难题。大部分框架不提供对契约继承的支持,为了实现契约继承,只能由开发人员在子类的方法声明契约时先复制一遍父类的契约,再补充子类的契约。这样做显然犯了软件开发中复制代码的大忌,给代码的维护带来极大的不便。有的框架则采取了折中的办法。如在 Contract4J中,开发人员可以在子类中声明一个空白的契约表达式,框架遇到空白的契约就会自动向上一级类型查找对应的契约,如果存在则自动继承。而对于需要扩展父类的契约的情
20、况,仍然需要把父类的契约复制到子类上。尽管可以避免一些复制的工作,但仍有一部分无法避免。并且由于这个向上查找契约的过程是递归的,当一个子类型同时既继承了一个类又实现了一个接口且两者的方法签名相同的时候,究竟以哪个父类型中的契约为准会导致一个不确定性的问题。显然,对于契约式设计框架来说,这是一个很严重的问题。这个问题的关键在于元数据的继承机制。元数据是类型声明的一部分,当元数据所对应的类型声明含有继承关系时,元数据应该随之继承。然而,在 Java中,元数据的继承行为并不和类型的继承相同。JSR-175规定了元数据的继承行为,即对于任何一个自定义的元数据类型,可以在声明时加上Inherited元数
21、据用于表示一个元数据拥有继承行为。例如:Inheritedpublic interface Foo 这个声明表明元数据 Foo可以拥有继承行为。规范对于继承行为的定义如下:当对一个类型进行某个元数据的查询时,如果该类型没有直接包含该元数据,则向其父类型查询该元数据,此过程是递归的,直到找到这个元数据的显式声明或到达继承树的顶部(java.lang.Object类)。并且,上述元数据的继承行为只对类(class)上声明的元数据起作用;对于其他位置出现的元数据声明则不支持继承行为。仔细分析 Java的继承体系便可得知 JSR-175规范不支持类以外的元数据继承的原因。Java的继承体系源于 C+。
22、继承表达了一种 Is-A的语义。C+支持“多继承” 特性,允许一个类继承多个父类。而多继承特性直接导致了“菱形问题”的产生。21第22页基于元数据的 Java平台契约式设计框架研究图 4-4继承体系中的菱形问题Fig.4-4 The Diamond Problem如图 4-4,所谓菱形问题是由下面的继承过程产生的:首先,假设有一个基类 A,A中有一个方法 foo。然后另两个类 B1和 B2同时继承 A构成兄弟关系,而这两者又分别覆盖了基类的方法 foo。最后,另一个类 C直接或间接地通过多继承特性同时继承了 B1和 B2,则对于 C中的方法 foo继承体系将无法确定应该采用 B1中 foo的行
23、为还是 B2中 foo的行为。继承出现了二义性。现代的面向对象设计理论指出,“菱形问题”应该在设计阶段避免。而 Java语言在设计之初就为了强调这一设计理论,规避复杂的“菱形问题”,从语言层次上不支持多继承,并且 Java还使用java.lang.Object类作为基类实现了单根继承体系,而只在接口层面保留的多继承的特性。因为接口本身不能实现任何方法,所以虽然在语言规范中规定接口继承后能够覆盖(override)父接口中的方法声明,但从实际运用角度来说,因为接口对方法的覆盖不能违反父接口对方法行为的声明,即便发生了类似 C+的多继承中的“ 菱形问题”,也不会对实现类的方法定义产生不确定性。Ja
24、va 通过这一方式保证了继承体系中语义传递的确定性。图 4-5元数据的菱形问题Fig.4-5 The Diamond Problem of Metadata Inheritance第23页基于元数据的 Java平台契约式设计框架研究但是,引入了元数据的继承后,对于类(class)来说,因为从语言层次本身就不允许类的多继承。所以子类直接把父类的元数据继承下去是没有问题的。而对于接口和类成员来说,就会发生问题:首先是接口,前面已经提到,接口可以实现类似多继承的语义,并且通过接口的多继承,Java也能够遇到菱形问题。如 4-5,当 B2上的元数据声明覆盖了 A的元数据声明后,类 C就失去了元数据的确
25、定性。同理,对于类中的成员,元数据的继承也会遇到由菱形问题带来的不确定性问题。因此,同接口上的元数据一样,语言本身不支持这种语义。为了扩展元数据的继承行为,首先要分析以下两种继承行为:“可覆盖继承”“叠加继承”“可覆盖继承”是 Java以及其他各种面向对象语言中默认的继承行为:即子类中如果含有与父类中签名一致的声明时,子类将覆盖父类的行为;如果子类不包含签名相同的声明,则在可见性允许的前提下,子类将继承父类所定义的行为。另一种继承行为是“叠加继承”,即:在整个继承体系中,子类中声明的行为并不覆盖父类的行为,而是与父类的行为互补,合成为一个完整的行为。与“可覆盖” 继承不同,“叠加继承” 能将所
26、有父类型的元数据合成到一起,因此,即便出现了“菱形问题” 也因为这些信息不会被覆盖,从而使得元数据继承能够保持确定性。“叠加继承”在传统的面向对象系统中不常见,但是如果使用声明式编程,就需要用到这种继承行为了。比如:用元数据实现契约式设计就是一例,当存在继承关系时,契约式设计要求子类型能够兼容父类型的所有行为。即子类必须把继承自父类的契约和本身的契约合成为一整套契约,然后一起验证。通过上述分析,可见 JSR-175所不允许的是元数据的“可覆盖继承” 行为。而对于“叠加继承”行为来说,由于不会产生不确定性,因此,在不违反规范的前提下,实现“叠加继承” 是完全可行的。元数据是类型信息的一部分,属于
27、静态类型信息。在 Java中,运行时可以利用反射 API获得类型的静态信息。为了实现元数据的叠加继承行为,其重要前提就是对一棵继承树的遍历。首先对 Java的继承树进行分析。图 4-6是一个典型的 Java中的继承树的结构。第24页基于元数据的 Java平台契约式设计框架研究图 4-6一颗典型的继承树Fig.4-6 A Typical HierarchyJava采用了单根继承的方式实现对象继承体系,即任何类的最原始基类都是 java.lang.Object;接口则没有原始基类。因为接口只能被实现,而不能直接实例化,因此对于运行时任何一个对象,通过方法 getClass(),总能够获得一个类类型
28、。而给定一个运行时的对象的类(如图 4-6中的 C2),利用反射 API,就可以对继承树进行遍历。主要有以下方法:22,34java.lang.Class#getSuperClass()用于获得父类(由 C2得到 C1)java.lang.Class#getInterfaces()用于获得(由 C2得到 I2和 I3)遍历的终止条件则是到达继承树的树叶节点。即:clazz = null | clazz.equals(Object.class)在继承树中任何一个层次上的类型,都可以使用 java.lang.Class#getDeclaredMethod(String name,Class types)来获取一个方法对象( java.lang.reflect.Method),从而得到方法声明。利用上述 API获得相应的类型和方法声明后,就能通过 getDeclaredAnnotations()方法获取元数据了。这个方法返回在对应的方法或类型上所有显式声明的元数据(不包括根据 JSR-175所规定的继承行为继承自父类的元数据)。通过自底向上的遍历,即可得到每一个节点中类型和方法上所声明的元数据。第25页