一旦熟練使用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 instanceResourceSet resourceSet = ;//file to loadString filename = ;//e.g. "c:\\work\\test.xxx"URI fileURI = URI.createFileURI(filename);//create resourceXMIResource resource = (XMIResource)resourceSet.createResource(fileURI);//load from fileresource.load(null);//add resource to global ResourceSet instanceresourceSet.getResources().add(resource);//get your modelXXXModel 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}