资讯专栏INFORMATION COLUMN

EMF学习笔记(三)——使用EMF编程——持久化

villainhr / 2036人阅读

摘要:生成的包首次被访问时,在全局包注册表中自动地注册。然而,类似于资源工厂注册表,这种显式注册的过程仅当独立运行时被要求,在下运行时通过扩展指针来自动地完成。通过使用合适的资源工厂,就可以确定被产生的和被使用的持久化形式。

持久化(Persistence)

  EMF拥有一个强大的模型持久化框架。通过一个高度可定制资源实现(resource implementation)来支持XML序列化
  EMF不要求持久化是基于XML或者是基于流(stream)。这个框架提供的API灵活到足以支持任何种类的存储,甚至在不同类型的存储中保存对象,其中包含引用。

持久化框架的概述

  EMF中持久化的基本单元叫做资源(resource),它是一个或多个与其内容一起持久化的对象容器
  EMF对象由Resource接口来进行持久化,方法是将对象添加到资源的内容列表中,然后调用save()方法,例子如下:

PurchaseOrder po = ...
Resource resource = ...
resource.getContents().add(po);
resource.save(null);

  这个例子中,save()方法的参数,一个Map,如果指定了保存操作的选项,那么这个参数将非空(non-null)。EMF的XML资源支持的选项详细介绍在15.3.3。
  持久化的逆操作,从持久化形式中在内存重建活动(actiive)的对象。使用load()方法从资源的内容列表中访问对象:

Resource resource = ...
resource.load(null);
PurchaseOrder po = (PurchaseOrder)resource.getContents().get(0);

  Resource接口的详细说明在15.2.3。
  上面的代码带来了以下问题:XML序列化写到哪里?从哪里读入?开始的时候如何获取资源?
  为了管理不同资源中对象之间的引用,EMF持久化框架包含了另一种接口,称作ResourceSet,作用相当于资源的容器getResources()方法返回的是资源的列表,以一个集合(set)的形式。
  通常而言,资源是由资源集合创建的,或者是加载的。如下:

ResourceSet resourceSet = new ResourceSetImpl();
URI uri = URI.createURI("file:/c:/data/out.epo2");
Resource resource = resourceSet.createResource(uri);

  这里我们创建了一个资源集合,调用createResource()来创建特定的资源。这个方法的参数是URI,它被用来指定资源,在资源集合中识别出这个资源。然后,我们调用save()load()方法,资源可以使用这个URI来决定写出读入的位置。这里的URI是文件模式(file-scheme),其他支持的URI类型详见15.2.1。
  EMF不会向资源集合提供工厂。由用户来决定实例化合适的实现。ResourceSetImpl是一个功能灵活的实现,通常应该是足够的。如果有必要的话,它也可以被扩展以及定制。
  传给createResource()URI还有另外的用途。资源集合维持(maintain)资源工厂注册表。为了创建一个资源,需要查询(consult)它的注册表,以根据特定的URI来获取一个合适的工厂。这个工厂实际上创建了对应的资源,并且决定对象如何被持久化
  注册表根据模式(scheme)或者文件扩展名(file extension)来选择资源工厂。我们可以对.epo2文件扩展名注册EMF的默认XMI资源工厂:

resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().
put("epo2", new XMIResourceFactoryImpl());

  当使用Eclipse里生成的模型时,这种注册不是必要的,因为相似的注册会通过模型插件(model plug-in)清单文件(manifest file)中指定的扩展名来自动地执行。然而,当独立运行时,注册必须被明确地执行。否则,createResource()将无法创建资源并且会返回null。详见15.2.4的Resource的嵌套工厂和Registry接口以及15.2.5的ResourceSet
  现在已经有了对持久化框架的足够理解,来编写简单但有效的程序来保存采购清单:

PurchaseOrder po = createPurchaseOrder();
ResourceSet resourceSet = new ResourceSetImpl();
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().
  put("epo2", new XMIResourceFactoryImpl());
URI uri = URI.createURI("file:/c:/data/out.epo2");
Resource resource = resourceSet.createResource(uri);
resource.getContents().add(po);
try
{
    resource.save(null);
    System.out.println("saved");
}
    catch (IOException e)
{
    System.out.println("failed to write " + uri);
}

  这里的createPurchaseOrder()返回的是我们想要持久化的采购清单。我们可以设想它创建了PurchaseOrder实例并且向其中加入一组Items,在out.epo2的最终的序列化结果如下:





  从其持久化形式中加载采购清单的程序如下:

EPO2Package epo2Package = EPO2Package.eINSTANCE;
ResourceSet resourceSet = new ResourceSetImpl();
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().
  put("epo2", new XMIResourceFactoryImpl());
URI uri = URI.createURI("file:/c:/data/out.epo2");
Resource resource = resourceSet.createResource(uri);
try
{
    resource.load(null);
    PurchaseOrder po = (PurchaseOrder)resource.getContents().get(0);
    System.out.println("loaded: " + po);
}
catch (IOException e)
{
    System.out.println("failed to read " + uri);
}

  程序的第一行,访问采购清单模型的,另一种作用是注册包。生成的包首次被访问时,在全局包注册表中自动地注册。资源取决于能够找到注册的包,以反射式地实例化其描述的。然而,类似于资源工厂注册表,这种显式(explicit)注册的过程仅当独立运行时被要求,在Eclipse下运行时通过扩展指针(extension point)来自动地完成。

EMF持久化API

  EMF持久化框架以个基础接口为中心:Resource,ResourceSet, Resource.Factory, URIConverter

URI

  URI是一个字符串,包含三个基本的部分:schemescheme-specific部分、可选片段。

URIConverter

  这个接口用来规范化 URIs:将一个URI转换为另一个访问资源的URI。

Resource

  前面已经说过,EMF中持久化的基本单元叫做资源(resource)。尽管资源直接关系EObjects,但是不像从第5章描述的元模型中生成的各种Ecore接口,Resource接口事实上不会来描述模型化的类。然而,它表现的像重要的方式之一:

public interface Resource extends Notifier
{
    int RESOURCE__RESOURCE_SET = 0;
    int RESOURCE__URI = 1;
    int RESOURCE__CONTENTS = 2;
    ...
    ResourceSet getResourceSet();
    URI getURI();
    void setURI(URI uri);
    EList getContents();
    ...
}

  首先,资源是通知器(notifier):多个适配器(adapter)可以被附加于一个资源,当资源改变时,将会给适配器们发送通知。可以看到,接口定义了“虚拟特征(virtual features)”,比如resourceSetURI内容,它们都被使用于通知中。
  其次,getContents()返回的列表,描绘了资源的内容,表现的十分像一个模型化双向的引用,相反的引用由EObject中的eResource()方法实现。需要注意的是,这种关系实际上并不是真正的模式化,而是概念上的,情况如下图:

  值得强调的是,当内容虚拟特征(contents virtual feature)期望getContents()访问器时,其相反的访问器的名字,eResource(),毫无疑问是非常规的(unconventional)。这是因为它反而遵循EObject中所有其他方法使用的模式,被选择以避免与在模型化子类中生成的访问器的名称相冲突。还值得注意的是,对于eResource()没有相应的设置(set)方法,所以这个虚拟特征只能从Resource end来设置,通过将对象加入到内容列表。
  在URI这一节,资源默认地包含其内容列表中每个对象所包含的整个对象树。这影响到eResource()的返回值。
  内容虚拟特征表现的像一个普通双向特征,相反的是multiplicity-one。这就是说,如果一个对象已经存在于一个资源的内容中,将其添加到另一个则会自动地从第一个中移除
  此外,内容虚拟特征展示了遏制(contaiment)语义,但仅涉及不是代理解析(proxy resolving)的模型化的遏制引用。换句话说,如果一个对象已经存在于任一模式化的非代理解析的遏制引用,把它添加到一个资源的内容中则会自动的将其从它的容器中移除。回顾第10章中遏制语义默认不是代理解析。因此,它就表现出默认阻止跨资源(cross-resource)遏制:一个对象只可以被直接放置在一个容器对象中,或者直接放置在一个资源内容中,而不能两者都放置。
  “遏制代理”详见13.8.2。
  一个资源通常被一个资源集合所包含,getResourceSet()来获取。资源在集合中通过URI来唯一标识。通常在资源创建时指定URI,但是只能分别通过getURI()setURI访问设置URI通常决定资源的持久化形式将要存贮的位置。

  save()load()方法将资源从活动的内存形式复制到持久化存储,反之亦然。每个都有两种形式:

void save(Map options) throws IOException;
void load(Map options) throws IOException;

void save(OutputStream outputStream, Map options) throws IOException;
void load(InputStream inputStream, Map options) throws IOException;

  EMF为资源提供了一个基础类,ResourceImpl,它通过从资源集合中获取一个URIConverter来实现每个单参数的方法,这个URIConverter用来打开资源的URI的输出或输入流,还能将流传到对应的双参数方法。因此,特定子类的作用是提供有意义的,特定于存储(storage-specific)保存加载实现。
  通过调用unload()方法可以卸载资源。卸载会将资源中所有的对象变为代理(proxy),这样的结果是,下次这些对象中之一从其他资源通过跨引用(cross-reference)被访问时,资源的请求重新加载(通过EcoreUtil.resolve())。这就可以把一个资源更新,比如自上次加载以来,底层文件( underlying file )发生了变化。isLoaded() 方法表明资源当前是否被加载。
  资源可以通过isModified(),来跟踪对其完整内容的任何更改,并且返回自从最近保存和加载后的变更。默认情况下,这个特性是无效的,因为它的实现昂贵而且对撤销命令是无效的,由于撤销看起来像变更。适当时候,setTrackingModification() 可以使其生效

  URI Fragments,是资源基于ResourceImpl使用类似XPath的方式来定位对象。Resource接口定义getEObject() 方法,可以根据给出的段路径(fragment path)来检索对象:

Resource resource = ...
Item item = (Item)resource.getEObject("//@orders.0/@items.2");

  相反,也可以获取段路径:

Item item = ...
String fragment = resource.getURIFragment(item);
Resource.Factory和Resource.Factory.Registry

  嵌套在Resource接口中的是Factory接口,它定义了资源如何被创建:

public interface Resource extends Notifier
{
    ...
    interface Factory
    {
        Resource createResource(URI uri);
        interface Descriptor
        {
            Factory createFactory();
        }
    
        interface Registry
        {
            Factory getFactory(URI uri);
            Map getProtocolToFactoryMap();
            Map getExtensionToFactoryMap();
            String DEFAULT_EXTENSION = "*";
            Registry INSTANCE = new ResourceFactoryRegistryImpl();
        }
    }
}

  正如这个接口建议的那样,资源工厂只是简单地知道如何去创建资源的一种类型。通过使用合适的资源工厂,就可以确定被产生的和被使用的持久化形式。
  嵌套在Factory中的是Registry接口,它基于特定URI,提供了选择合适资源工厂的机制。资源工厂注册表维护着两个可以注册资源工厂的map。这些map被getFactory方法咨询,以获取适合于给定URI的工厂。
  ResourceFactoryRegistryImpl(),按照以下步骤来获取资源工厂:

检查getProtocolToFactoryMap()返回的map,使用URI模式作为关键字。

如果没有找到任何东西,检查getExtensionToFactoryMap()返回的map,使用URI文件扩展名作为关键字。

如果仍然没有找到,就检查getExtensionToFactoryMap()返回的map默认值,使用DEFAULT_EXTENSION*作为关键字。

如果没有找到相应的匹配,调用受保护的(protected)delegatedGetFactory()方法。默认情况下,它没有做任何事,但可以被重写(override)

  资源集合维护(maintain)着资源工厂注册表,当其需要创建资源时,就要咨询(consult)来获取工厂。此外,有一个可用的全局注册表Resource.Factory.Registry.INSTANCE,它可以注册系统中的所有资源集合。默认资源集合中注册表的实现重写了delegatedGetFactory() 来委托给全局注册表。所以仅当根据URI的模式,在局部没有找到合适的工厂时,才会根据文件扩展名(file extension)来咨询全局注册表。
  在持久化框架概述中说道,在Eclipse环境下运行时,资源工厂可以通过Eclipse的扩展指针机制,来被自动地注册。这样的注册是在全局注册表中完成的。
  org.eclipse.emf.ecore.xmi 插件清单文件包含以下扩展:


    

  它默认注册了XMIResoureFactoryImpl资源工厂实现。这就是为什么EMF的XMI序列化作为默认持久化格式,以及为什么在Eclipse下运行时没有要求显式的注册。
  如果你创建了自己的资源实现,你需要为其创建工厂然后进行注册,以相似的方式,在你自己的插件(plug-in)中。例如,设想你已经创建了自己的资源实现类,EPO2ResourceImpl,你想要使用它来持久化.epo2资源。为此,需要创建资源工厂:

public class EPO2ResourceFactoryImpl implements Resource.Factory
{
    public Resource createResource(URI uri)
    {
        return new EPO2ResourceImpl(uri);
    }
}

  然后,你可以向plugin.xml文件中添加以下扩展:


    

  事实上,这就是在为生成器中的包选择“资源类型(Resource Type)”时发生的事情。从以XML Schema描述的模型开始尤为重要,因为默认XML资源将不能符合模式的序列化。如果注册不能完成或者你试图加载没有合适文件扩展名(file extension)的资源,Resource.load()将会抛出ClassNotFoundException。对于独立的应用程序,需要在代码进行以下等价的注册:

Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(
    "epo2", new EPO2ResourceFactoryImpl());

  尽管等价,这两个资源工厂注册表(registration)还是有细微差别的。后者,显式注册,工厂的实例被创建并且被注册表中的一个map直接注册。然而,前者,基于扩展名(extension-based)的注册表,实际注册的是描述符(Descriptor)
  描述符,另一个嵌套在Resource.Factory中的接口,在创建资源工厂时提供了额外的间接层(indirection)级别(level)。在完成之前所述的4步枚举型之后,如果注册获取了描述符而不是资源工厂,它将调用createFactory()并返回结果。这项功能使EMF能够推迟加载特定的资源工厂直到这个类被真正需要时。这对基于扩展名的注册表很重要,其发生在平台启动时。

ResourceSet

  ResourceSet是持久化API中最高级别的接口,作用相当于资源的容器,以便允许它们之间的引用。它提供了访问这些资源的途径,从模型或者一组相关联的模型中,定义了对象的上下文(context)。此外,它维护这URI转换器,资源工厂注册(resource factory registry),以及它自己使用的和用于资源的包注册(package registry)
  类似于ResourceResourceSet并不表示模型化,但是它实现Notifier接口:

public interface ResourceSet extends Notifier
{
    int RESOURCE_SET__RESOURCES = 0;
    EList getResources();
    ...
}

  由getResource()返回的资源的列表,构成了它仅有的虚拟特征,表现得像个遏制引用(containment reference)。换句话说,资源每次只能在一个资源集合中。
  ResourceSet为创建新资源提供了用户级别(user-level)的接口。在URI那一节,我们使用最简单的方法来实现这个目的,createResource(),它授权(delegate)给集合的资源工厂注册返回的资源工厂。回顾这个注册,是getResourceFactoryRegistry()返回的,当它不能定位适当的资源工厂时,就授权给全局资源工厂。createResource()方法在内存中创建资源,并且将其添加到资源集合中,毫不费力就能读入写出持久化表示。在它盲目地创建资源前,也不会检查集合是否已经包含给出的URI的资源,这就有可能使集合处于无效状态。
  一个更强大的方法,getResource(),首先规范化给出的URI,检查资源集合中是否有资源拥有与其匹配的规范化URI,如果有,返回这个资源。如果这个资源没有加载,它将会被要求加载(demand-loaded),如果此方法的第二个布尔参数为真的话。如果资源没有找到并且第二个参数为真,资源将会被要求创建(demand-created)和要求加载。使用getURIConverter()返回的URI 转换器来规范化URI,使用getLoadOptions()返回的map中指定的选项来完成要求的加载。
  注意的是,资源的持久化形式不会被要求创建。如果因为持久化形式无法找到而导致要求的加载失败,getResource()会抛出异常
  所以,在持久化框架的概述中最后的例子可以被简化。在设置资源集合和创建URI之后,两行就足以创建资源,加载,并且访问其中的对象:

...
Resource resource = resourceSet.getResource(uri, true);
PurchaseOrder po = (PurchaseOrder)resource.getContents().get(0);
System.out.println("loaded: " + po);

  ResourceSet中的另一种简便方法,getEObject(),允许我们进一步简化,替换获取资源和使用单个声明检索其中对象的两行:

...
PurchaseOrder po =
    (PurchaseOrder)resourceSet.getEObject(uri.appendFragment("/"),true);
...

  getEObject()方法简单地调用getResource()来获取资源,必要的话进行要求的创建和要求的加载,然后使用指定URI的分段(fragment)来定位和返回资源里面的对象。
  包注册,提供了一种手段来定位描述任一特殊包的元数据。有个全局包注册,EPackage.Registry.INSTANCE,在其中生成的包会被自动地注册。在其他情况下,特定语境(context-specific)的包注册会更合适些。
  事实上,正是资源集合提供了语境(context)ResourceSet定义了getPackageRegistry()方法,其返回局部的(local)注册。这个局部注册只在局部不能找到包时,授权给全局注册。因此,加载到给定资源集合的资源已经检索的包,可以在不污染全局注册的情况下被注册。甚至有可能,针对同一个命名空间URI,仅仅通过局部注册一个不同的包,来重写一个全局注册表。
  资源集合的主要目的是支持不同资源中对象之间的引用。特别的事,它们对要求的加载(demand-loading)代理解析(proxy resolution)至关重要。例如,考虑资源如下序列化:



    
        
    

  可以看出资源中的Supplier包含一个PurchaseOrder引用了另一个资源中Supplier中序列化的PurchaseOrderhref属性表示的是跨文档(cross-document)的引用,属性的值是标识其他资源中引用的URI*。
  当加载这个资源时,代理被创建作为previousOrder引用的目标,指定对象的URI(例如:platform:/resource/project/sample.epo2#//@orders.1)。如果想要访问采购清单的上个清单,可以调用 getPreviousOrder()EcoreUtil.resolve()被调用以便解析代理(resolve proxy)

XML资源 EMF资源和资源工厂实现 性能考量 主动对象的定制存储

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67579.html

相关文章

  • Eclipse Modeling Framework, 2nd Edition. (EMF)学习笔记

    摘要:定义模型元模型用于表示中模型的模型称为。用于表示的类型,它可以是基本类型,例如或对象类型等。此外,因为是货物的容器并会在其中将货物作为孩子序列化,所以需要标识出。 EMF介绍 为了理解EMF究竟是什么,你只需要知道一件事:模型(model)是什么?模型的目的是什么? EMF不要求全新的方法论亦或是任何复杂的建模工具。只需要从Eclipse的Java开发工具着手开始。 EMF将建模概念...

    yagami 评论0 收藏0
  • Eclipse Modeling Framework, 2nd Edition. (EMF)学习笔记

    摘要:定义模型元模型用于表示中模型的模型称为。用于表示的类型,它可以是基本类型,例如或对象类型等。此外,因为是货物的容器并会在其中将货物作为孩子序列化,所以需要标识出。 EMF介绍 为了理解EMF究竟是什么,你只需要知道一件事:模型(model)是什么?模型的目的是什么? EMF不要求全新的方法论亦或是任何复杂的建模工具。只需要从Eclipse的Java开发工具着手开始。 EMF将建模概念...

    yacheng 评论0 收藏0
  • EMF学习笔记)——使用EMF编程——久化

    摘要:生成的包首次被访问时,在全局包注册表中自动地注册。然而,类似于资源工厂注册表,这种显式注册的过程仅当独立运行时被要求,在下运行时通过扩展指针来自动地完成。通过使用合适的资源工厂,就可以确定被产生的和被使用的持久化形式。 持久化(Persistence)   EMF拥有一个强大的模型持久化框架。通过一个高度可定制资源实现(resource implementation)来支持XML序列化...

    helloworldcoding 评论0 收藏0
  • EMF学习笔记(二)——使用EMF编程——开发元数据

    摘要:使用元数据包中包含了中每一个被建模类对应的接口。任何对象的元数据是使用的实现来表示的。加载模型的序列化形式是个在运行期间获取元数据的有效方法。反射提供一个反射式,可以检查对象的元数据以及一般地访问和操纵数据。 使用元数据   Java包org.eclipse.emf.ecore中包含了Ecore中每一个被建模类对应的接口。任何EMF对象的元数据是使用Ecore的实现(implement...

    Jiavan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<