资讯专栏INFORMATION COLUMN

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

villainhr / 1869人阅读

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

持久化(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
  • 新云主机的配置

    摘要:目标很简单不被黑就行否则成了别人肉鸡太囧了说明主机日本系统首先登陆到主机上最开始的用户名是登陆后首先就是安装这样方便后续操作更改默认的端口关闭登陆将的数字修改为任意的位数字找到修改为重启添加新用户并使用其它用户名登陆添加用户并设置密码修改 目标很简单, 不被黑就行, 否则成了别人肉鸡太囧了 说明 主机: vultr日本系统: Ubuntu12.04 64bit 首先登陆到主机上, 最开...

    Yang_River 评论0 收藏0

发表评论

0条评论

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