Eclipse Modeling Framework 实现模型驱动开发

用 Eclipse Modeling Framework 实现模型驱动开发,第 1 部分

创建 UML 模型并生成代码

developerWorks
文档选项
将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

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 究竟是什么?


Eclipse Modeling Framework(EMF)是一个开放源代码的框架,它的目标是实现模型驱动架构(Model-Driven Architecture)的开发。如果我们当中的少数人有幸得到了某个 UML 模型,那么这个框架就可以帮助我们将文档变成代码。至于其他人,这个工具也使您又有一次机会向老板证实,把时间花在为解决方案建模上是值得的。除了可以生成令人赞叹的 Java 代码之外,EMF 还可以生成 Eclipse 插件,以及图形化的可定制编辑器。当您改变模型时(这种情况真的会出现),EMF 可以通过单击一个按钮,就使代码和模型保持同步。

EMF 生成的代码也不是一种只配丢进垃圾箱的解决方案。这种代码支持标准的创建、获取、更新和删除操作,而且还支持元数约束、复杂关系和继承结构、屏蔽定义,以及一套属性描述。生成的代码还提供通知、参照完整性和可定制的 XMI 持久性。您所需要做的全部工作就是创建一个对象模型,就像您以前也想做的那样。

EMF 是比较新的事物,但前景广阔,对它持续支持的力度也很强。它实现的是一项公共标准,即对象管理组织(Object Management Group)的元对象工具(Meta-Object Facility,MOF)。现在 EMF 已经对 MOF 的第二版进行了增强。更进一步看,EMF 还是 EMF:XSD 以及 Hyades 等 Eclipse 项目的基础,大多数 IBM WebSphere Studio 产品也都使用它。EMF 第二版的开发已经开始,开发构建应该很快就会出炉。第二版开发计划中包括更好的 XML Schema 支持、更灵活的代码生成方式以及模型之间的映射机制。




回页首


让工具自己说话


商业宣传已经说得够多了。现在让我们直接进入代码中,看看 EMF 到底能做些什么。下面的例子都是用 Eclipse 3.0M7 和 EMF 2.0.0,再加上与之匹配的 XSD 工具箱实现的。现在有四种独立的 EMF 开发流程,每一种都适用于不同版本的 Eclipse,所以一定要保证根据您的 Eclipse 版本选择了正确的 EMF 版本(请参阅 参考资料中的链接,获取这些插件)。

我们将以一个简单的 Web 论坛为例,向您展示最重要的特性。模型的根为 Forum ,下面包括一组 Member 和 Topic 。每一个 Topic 都具有一个 TopicCategory (枚举类型), Member 和Topic 通过 Post 类间接相关联,这两者之间也存在直接关联,因为 Member 可以创建 Topic 。

用 UML 和 Omondo 创建 EMF 模型


Omondo 的 UML 插件是在 Eclipse 中创建 UML 文档的方便可靠的工具。它看起来就像是 Rational Rose 受冷落的小兄弟,但除非是您需要特别强大的功能,否则用它就可以工作得很好了。不过,该工具尚不支持 Eclipse 3,所以我采用 Eclipse 2.1 来创建 UML 类图。

一开始,我们创建一个新的 Java 项目 UMLForum,以及一个新包 com.ibm.example.forum 。再创建一个新的 EMF 类图, forum.ucd ,存放在 src/com/ibm/example/forum 下。目录中创建了两个文件,forum.ecd 和 forum.ecore。向类图中增加一个新类,名为 Forum ,然后单击 Finished。向 Forum 类中增加一条属性描述,类型为 EString (对于所有的简单 Java 类都有相应的 Ecore 类),如图 1 所示。对于属性的特性,只选择 changeable ,并将范围设为从 0 到 1。

如果您过一会改主意了,想使用其他的特性,可以打开 Properties 视图,选择其中的类或属性。


图 1. 新建的 Forum 类及其属性的性质
新建的 Forum 类及其属性的性质 

对于下列接口重复上述步骤:

接口 属性 类型
Member nickname EString
Topic title EString
Post comment EString

为定义关联,我们可以选中关联按钮,然后单击关联的源( Forum )和目标( Member )。这样将打开关联属性设置对话框。在其中将名字设置为 members ,确保仅仅选择了 changeable 和 containment,然后将上限设为 -1。在第二个 Association End 选项卡中,取消选中的 Navigable,然后单击 Ok。对 Forum 和 Topic 也执行相同的操作,属性名称从 members 改为topics 。取消选中的 navigable,从而创建一个无方向的关联,但我们想让其他属性都保持为双向。

按照下表所示完成关联设置:

目标 关联 名称 特性 范围
Member Topic 1st Association topicsCreated changeable 0 到 1
2nd Association creator changeable 0 到 1
Topic Post 1st Association posts Containment, changeable 0 到 -1
2nd Association topic changeable 0 到 1
Member Post 1st Association posts changeable 0 到 -1
2nd Association author changeable 0 到 1

最后,我们要定义一个枚举类型,用于表示 topic 有多少不同的类型。创建一个新的枚举类型,名字叫做 TopicCategory 。Literal 中加入以下的内容:

  • ANNOUNCEMENT , value = 0
  • GUEST_BOOK , value = 1
  • DISCUSSION , value = 2

然后,为 Topic 定义一个新属性,叫做 category ,类型为 TopicCategory ,changeable,范围 0-1。如果您愿意的话,可以在属性标签上对默认值进行修改,但我们将接受 ANNOUNCEMENT的默认值。


图 2. 完成后的 UML 类模型
完成后的 UML 类模型 

一旦您完成了图 2 所示的 UML 类图,下一步就是创建一个 EMF 模型。为此,需要先创建一个新的 EMF 项目( File > New > Project... > Eclipse Modeling Framework > EMF Project),并用 com.ibm.example.forum 作为该项目的名称(这是插件名称的基础,因此我们遵从 Eclipse 插件的命名规范)。在下一个页面上,选择 Load from an EMF core model,然后单击 Next。从文件系统中加载 ecore 文件,它将自动填充 Generator 的模型名。在最后一个页面上,单击包旁边的复选框,然后单击 Finish。这样就创建好了 EMF 模型,它的名字叫做 forum.genmodel。您可以从 使用生成的 EMF 模型一节中了解到这个模型是什么,以及如何使用它。

用 XML Schema

创建 EMF 模型 
XML Schema(XSD)的表现能力不如 UML 或带注释的 Java 代码那么强大,例如,它不能表达出双向引用的关联。但是由于默认的的序列化方法要使用到您的方案,因此 XSD 对定制序列化来说是最快的方法。如果您希望为模型生成非常详细的 XML/XMI,那么 XSD 就是必然的选择。

清单 1. forum.xsd 的片段 
<xsd:simpleType name="TopicCategory">
                        <xsd:restriction base="xsd:NCName">
                        <xsd:enumeration value="Announcement"/>
                        <xsd:enumeration value="GuestBook"/>
                        <xsd:enumeration value="Discussion"/>
                        </xsd:restriction>
                        </xsd:simpleType>
                        <xsd:complexType name="Post">
                        <xsd:sequence>
                        <xsd:element name="comment" type="xsd:string"/>
                        <xsd:element name="author" type="xsd:anyURI" ecore:reference="forum:Member"/>
                        <xsd:element name="topic" type="xsd:anyURI" ecore:reference="forum:Topic"/>
                        </xsd:sequence>
                        </xsd:complexType>
                        

在清单 1 中,您可以看到枚举是如何表示的,也能从中了解到如何定义一个具有指向其他类型的元素和引用的类型。在 Forum 这个例子中,我们仅仅使用了字符串属性 "xsd:string" ,但是其他简单 Java 类型也是支持的。有关 XML Schema 和 forum.xsd 文件的更多信息,请参阅 参考资料

一旦完成了 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 模型一节中了解到这个模型是什么,以及如何使用它。

用带注释的 Java 代码创建 EMF 模型


如果通过 Java 代码定义 EMF 模型,我们可以用 Interface 列出每一个类的属性,以及类之间的关系。这样得到的内容并不充足,无法定义我们想要的全部信息,所以 EMF 使用了特殊的 JavaDoc 标签。每一个属性或类,如果是 EMF 模型的一部分,就必须在其 JavaDoc 中包含一个 @model 标签,也可以包含一个附加属性列表。比如说,如果要构造如上面图 2 所示的一个对象模型,我们对 Forum 的定义看起来应该像清单 2 的样子。

清单 2. 带注释的 Forum.java 
package com.ibm.example.forum;
                        import java.util.List;
                        /**
                        *
                        @model
                        */
                        public interface Forum {
                        /**
                        *
                        @model type="Topic" containment="true"
                        */
                        List getTopics();
                        /**
                        *
                        @model type="Member" containment="true"
                        */
                        List getMembers();
                        /**
                        *
                        @model
                        */
                        String getDescription();
                        }
                        

清单 2 声明了一个叫做 Forum 的对象,它具有一条 String 类型的描述信息和两个孩子,一个是 Topic 列表,还有一个是 Member 列表。这两个孩子都包含在 Forum 之内。

对于简单的属性,如 描述信息 , @model 标签就足够了,但对于 list 而言,您也需要为其指明类型。 containment 属性是可选的,但是如果某个对象是被包含的,那么它就和其容器一起被序列化。为了简化序列化的过程,我们要保证所有的对象都是直接或者间接包含在 Forum 中的。其他一些有用的可选属性如下:

  • opposite (用于双向属性)。
  • default (属性的默认值)。
  • transient (该属性不能被序列化)。

要获得完整的属性列表,请您参阅 参考资料中的 EMF user's guide。

惟一需要当心的是枚举类型。它被定义成一个 Class,而不是其他模型类中的 Interface! 为了明确这一点,清单 3 展示了 TopicCategory 枚举类型是如何实现的。

清单 3. 枚举类型 TopicCategory.java 
package com.ibm.example.forum;
                        /**
                        * @model
                        */
                        public
                        class TopicCategory{
                        /**
                        * @model name="Announcement"
                        */
                        public static final int ANNOUNCEMENT = 0;
                        /**
                        * @model name="GuestBook"
                        */
                        public static final int GUEST_BOOK = 1;
                        /**
                        * @model name="Discussion"
                        */
                        public static final int DISCUSSION = 2;
                        }
                        

最后,生成如下所示的三个接口,模型就完成了:

接口 方法 模型标签
Member List getPosts() type="Post" opposite="author"
List getTopicsCreated() type="Topic" opposite="creator"
String getName()
Topic List getPosts() type="Post" opposite="author"
Member getCreator() opposite="topicsCreated"
String getTitle()
TopicCategory getCategory()
Post Member getAuthor opposite="posts"
Topic getTopic() opposite="posts"
String getComment()

模型定义完成之时,可以生成 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 模型。




回页首


使用生成的 EMF 模型


现在您的工作空间中应该有一个生成好的 EMF 模型 forum.genmodel。这个模型中包含您输入其中的所有信息。用默认的编辑器打开这个模型(参见图 3),再打开 Properties 视图,然后检查模型树中每一个节点的属性。前面输入的所有属性都可以定制,但是也有一些用于定制代码生成的属性。为了验证这一点,让我们试着修改“Copyright Text”或“Generate Schema”之类的属性,看看会发生什么事情。


图 3. 在默认的编辑器中打开生成的 EMF 模型
带有生成的 EMF 模型的 Eclipse 屏幕快照 

如果对模型描述(UML、XSD、带注释的 Java)进行了修改,也可以在 Package Explorer 中用右键单击该模型,然后选择 Reload,这样就能够重新加载模型。这实现了用 EMF 生成的模型与模型描述之间的同步。重新加载后将会改变您在生成的模型中修改过的属性。




回页首


生成 Java 代码

如果您对模型描述感到满意,或者如果您仅仅是想看看所有这一切到底是什么意思,那么现在就可以生成代码了。在根节点上单击鼠标右键,选择其中一个生成选项:Model、Edit、或 Editor code。 Generate Model将在当前项目中创建该 EMF 模型的 Java 实现代码。其中会包含下列内容:

  • com.ibm.example.forum -- 创建该 Java 类的接口和工厂。
  • com.ibm.example.forum.impl -- com.ibm.example.forum 中定义的接口的具体实现。
  • com.ibm.example.forum.util -- AdapterFactory。

Generate Editor Code将创建 com.ibm.example.forum.edit 项目。其中仅仅包含一个包, com.ibm.example.forum.provider ,用于控制每一个模型对象出现在编辑器中的方式。Generate Editor Code将在 com.ibm.example.forum.editor 项目中创建一个插件编辑器示例,其中包含了 com.ibm.example.forum.presentation。这些类提供了一系列简单的 JFace 编辑器,可以与您的模型进行交互。

为了测试生成的插件,请依次进入 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 的插件详细信息
自定义插件列表的 Eclipse 屏幕快照 

为了测试生成的插件,您可以创建一个新的 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. 所生成的插件编辑器
自定义插件列表的 Eclipse 屏幕快照 



回页首


定制生成的代码


生成的代码都很不错,但是这只是真正应用程序的起点。为了满足我们的需要,我们必须对其进行调整和定制。我们可以改变所生成的模型类的实现,也可以对编辑器进行扩展和定制。好在 EMF 没有让我们失望,我们可以按照自己的想法做任何定制,当重新生成代码时也不会丢掉这些内容。我们需要做的全部工作就是删除 @generated JavaDoc 标签,EMF 的 jmerge 将保证这些方法、属性或类不被打扰。

为着重说明您能对代码进行哪些修改,让我们来看一个简单的例子。在所生成编辑器的 Table 视图中,两个字段都显示出相同的的值。这一点并不是完全没有用处。为了改善一下,我们可以修改第二个字段,让它在选中一个 Topic 的时候显示 Author,然后增加第三个字段,给出该 Topic 中的帖子数。

第一步,向 Table 视图中额外增加一个字段。这一步在 com.ibm.example.forum.editor 项目中实现,即 createPages() 方法中的 com.ibm.example.forum.presentation.ForumEditor 。把 @generated 标签删除,这样就能持久保存我们的修改,然后定位到表浏览窗口所在的位置。按照清单 4 的内容对这段代码进行修改。

清单 4. 修改后的 createPages() 
TableColumn selfColumn = new TableColumn(table, SWT.NONE);
                        layout.addColumnData(new ColumnWeightData(2, 100, true));
                        selfColumn.setText("Author");
                        selfColumn.setResizable(true);
                        TableColumn numberColumn = new TableColumn(table, SWT.NONE);
                        layout.addColumnData(new ColumnWeightData(4, 100, true));
                        numberColumn.setText("Number of Posts");
                        numberColumn.setResizable(true);
                        tableViewer.setColumnProperties(new String [] {"a", "b", "c"});
                        

这样就额外增加了一个字段,但是现在所有的三个字段都显示相同的数据。为了定制每一个字段中的数据,我们需要提供一些 ITableItemLabelProvider 的实现。打开com.ibm.example.forum.provider.TopicItemProvider ,在实现列表中加入 ITableItemLabelProvider 。我们需要增加两个方法, getColumnText(Object, int) 和 getColumnImage(Object, int) ,如清单 5 所示。

清单 5. 加入 TopicItemProvider 
public String getColumnText(Object obj, int index) {
                        if( index == 0 ){
                        return getText(obj);
                        }
                        else if( index == 1 ) {
                        return ((Topic)obj).getCreator().getNickname();
                        } else if( index == 2 ) {
                        return " + ((Topic)obj).getPosts().size();
                        }
                        return "unknown";
                        }
                        public Object getColumnImage(Object obj, int index) {
                        return getImage( obj );
                        }
                        

最后,我们需要注册这个提供程序。实现方法是编辑 com.ibm.example.forum.provider.ForumItemProviderAdapterFactory 的构造函数,向支持的类型中增加 ITableItemLabelProvider ,如清单 6 所示。

清单 6. ForumItemProviderFactory 构造函数 
public ForumItemProviderAdapterFactory() {
                        supportedTypes.add(ITableItemLabelProvider.class);
                        supportedTypes.add(IStructuredItemContentProvider.class);
                        supportedTypes.add(ITreeItemContentProvider.class);
                        supportedTypes.add(IItemPropertySource.class);
                        supportedTypes.add(IEditingDomainItemProvider.class);
                        supportedTypes.add(IItemLabelProvider.class);
                        }
                        

现在我们再运行这个插件,打开表视图,就能看到图 6。请注意,没有实现的 ITableItemLabelProvider 元素将在所有的字段中显示相同的文本。


图 6. 修改后的 Table 编辑器
修改后的 Table 编辑器 



回页首


在 Java 中操纵模型


生成的模型代码看起来就像是 Java 代码中增加了一些有用的东西。系统还提供了一种灵活的定制反射 API,对工具很有用。您也许注意到了,这就是 eGet() 和 eSet() 两个方法。在大多数情况下,我们并不需要关心它,所以我们还是看看我们感兴趣的东西:如何创建、保存和加载模型。让我们从头开始:加载 EMF 模型。

清单 7. 加载 Forum 
// Register the XMI resource factory for the .forummodel extension
                        Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
                        Map m = reg.getExtensionToFactoryMap();
                        m.put("forummodel", new XMIResourceFactoryImpl());
                        ResourceSet resSet=new ResourceSetImpl();
                        Resource res = resSet.getResource(URI.createURI("model/forum.forummodel"),true);
                        Forum forum = (Forum)res.getContents().get(0);
                        

清单 7 展示了如何给文件关联一个符合 XMI 格式的扩展名“forummodel”,然后用 EMF 的 ResourceSet 解析并加载 forum 模型。我们知道,Forum 是惟一的根元素,所以可以想象,res.getContents().get(0) 将返回一个且仅有一个 Forum 对象。如果情况不是这样,我们还可以从 getContents().iterator() 中取出一个 Iterator,然后分别检查每一个元素。

我们还可以换一种方法,创建一个新的 Forum,然后用程序组装起来,如清单 8 所示。

清单 8. 初始化 Forum 
// initialize model and dependencies
                        ForumPackageImpl.init();
                        // retrieve the default Forum factory singleton
                        ForumFactory factory = ForumFactory.eINSTANCE;
                        Forum forum = factory.createForum();
                        forum.setDescription("programmatic forum example");
                        Member adminMember = factory.createMember();
                        adminMember.setNickname("Administrator");
                        forum.getMembers().add( adminMember );
                        Topic noticeTopic = factory.createTopic();
                        noticeTopic.setTitle("Notices");
                        noticeTopic.setCategory(TopicCategory.ANNOUNCEMENT_LITERAL);
                        noticeTopic.setCreator(adminMember);
                        forum.getTopic().add( noticeTopic );
                        

在这个例子中,我们首先初始化包,然后创建 ForumFactory,用它生成所有的子对象。创建完毕之后,就可以像标准的 JavaBean 那样访问这些对象。然而,由于我们把 Topic 和 Memeber之间的 creator/topicsCreated 关系声明为双向,当我们调用 noticeTopic.setCreator(adminMember) 的时候, adminMember 的 topicsCreated 清单中就包括 noticeTopic 。

一旦我们创建并操纵了 EMF 模型,就很容易将其保存为我们选定的格式(参见清单 9)。

清单 9. 保存 Forum 
URI fileURI = URI.createFileURI("model/forum.ecore");
                        Resource resource = new XMIResourceFactoryImpl().createResource(fileURI);
                        resource.getContents().add( forum );
                        try {
                        resource.save(Collections.EMPTY_MAP);
                        } catch (IOException e) {
                        e.printStackTrace();
                        }
                        

在本例中,我们给 URI.createFileURI() 提供了希望保存成的文件名与目标格式。这个例子因为是保存为 XMI,所以使用了 XMIResourceFactoryImpl 。一旦创建完毕,所有的模型对象就如我们所愿的持久保存起来了。在这个例子中,除 Forum 之外的每一个对象都被另一个类包含,所以我们只需要对包含所有孩子的 root 增加这条命令即可。如果某些对象没有 包含 关系,那么也必须通过 resource.getContents().add() 显式地将它们加进去。否则,当您调用 resource.save() 时就会出现异常。


  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."

三、定义模型

  上面我们用三种形式描述了我们的概念模型,那么这三者有没有共同的模型概念,方便它们之间的转换呢(模型转换的知识在此暂时省略)?我们再回顾下前面的三种模型:

  • PurchaseOrder and Item 在UML和java中是类,但在XML schema中是复杂类型定义;
  • shipTo, billTo, productName, quantity 和 price在UML中是属性,在java中是get()/set()方法对,在XML schema中是内嵌的元素类型;
  • items在UML是类的关联或引用,在java中是get()方法,在XML schema中是另一种复杂类型的内嵌元素类型。

  可以看到这三者都用到了一种较高层的定义来分别表达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类来描述我们的模型,它们是:

  • EClass 用于表示模型中的类,它有一个name,0个或多个attributes,0个或多个references。
  • EAttribute 用于表示模型中的attribute,它有一个name和一个type。
  • EReference 用于表示两个类之间的关联,它有一个name,一个布尔值表示它是否是containment,还有一个引用类型(其它类)。
  • EDataType 用于表示attribute的类型,它可以是基本类型,例如int 、 float 或对象类型 java.util.Date等.

  我们还可以观察到,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"

  我们先来回顾前面的知识:

  • Ecore和它的XMI序列化格式是EMF的核心;
  • EMF模型至少可以通过三种形式得到(UML、XML schema、Java 接口);
  • 通过Ecore模型可以生成Java接口及其它形式的模型;

  其实,通过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包含下面三种行为:

  • eClass()返回对象的元对象(是一个EClass)。
  • eContainer() and eResource() 分别返回对象的包含对象及其资源。
  • eGet(), eSet(), eIsSet(), and eUnset() 提供了访问交互对象的API。

  然后,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)


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值