浅析Eclipse建模框架(EMF)及其动态能力

如果给定一个模型,那么EMF能够自动生成Java源代码-该源码允许你创建、查询、更新、串行化、反串行化、检验和追踪你的模型实例的变化。EMF提供了一组有效的反射API并且允许你用动态的(非生成的)模型进行工作。

一、引言

Eclipse建模框架(EMF)是一个Java开源框架与代码生成工具-用于基于结构化的模型来构建工具和其它应用程序。在Eclipse平台在用户界面和文件级上提供一个强有力的集成框架的同时,EMF加强了这种能力来实现工具和应用程序之间良好粒度的数据分享。

类似于其它的Java绑定框架,例如JAXB或XMLBeans,给定一个模型后,EMF就能够生成Java源代码-它允许你创建、查询、更新、反串行化以及串行化你的模型的实例。尽管多数Java绑定框架仅支持一个模型类,例如XML模式,而EMF支持从XML模式,UML类图(Rational Rose或UML2)以及被注解的Java接口中生成代码。除了模型代码,EMF还能生成一个完整的应用程序-它包括一个可定制的编辑器。

EMF生成的代码有一个内建的改变通知机制并且支持跨文档参考。EMF提供一个反射API以存取你的模型实例并且允许你动态地创建模型。EMF支持模型约束校验。EMF提供强有力的代码生成工具来支持模型的重新生成和使用用户书写的代码进行合并。

在本文中,我们将解释什么是EMF,并分析其基本框架。

EMF最开始是一个对象管理组的(OMG)元对象设备(MOF)说明书的实现-它为面向对象的分析和设计提供一个标准化的元模型。在很长一段时间以来,EMF被用于实现大量的工具并且因此演变为一个有效的MOF API的一个核心子集的Java实现。

在EMF中的类MOF核心元模型(一个模型的模型)被称作Ecore。在对当前的MOF 2.0的实现中,有一个类似的MOF模型的子集,称作Essential MOF(EMOF),它现在已经被独立出来。在Ecore和EMOF之间主要存在上些小的特别是命名上的区别,因此EMF能透明地读和写串行化的EMOF,从而允许工具间数据的标准交换。

今天EMF已被广泛应用。例如,EMF被用于实现开源XML模式Infoset模型(XSD),服务数据对象(SDO),UML2以及Eclipse上的Web工具平台(WTP)工程。另外,EMF也被使用在商业化的产品中,例如Omondo EclipseUML以及IBM Rational和WebSphere产品等。

二、Ecore和反射API

EMF中的一个关键接口是Eobject,它在概念上等价于java.lang.Object。所有的建模对象,无论是生成的与否,为了提供以下几个重要特征,都要实现这个接口:

·类似Java的Object.getClass(),通过使用eClass()方法,你能检索实例的元数据,也就是它的Eclass。

·在任何EMF建模的对象上,你都能使用反射API(eGet(),eSet())来存取它的数据。这在概念上等同于Java的java.lang.reflect.Method.invoke()方法,尽管效率更高些。

·从任何实例对象,你都可以通过使用eContainer()方法得到它的容器(parent)。

·EObject也扩展了Notifier,这允许你监视对象的数据的所有变化。

为找到更多关于EMF和Ecore,请读在线概述或购买Eclipse建模框架(EMF)。EMF网站提供了若干文件来描述怎么使用EMF来从一个XML模式或UML图表生成Java代码。

下面我将描述一个示例,它使用Ecore来创建一个简单公司模型,然后使用动态的EMF来创建,串行化和反串行化这个模型的实例。如果你想继续读下去并且你已经是一个Eclipse用户,请下载和安装EMF 2.1 SDK或任何可用的更新的版本,在EMF下载站点。如果不那样,你还可以下载独立包,它包括EMF jar文件,它没有任何对Eclipse的依赖性并且能被使用于一个独立的应用程序。

三、使用动态EMF能力

一般地,如果你在开发期间创建了模型,那么典型情况下,你最好生成Java代码,因为在这种情况中你的应用程序会使用较少的内存并且提供更快的数据存取(或是使用生成的API或是使用反射API)。尽管生成Java代码满足了大多数应用程序的需要,但是情况并不总是如此。你可能需要处理数据,而不需要使用生成的实现类。例如,你可能不知道在开发时间你将要处理的数据的模型,这就使得生成的Java代码成为一个可怜的选项。

动态的(也就是非生成的)类可以在运行时刻用几种方法来创建。让我们先开始使用Ecore API以编程地方式来创建一个公司模型。公司模型用于描述一个公司,它有一个名称和部门。每个部门由一个数字来唯一标志出并且它还有雇员,每个雇员有一个名称。在下面的代码显示出一个相应于该模型的Ecore元模型。

EcoreFactory ecoreFactory = EcoreFactory.eINSTANCE;EcorePackage ecorePackage = EcorePackage.eINSTANCE;//创建一Company类EClass companyClass = ecoreFactory.createEClass();companyClass.setName("Company");//创建公司名EAttribute companyName = ecoreFactory.createEAttribute();companyName.setName("name");companyName.setEType(ecorePackage.getEString());companyClass.getEStructuralFeatures().add(companyName);//创建一Employee类EClass employeeClass = ecoreFactory.createEClass();employeeClass.setName("Employee");//在Employee类上添加一个名字属性EAttribute employeeName = ecoreFactory.createEAttribute();employeeName.setName("name");employeeName.setEType(ecorePackage.getEString());employeeClass.getEStructuralFeatures().add(employeeName);//创建一Department类EClass departmentClass = ecoreFactory.createEClass();departmentClass.setName("Department");//添加department标志数字EAttribute departmentNumber = ecoreFactory.createEAttribute();departmentNumber.setName("number");departmentNumber.setEType(ecorePackage.getEInt());departmentClass.getEStructuralFeatures().add(departmentNumber);//department类能够包含到一个或多个employee的参考EReference departmentEmployees = ecoreFactory.createEReference();departmentEmployees.setName("employees");departmentEmployees.setEType(employeeClass);//指定它可能是一个或多个employeedepartmentEmployees.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);departmentEmployees.setContainment(true);departmentClass.getEStructuralFeatures().add(departmentEmployees);//company能够包含到一个或多个departments的参考EReference companyDepartments = ecoreFactory.createEReference();companyDepartments.setName("department");companyDepartments.setEType(departmentClass);companyDepartments.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);companyDepartments.setContainment(true);companyClass.getEStructuralFeatures().add(companyDepartments);//创建一个包-描述companyEPackage companyPackage = ecoreFactory.createEPackage();companyPackage.setName("company");companyPackage.setNsPrefix("company");companyPackage.setNsURI("http:///com.example.company.ecore");companyPackage.getEClassifiers().add(employeeClass);companyPackage.getEClassifiers().add(departmentClass);companyPackage.getEClassifiers().add(companyClass);通过使用反射API,你能创建并且初始化一个你的模型的实例://得到company工厂EFactory companyFactory = companyPackage.getEFactoryInstance();//使用工厂来创建company类的实例并且//设置company名字EObject company = companyFactory.create(companyClass);company.eSet(companyName, "MyCompany");//创建一个employee类的实例EObject employee = companyFactory.create(employeeClass);//使用反射API初始化employee的名字employee.eSet(employeeName, "John");//创建一个department类的实例EObject department = companyFactory.create(departmentClass);department.eSet(departmentNumber, new Integer(123));//添加"John"到department((List)department.eGet(departmentEmployees)).add(employee);//添加department到company((List)company.eGet(companyDepartments)).add(department);

四、数据的串行化和反串行化

为了串行化你的模型实例,你需要把一个你的实例模型的根对象放置到一个资源中。EMForg.eclipse.emf.ecore.resource.Resource接口描述了一个物理的存储位置(例如文件或URL)并且提供方法以串行化和装载数据。每一种资源都被存储在一个ResourceSet中-它代表了一个资源集合-这些资源被一起创建和加载并允许在它们当中进行参考引用。特别地,一个ResourceSet负责跟踪哪些资源已被装载并且保证这个ResourceSet中的资源不会被重复装载。

因为EMF能够处理多重模型源,例如XML模式,所以指定使用哪些资源来实现(反)串行化你的数据也是很重要的。通常,当你调用ResourceSet.createResource(URI)方法时,它查询Resource.Factory.Registry来查找一个工厂-该工厂是为该URI而注册的并且使用它来创建一个适当的资源实现。因此,在你(反)串行化你的数据以前,请确保你已注册了适当的资源工厂实现。EMF提供若干Resource.Factory实现:

·对于XML数据,使用org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl。

·对于XMI数据,使用org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl。

·对于Ecore模型,使用org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl。

你的工具箱中有了这些EMF资源后,你就能使用下面的代码来串行化你的数据:

//创建资源集和资源ResourceSet resourceSet = new ResourceSetImpl();//注册XML资源工厂resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xmi",new XMIResourceFactoryImpl());Resource resource = resourceSet.createResource(URI.createFileURI("c:/temp/company.xmi"));//添加根对象到资源resource.getContents().add(company);//串行化资源-你还能指定串行化//选项,它定义在org.eclipse.emf.ecore.xmi.XMIResource中resource.save(null);company.xmi被串行化后的形式如下:xmlns:company="http:///com.example.company.ecore" name="MyCompany">

在反串行化过程中,XML数据的命名空间URI被用于定位所需要的Ecore包(它用于描述你的实例文档的模型)。因此,在你尝试装载任何模型以前,请确保你已经为你的文档将要使用的每个Ecore包注册了命名空间URI:

//在本地资源注册表中注册包

resourceSet.getPackageRegistry().put(companyPackage.getNsURI(), companyPackage);

//加载资源

resource.load(null);

注意到局部的和全局的包(EPackage.Registry.INSTANCE)以及资源工厂(Resource.Factory.Registry.INSTANCE)的注册差别也是很重要的。全局注册是静态的,因此任何应用程序在JVM生存期都能存取全局注册并且可能覆盖它。为确保你的注册不会覆盖全局注册并且反过来也如此,典型地,你最好使用局部资源集合注册。

五、由XML模式生成动态的Ecore

如前所提及,如果你的模型是一个XML模式但是你没有选择生成Java类,那么,你可以通过使用XSDEcoreBuilder来动态地创建一个Ecore模型。这个示例使用了ipo.xsd:

XSDEcoreBuilder xsdEcoreBuilder = new XSDEcoreBuilder();ResourceSet resourceSet = new ResourceSetImpl();Collection eCorePackages=xsdEcoreBuilder.generate(URI.createFileURI("c:/temp/ipo.xsd"));

这个generate方法返回为这个模式中的每个URI生成的Ecore包。如果该模式导入了其它命名空间,那么将有多个Ecore包被返回。每个包是被注册到本地的资源集中-用于实现模式转换。因此,如果你使用同样的资源集来装载你的实例XML文档的话,你就不需要自己注册包。

因为XML模式包括更多概念而不仅仅是Ecore,例如通配符等,所以EMF使用Ecore EAnnotations来记录到XML模式的映射。在数据(反)串行化期间,EMF需要处理这些注解。为了确保这些注解在(反)串行化期间被加以考虑,你必须使用XMLResource.ExtendedMetaData选项:

HashMap ptions = new HashMap();options.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);//请参考http://www.w3.org/TR/2004/PER-xmlschema-0-20040318/#ipo.xmlResource resource = resourceSet.createResource(URI.createFileURI("c:/temp/ipo.xml"));resource.load(options);

EMF 2.1还增加了一项新功能-它允许你在加载一个包含一个xsi:schemaLocation或xsi:noNamespaceSchemaLocation属性的XML文档时,不断地把模式转换成Ecore。同时,它也允许你加载一个没有与之相关联的模式的XML文档。为了使用这一功能,你需要注册 org.eclipse.emf.ecore.xmi.impl.GenericXMLResourceFactoryImpl:

resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xml",new GenericXMLResourceFactoryImpl());

六、小结

本文向你简短介绍了EMF,并解释了几个核心的EMF概念。同时,对于如何利用模式EMF的动态能力提供了相关示例。

那么威尼斯就是一艘轻盈和流动的舟船,

浅析Eclipse建模框架(EMF)及其动态能力

相关文章:

你感兴趣的文章:

标签云: