脱离Eclipse环境使用EMF
一旦熟练使用EMF后,有新项目我总是习惯于先使用工具构造出数据模型,然后让EMF帮我生成java代码。当模型需要修改时,也是用工具修改模型本身,然后让EMF把改动更新到java代码,从而保证模型与代码的同步。最近的一个基于struts的Web项目里我试验了脱离Eclipse运行环境使用EMF,发现比想象中的要更容易,以下是一些经验总结。
1. EMF可以脱离Eclipse环境使用,所以不论你要做的项目是纯swt应用程序、swing应用程序、基于web的应用程序甚至没有GUI的应用程序,这些非Eclipse插件的项目也都可以利用EMF生成的模型代码。不过EMF生成的.edit和.editor部分就难以利用了,因为.edit主要为jface的各种viewer提供支持,.editor则是基于Eclipse的一个编辑器。
2. 要脱离Eclipse独立使用EMF生成的代码,需要在项目里包含EMF的运行库,可以在EMF下载页面找到这个名为Standalone的下载项,下载以后要让里面的.jar文件都包含在项目classpath里。
3. 除添加这些库文件以外,还有少量初始化工作需要在你的应用程序启动时执行。在EMF-FAQ页面专门介绍了初始化的方法,基本上要做的就是在程序启动时执行下面两条命令:
XXXPackage xxxPackage = XXXPackage.eINSTANCE; Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xxx", new XMIResourceFactoryImpl());
Update(2009-2-19): 如果模型文件包含了除emf以外的名称空间声明(例如要打开一个.emx文件),则需要对它们进行注册,下面的代码注册了gmf1.0的名称空间:
((Delegator)EPackage.Registry.INSTANCE).put("http://www.eclipse.org/gmf/runtime/1.0.0/notation", NotationPackage.eINSTANCE);
4. 用EMF生成模型代码的步骤和平时基本没有区别,仍然是".ecore->.genmodel->java代码"。一般来说,直接在这个EMF项目里以EMF生成的java代码为基础继续开发就可以了,如果你用其他IDE则可以把这些java代码复制过去使用,只是注意一下每次修改ecore后要重复这个生成和复制代码的过程。
5. 关于多个模型和跨模型引用的处理。EMF的一个好处是可以把整个项目的模型保存为多个文件,并且允许文件之间的相互依赖。例如一个在线商店系统里,在设计的时候可以把模型分为用户信息、商品信息和订单信息三大部分,显然订单信息依赖用户和商品信息,所以对用户和商品的管理可以独立进行开发,一方面提高了模块化程度,也增加了重用的可能性,例如在另外一个项目里可以只做很小的修改甚至不做修改的重用用户管理部分或是商品管理部分。
EMF是通过ResourceSet处理多个模型之间的引用的,以上面的在线商店为例,当从一个文件(一般是xmi格式)里载入一个订单实例时,EMF并不会去实际读取存储用户或商品信息的文件,而是使用代理(Proxy)的方式生成一个替代品,当程序里需要得到订单对应的用户的详细信息时(例如执行了theOrder.getCustomer().getAddress()方法),才会实际读取用户信息文件,并根据订单的用户id找到对应的用户信息。
因此,当你的项目里有多个模型并且存在相互引用的这种情况时,建议维护一个全局的ResourceSet实例,可以在程序启动时创建:
ResourceSet resourceSet = new ResourceSetImpl();
对于普通应用程序,可以用单例模式(Singleton)维护ResourceSet实例;若在web应用程序里,也可以利用application维护它。在程序里,每载入一个模型都要把它加到这个ResourceSet实例上。具体方法如下:
//get global ResourceSet instance ResourceSet resourceSet = ; //file to load String filename = ;//e.g. "c:\\work\\test.xxx" URI fileURI = URI.createFileURI(filename); //create resource XMIResource resource = (XMIResource)resourceSet.createResource(fileURI); //load from file resource.load(null); //add resource to global ResourceSet instance resourceSet.getResources().add(resource); //get your model XXXModel xxxModel = (XXXModel) resource.getContents().get(0);
6. 其他常用方法,以下仍以在线商店里的模型(见此帖)为例:
创建一个新产品(Product),通过ShopFacotory的createProduct()方法而不要直接new ProductImpl():
Product p = ShopFactory.eINSTANCE.createProduct(); //或 Product p = (Product)ShopFactory.eINSTANCE.create(ShopPackage.Literals.Product);
把新创建的产品添加到商店(Shop):
Shop model = …; Product p = …; model.getProducts().add(p);
通过EMF反射机制得到Product类型的所有属性(非引用类型),任何一个EObject都可以通过这种方式得到其所有属性:
Product p = …; EClass eclass = p.eClass(); for (Iterator iterator = eclass.getEAllAttributes().iterator(); iterator.hasNext();) { EAttribute attr = (EAttribute)iterator.next(); //属性,如Product#name Object value = p.eGet(attr); //属性值,如“walkman” … //do whatever with the value }
通过EMF反射机制得到Category类型的所有引用,任何一个EObject都可以通过这种方式得到其所有引用:
Category category = …; EClass eclass = category.eClass(); for (Iterator iterator = eclass.getEAllReferences().iterator(); iterator.hasNext();) { EReference ref = (EReference)iterator.next(); //引用,如Category#products Object value = category.eGet(ref); //引用值,如EList<Product>(一对多的情况) … //do whatever with the value }