用 Eclipse Modeling Framework 实现模型驱动开发,第 1 部分创建 UML 模型并生成代码 ![]() | ![]() |
![]() |
级别: 初级 Adrian Powell, Advisory I/T Specialist, IBM 2004 年 4 月 01 日 Eclipse Modeling Framework(EMF)是一个开放源代码的模型驱动应用程序开发框架。它可以基于 XML Schema、UML 或经过注释的 Java 中指定的模型,创建 Java 代码,实现图形化的数据编辑、操纵、读取和序列化。EMF 是 IBM WebSphere Studio 和 Eclipse 项目中很多工具的基础。本文将帮助您逐步了解创建模型、生成代码、使用生成的应用程序和定制编辑器的整个过程。
EMF 生成的代码也不是一种只配丢进垃圾箱的解决方案。这种代码支持标准的创建、获取、更新和删除操作,而且还支持元数约束、复杂关系和继承结构、屏蔽定义,以及一套属性描述。生成的代码还提供通知、参照完整性和可定制的 XMI 持久性。您所需要做的全部工作就是创建一个对象模型,就像您以前也想做的那样。 EMF 是比较新的事物,但前景广阔,对它持续支持的力度也很强。它实现的是一项公共标准,即对象管理组织(Object Management Group)的元对象工具(Meta-Object Facility,MOF)。现在 EMF 已经对 MOF 的第二版进行了增强。更进一步看,EMF 还是 EMF:XSD 以及 Hyades 等 Eclipse 项目的基础,大多数 IBM WebSphere Studio 产品也都使用它。EMF 第二版的开发已经开始,开发构建应该很快就会出炉。第二版开发计划中包括更好的 XML Schema 支持、更灵活的代码生成方式以及模型之间的映射机制。
我们将以一个简单的 Web 论坛为例,向您展示最重要的特性。模型的根为
一开始,我们创建一个新的 Java 项目 UMLForum,以及一个新包 如果您过一会改主意了,想使用其他的特性,可以打开 Properties 视图,选择其中的类或属性。 图 1. 新建的 Forum 类及其属性的性质 ![]() 对于下列接口重复上述步骤:
为定义关联,我们可以选中关联按钮,然后单击关联的源( 按照下表所示完成关联设置:
最后,我们要定义一个枚举类型,用于表示 topic 有多少不同的类型。创建一个新的枚举类型,名字叫做
然后,为 Topic 定义一个新属性,叫做 图 2. 完成后的 UML 类模型 ![]() 一旦您完成了图 2 所示的 UML 类图,下一步就是创建一个 EMF 模型。为此,需要先创建一个新的 EMF 项目( File > New > Project... > Eclipse Modeling Framework > EMF Project),并用 创建 EMF 模型
在清单 1 中,您可以看到枚举是如何表示的,也能从中了解到如何定义一个具有指向其他类型的元素和引用的类型。在 Forum 这个例子中,我们仅仅使用了字符串属性 一旦完成了 XSD,下一步就是创建 EMF 模型。方法与 UML 模型中类似,先创建一个新的 EMF 项目( File > New > Project... > Eclipse Modeling Framework > EMF Project),项目名称为 com.ibm.example.forum(这是插件名称的基础,因此我们遵从 Eclipse 插件的命名规范)。在下一个页面上选择 Load from an XML Schema,然后单击 Next。在文件系统中找出 XSD 文件并加载,然后 Generator 中的模型名就会自动填充。在最后一个页面上,单击包旁边的复选框,然后单击 Finish。这样就创建了一个 EMF 模型,名字叫做 forum.genmodel。 您可以从 使用生成的 EMF 模型一节中了解到这个模型是什么,以及如何使用它。
清单 2 声明了一个叫做 对于简单的属性,如
要获得完整的属性列表,请您参阅 参考资料中的 EMF user's guide。 惟一需要当心的是枚举类型。它被定义成一个 Class,而不是其他模型类中的 Interface! 为了明确这一点,清单 3 展示了 TopicCategory 枚举类型是如何实现的。 清单 3. 枚举类型 TopicCategory.java
最后,生成如下所示的三个接口,模型就完成了:
模型定义完成之时,可以生成 EMF 模型( File > New > Other > Eclipse Modeling Framework > EMF Models)。将父目录设为 com.ibm.example.forum/src/model, File name设为 forum.genmodel。在下一个页面上,选择 Load from annotated Java,然后选中包“forum”旁边的复选框。然后单击 Finish。这样就创建了一个名为 forum.genmodel 的 EMF 模型。
图 3. 在默认的编辑器中打开生成的 EMF 模型 ![]() 如果对模型描述(UML、XSD、带注释的 Java)进行了修改,也可以在 Package Explorer 中用右键单击该模型,然后选择 Reload,这样就能够重新加载模型。这实现了用 EMF 生成的模型与模型描述之间的同步。重新加载后将会改变您在生成的模型中修改过的属性。
如果您对模型描述感到满意,或者如果您仅仅是想看看所有这一切到底是什么意思,那么现在就可以生成代码了。在根节点上单击鼠标右键,选择其中一个生成选项:Model、Edit、或 Editor code。 Generate Model将在当前项目中创建该 EMF 模型的 Java 实现代码。其中会包含下列内容:
Generate Editor Code将创建 com.ibm.example.forum.edit 项目。其中仅仅包含一个包, 为了测试生成的插件,请依次进入 Run > Run... > Run Time Workbench > New。输入一个描述性的名称,然后在 plug-ins 选项卡中,选择 launch with all workspace and enabled external plug-ins。再在 Common 页下,单击 Display in favorites menu > Run和 Launch in background。最后保存设置并运行。 这时将出现一个新的 Eclipse 工作台,您可以在 Help > About Eclipse Platform > Plug-in Details下面验证您的插件是否可用,如图 4 所示。 图 4. Forum 的插件详细信息 ![]() 为了测试生成的插件,您可以创建一个新的 Simple 项目,名为“Forum Demo”,然后依次进入 New > Other... > Example EMF Model Creation Wizards > Forum Model。给文件取名叫做 sample.forum,然后选择 Forum 作为 Model Object。这时会打开一个窗口,您可以在这里向根中增加新的模型元素。其中包含几种视图:Selection、Parent、List、Tree、Table 和 TreeTable。所有这些视图都显示相同的数据,也和 Outline 视图保持同步。虽然所有视图都会在右键菜单选项中显示 New Sibling/New Child,但是我发现,有些视图在加入兄弟节点或子节点时不能正确响应。如果您也遇到这种情况,可以使用 TableTree 视图,或是在 Outline 视图中创建新的节点。图 5 展示了所生成的插件编辑器。 图 5. 所生成的插件编辑器 ![]()
为着重说明您能对代码进行哪些修改,让我们来看一个简单的例子。在所生成编辑器的 Table 视图中,两个字段都显示出相同的的值。这一点并不是完全没有用处。为了改善一下,我们可以修改第二个字段,让它在选中一个 Topic 的时候显示 Author,然后增加第三个字段,给出该 Topic 中的帖子数。 第一步,向 Table 视图中额外增加一个字段。这一步在
这样就额外增加了一个字段,但是现在所有的三个字段都显示相同的数据。为了定制每一个字段中的数据,我们需要提供一些
最后,我们需要注册这个提供程序。实现方法是编辑
现在我们再运行这个插件,打开表视图,就能看到图 6。请注意,没有实现的 图 6. 修改后的 Table 编辑器 ![]()
清单 7 展示了如何给文件关联一个符合 XMI 格式的扩展名“forummodel”,然后用 EMF 的 ResourceSet 解析并加载 forum 模型。我们知道,Forum 是惟一的根元素,所以可以想象, 我们还可以换一种方法,创建一个新的 Forum,然后用程序组装起来,如清单 8 所示。 清单 8. 初始化 Forum
在这个例子中,我们首先初始化包,然后创建 ForumFactory,用它生成所有的子对象。创建完毕之后,就可以像标准的 JavaBean 那样访问这些对象。然而,由于我们把 一旦我们创建并操纵了 EMF 模型,就很容易将其保存为我们选定的格式(参见清单 9)。 清单 9. 保存 Forum
在本例中,我们给
Eclipse Modeling Framework (EMF),简单的说,就是Eclipse提供的一套建模框架,可以用EMF建立自己的UML模型,设计模型的XML格式或编写模型的java代码。EMF提供了一套方便的机制,实现了功能的相互转换,大大提高了效率,下面是其具体功能介绍: 一、统一java、UML、XML 为了帮助理解EMF,我们可以举一个例子。假设老板要你写需要写一个程序,来管理供应商的采购清单,采购清单需要维护三大项:付款方(bill to)、运送地址(ship to)和购买货物集合(集合中包含名字name、数量quantity、价格price)。你回答说:没问题。然后就开始设计采购系统的java接口: 1 public interface PurchaseOrder
2 {
3 String getShipTo();
4 void setShipTo(String value);
5 String getBillTo();
6 void setBillTo(String value);
7 List getItems(); // List of Item
8 }
9 public interface Item
10 {
11 String getProductName();
12 void setProductName(String value);
13 int getQuantity();
14 void setQuantity(int value);
15 float getPrice();
16 void setPrice(float value);
17 }
然后就准备开始实现自己的接口,忽然老板问了一句,“你不要先建立自己的模型吗”,对于大多数从来不建模的java程序员,你认为代码就是模型,用那些形式化建模元素建立模型除了能在文档中增加篇幅外,没有任何好处,但即使如此,你仍然要听老板的话,所以你建立了如图1所示的UML模型。 图1 采购系统的UML模型 这里,你可以要求老板走开,然后编码实现了,但首先你还得保存模型,这里我们你想到了用XML文件保存,自诩聪明之时,写好了如下的XML schema文件: 1 <?xml version="1.0" encoding="UTF-8"?>
2 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
3 xmlns:po="http://www.example.com/SimplePO"
4 targetNamespace="http://www.example.com/SimplePO">
5 <xsd:complexType name="PurchaseOrder">
6 <xsd:sequence>
7 <xsd:element name="shipTo" type="xsd:string"/>
8 <xsd:element name="billTo" type="xsd:string"/>
9 <xsd:element name="items" type="po:Item"
10 minOccurs="0" maxOccurs="unbounded"/>
11 </xsd:sequence>
12 </xsd:complexType>
13 <xsd:complexType name="Item">
14 <xsd:sequence>
15 <xsd:element name="productName" type="xsd:string"/>
16 <xsd:element name="quantity" type="xsd:int"/>
17 <xsd:element name="price" type="xsd:float"/>
18 </xsd:sequence>
19 </xsd:complexType>
20 </xsd:schema>
在下一步工作之前,你突然意识到现在已经有三种系统表示:Java interfaces、UML模型和 XML Schema,你开始思考,同一个系统设计了三种模型(暂且认为XML和接口也是模型),进行了大量重复的工作,有没有一种方法,只设计其中的一种模型,然后实现三者之间的相互转换? 此时,EMF出现了,EMF是一个建模框架,能够实现java代码的生成,即它统一了三种重要的技术:Java, XML 和 UML。如图2所示。 图2 EMF 统一了UML、XML、java 假设你要操作一个XML文档,如果你是XML schema大牛,你可以从设计schema开始,然后通过EMF得到UML模型,再通过EMF得到java代码,当然如果你不是,可以从设计UML模型开始,一样可以完成上述功能。 二、建模 vs 编程 这里有的人可能会问:难道EMF只是一个描述模型和生成其它东西的框架吗?回答是的,但不全面,EMF的功能远不止此,它能有效地为编程服务,这也是一个经典问题的答案:“我是应该先建模还是直接写代码?”,EMF的回答是两者都可以,因此在EMF看来,两者是等价的。用英语回答就是:"To model or to program, that is not the question." 三、定义模型 上面我们用三种形式描述了我们的概念模型,那么这三者有没有共同的模型概念,方便它们之间的转换呢(模型转换的知识在此暂时省略)?我们再回顾下前面的三种模型:
可以看到这三者都用到了一种较高层的定义来分别表达UML、XML、java。因此,要实现EMF和模型之间的转换,我们需要这种能够描述EMF模型的模型,我们称为元模型。 1、Ecore (Meta)模型 能够描述EMF的模型称为Ecore元模型(位于MOF的M2层,关于MOF可以参考http://en.wikipedia.org/wiki/Meta-Object_Facility),它本身也是EMF模型,因此Ecore是它自己的元模型。如果再深入一点,Ecore的元模型又是什么呢?回答仍然是Ecore,因为它可以描述自身。Ecore只是OMG的MOF在Eclipse下的一种实现,可能还有其它元模型形式(可以参考http://wenku.baidu.com/view/d28de6717fd5360cba1adb28.html第二段),这里就不详细介绍了。图3给出了一个简化的Ecore元模型,说它是简化的,是因为它只是Ecore元模型的子集,而且为了方便,将某些公共类省略,如ENamedElement类(这个类定义了类中属性的名字)。 图3 简化的Ecore元模型 从图2中可以看到,我们需要四种Ecore类来描述我们的模型,它们是:
我们还可以观察到,Ecore元模型这么类似于UML中的类图,这不足为怪,因为UML本来就是一种统一的建模语言(名符其实),倒是有另一点可能不明白?即然Ecore也是UML,为什么不用UML作为UML的元模型呢?答案很简单,因为Ecore只是UML的子集,没有必要用到除类图以外的其它UML元素。 有了Ecore元模型,我们就可以用我们的例子实例化Ecore元模型,图4是采购系统的Ecore实例(如果对此不理解,可以参考http://www.cnblogs.com/riky/archive/2007/04/07/704298.html)。 图4 采购系统的Ecore实例 2、XMI序列格式 有了Ecore元模型后,可能有人会问:“Ecore的序列化格式(Serialized form)是什么呢?”是Java code、XML Schema 和 UML diagram中的一种吗?实际上,Ecore的规范序列化格式是XMI(XML Metadata Interchange),为什么会出现另外一种XMI格式,而不用前面介绍的三种格式呢?首先,Java code、XML Schema 和 UML diagram都增加了Ecore本身并不具有的信息,已经改变了Ecore;其次,Java code、XML Schema 和 UML diagram中没有一种能够适用于任何场合,即没有一种是通用的,而XMI正好满足这两点。我们的采购系统Ecore实例可以用下面的XMI序列表示: <?xml version="1.0" encoding="UTF-8"?>
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="po"
nsURI="http://www.example.com/SimplePO" nsPrefix="po">
<eClassifiers xsi:type="ecore:EClass" name="PurchaseOrder">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="shipTo"
eType="ecore:EDataType
http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="billTo"
eType="ecore:EDataType
http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="items"
upperBound="-1" eType="#//Item" containment="true"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Item">
<eStructuralFeatures xsi:type="ecore:EAttribute"
name="productName" eType="ecore:EDataType
http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="quantity"
eType="ecore:EDataType
http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="price"
eType="ecore:EDataType
http://www.eclipse.org/emf/2002/Ecore#//EFloat"/>
</eClassifiers>
</ecore:EPackage>
当我们听到别人要导出EMF模型时,实际上就是导出EMF的XMI。 3、java注释 我们已经知道EMF能够将java接口自动生成UML的属性和方法,但是否所有的接口都能生成呢?或者什么样的接口才能生成呢?首先,EMF并不会盲目地把所有的java接口自动生成UML的属性和方法,其次,只有符合特定规范才能通过EMF生成(EMF使用的是JavaBeans简单属性祖先命名模式的子集,具体规范可参考http://java.sun.com/products/javabeans/docs/spec.html)。根据此规范,需要用@model标明哪些接口需要用EMF生成模型。例如前面给的PurchaseOrder接口就需要用下面的格式来生成UML: /**
* @model
*/
public interface PurchaseOrder
{
/**
* @model
*/
String getShipTo();
/**
* @model
*/
String getBillTo();
/**
* @model type="Item" containment="true"
*/
List getItems();
}
4、Ecore "Big Picture" 我们先来回顾前面的知识:
其实,通过XML schema定义模型有一个优势:给定schema后,可以序列化成持久模型的实例。因为XML schema不仅仅定义了模型,还指定了模型实例的持久格式。问题出来了:是否还有其它的持久模型格式呢?回答是肯定的,例如relational database (RDB) Schema等。Ecore的"big picture"如图5所示。 图5 Ecore及其源格式 四、生成代码 EMF最主要的功能莫过于其自动代码生成能力了,能够极大提高工作效率。假设我们已经有了上面的模型,生成转换成Java代码呢?你只要新建EMF项目,它即自动加载了生成器。那么,EMF生成是的是什么样的代码呢? 首先,Ecore类(即EClass)相当于java的两个实体:接口及其实现类。例如对于EClass PurchaseOrder相当于java的: public interface PurchaseOrder ...
public class PurchaseOrderImpl extends ... implements PurchaseOrder {
是这种设计方式,是因为我们认为这是类模型API(如文档对象模型DOM)的最好模式。 其次,每个生成的接口都直接或间接扩展了基接口EObject,如: public interface PurchaseOrder extends EObject {
EObject是java.lang.Object的EMF等价物,即所有模型对象的基础。扩展的EObject包含下面三种行为:
然后,EObject还是另一个接口的扩展: public interface EObject extends Notifier {
最后,根据类型及用户设定的属性生成代码。例如: public String getShipTo()
{
return shipTo;
}
对于相应的set()会有点不同,它会对其它可能对此属性状态改变的观察者发出通知,如: public void setShipTo(String newShipTo)
{
String oldShipTo = shipTo;
shipTo = newShipTo;
if (eNotificationRequired())
eNotify(new ENotificationImpl(this,
Notification.SET,
POPackage.PURCHASE_ORDER__SHIP_TO,
oldShipTo, shipTo));
}
注意到当没有观察者时,为了避免调用eNotify()花费的高昂代价,EMF会添加一个eNotificationRequired()守卫条件。 五、总结 本篇由浅入深的介绍了EMF的相关知识,由于篇幅原因,不可能将全部的EMF知识都写在这里,感兴趣的读者可以通过参考书籍了解其它内容。 六、参考资料 1. Steinberg, D., Budinsky, F., Paternostro, M., Merks, E.: Eclipse Modeling Framework, 2nd Edition. Pearson Education (2008) |