EMF 是 Eclipse 平臺的主要部分,并且是一些相關(guān)技術(shù)和框架的基礎(chǔ),比如 Eclipse VisualEditor、SDO、XSD 和 UML — 其中的許多技術(shù)都被集成到 Rational? Application Developer 和WebSphere? Business Modeler 等 IBM? 平臺中?,F(xiàn)在,EMF 已經(jīng)吸收了許多 Java技術(shù)特性,比如枚舉類型、注釋和泛型。如果您還不熟悉 EMF,請參閱 參考資料 中的文章獲得入門知識。
在大多數(shù)文檔和教程中,EMF 都被用于建模數(shù)據(jù) 和接口(比如 EMF 發(fā)行文檔中的 Library 和 Books),而不用于建模行為。當(dāng)然,還有一些針對數(shù)據(jù)對象生成的默認(rèn)方法實現(xiàn),但這些實現(xiàn)涉及到模型元素之間的關(guān)系。而且,將 EMF 用作 “元模型” 的經(jīng)過歸檔的示例非常少 —除了 Eclipse Foundation 文章 “Modeling Rule-Based Systems with EMF”(參見 參考資料)— 但是這個示例并沒有展示如何擴展 Ecore 元模型。
最后,使用和擴展 EMF JET 模板的過程也沒有被很好地進行歸檔。此外,JET Editor 項目最近已經(jīng)遷移到另一個Eclipse 項目(M2T)上。本文旨在澄清這些問題,并使您能夠在 EMF 上下文中使用動態(tài)模板實現(xiàn)更多的功能。因此,本文假設(shè)您對 EMF有基本的了解。
Ecore 元模型是一個強大的工具,可用于設(shè)計模型驅(qū)動架構(gòu)(Model-Driven Architecture,MDA),后者可以作為軟件開發(fā)的起點。通常情況下,我們定義應(yīng)用程序范圍內(nèi)的對象(EClass
類型)、對象屬性以及它們之間的關(guān)系。我們還使用 EOperation
模型元素定義屬于這些對象的特定操作。默認(rèn)情況下,EMF 將會為這些操作生成骨架 或方法簽名,但是我們必須返回并實現(xiàn)這些操作,常常要反復(fù)地重新編寫類似的邏輯。
但是,如果我們想在模型中指定某種任意的實現(xiàn)行為該怎么辦呢?一種方法是添加基于文本的注釋(EAnnotation
類型),以建模對象并在代碼生成期間解釋模板中的這些注釋。關(guān)于這種方法的出色示例,可以查閱 Eclipse Foundation 文章 “Implementing Model Integrity in EMF with MDT OCL”(參見 參考資料)。但是,正如這篇文章中所描述的,我們的目標(biāo)不是驗證模型元素,而是對實現(xiàn)本身進行建模,以使任何具體的模型能夠重用這些元模型元素。為此,我們需要擴展 Ecore 元模型。
本文附帶了一個高度簡化的用來擴展 Ecore 的編程式模型。它不是一個完整或連貫的元模型或框架;嚴(yán)格來講,它是一個元素的原型集合,用于演示使用 EMF 對代碼實現(xiàn)進行元建模的能力。圖 1 顯示了我們的擴展元模型示例 EcoreX 的快照,下面是每個元素的簡短描述。
EPackageX
擴展 EPackage
EPackage
的一個簡單 “標(biāo)記” 擴展,沒有任何附加屬性。這個元素是必需的,因為在默認(rèn)情況下,元素 EPackage
EMF 編輯器插件不允許將EClass
的子類作為子元素添加(參閱下面的 EClassX
)。通過提供一個可擴展EPackage
的模型元素,代碼將會自動生成,從而允許將一個 EClassX
子元素添加到 EPackageX
中。EClassX
擴展 EClass
EClass
的一個簡單標(biāo)記擴展,沒有任何附加屬性。與上面的元素類似,此元素也是必需的,因為在默認(rèn)情況下,EClass
的編輯器插件不允許添加 EOperation
的子類 — 這正是我們要在本文中實現(xiàn)的目標(biāo)。EOperationImpl
擴展 EOperation
EOperation
元素中沒有的屬性。下面描述的所有其他元素都屬于EOperationImpl
并用于構(gòu)成編程式實現(xiàn)。例如,EOperationImpl
包含變量和語句,可以返回一個引用或值。LocalVariable
擴展 ETypedElement
LocalVariable
是一個本地變量。變量包含一個名稱和一個 Java 類型(比如 String
、Integer
、Object
),而且由于這些屬性已經(jīng)存在于其超級超類(super-superclass)EParameter
中,所以 LocalVariable
不需要額外屬性。Statement
擴展 EClass
EOperationImpl
包含許多將會按給定順序計算的statement
。Statement
是一個抽象超類。LiteralAssignment
擴展 Statement
LiteralAssignment
引用一個變量,并且有一個 String
屬性,允許用戶輸入一個要被解析的值并將其分配給一個變量(例如,“hello”、“4.5” 可以分別分配給 String
或 float
)。Access
擴展 Statement
Access
表示引用 Java 字段或操作的動作。FieldReferenceAssignment
擴展 Access
var1 =var2.name
)。Invoke
擴展 Access
Invoke
的結(jié)果可以分配給一個變量(例如,myVar = obj.toString()
)。圖 2 展示了 EcoreX 元模型的一種更加類似 UML 的表示。
本文包括六個高級的步驟:
genmodel
。genmodel
。可以創(chuàng)建或?qū)肷厦婷枋龅脑P汀煞N情況都需要從一個現(xiàn)有 EMF 項目或創(chuàng)建一個新項目入手(New > Other > Eclipse Modeling Framework > Empty EMF Project)。我們的項目名為 EMFX,并且它應(yīng)包含一個名為 model 的文件夾。可以將這個 EcoreX.ecore 模型(參見 參考資料)復(fù)制到 model 目錄并跳至 構(gòu)建和啟動 Editor Metamodel 插件 小節(jié),也可以執(zhí)行以下步驟,從頭創(chuàng)建一個元模型。
右鍵單擊項目,從上下文菜單中選擇 New > Other >Example EMF Model Creation Wizards > Ecore Model。(對于 Eclipse V3.5+ [Galileo,Helios],則應(yīng)選擇 New > Other > Eclipse Modeling Framework > EcoreModel。)選擇 model
文件夾和名稱 EcoreX.ecore
。
默認(rèn)情況下,我們將模型包稱為 ecorex
。在模型窗口中右鍵單擊并選擇 Load Resource > Browse Registered Packages。選擇具有名稱空間 http://www.eclipse.org/emf/2002/Ecore
的 Ecore Model。
導(dǎo)入 Ecore 元模型之后,就可以對其進行擴展了。要重新創(chuàng)建 ecorex.ecore
模型,首先在包元素 ecorex
上右鍵單擊并選擇 New Child EClass。將此元素稱為 EPackageX
(參閱上面的模型元素描述)。然后需要將基元素 EPackage
作為這個新元素的 ESuper Type
添加。
通過將 EClass
指定為 ESuperType
,使用相同的過程創(chuàng)建新元素 EClassX
。根據(jù)需要對 Ecore 對象劃分子類,在 EcoreX 模型中繼續(xù)定義其他 EClass
。使用圖 1 和 EcoreX.ecore 文件了解要為哪個 EClass
創(chuàng)建什么屬性。
構(gòu)建并啟動the Editor Metamodel 插件
I在構(gòu)建步驟中,我們將創(chuàng)建元模型 genmodel
并構(gòu)建模型和編輯器項目。右鍵單擊 EcoreX 項目并選擇 New > Other >Eclipse Modeling Framework > EMF Model。(對于 EMF V2.5+ [Galileo, Helios],則應(yīng)選擇 New > Other > Eclipse Modeling Framework > EMFGenerator Model。)可以提供一個名稱或接受默認(rèn)的名稱 EcoreX.genmodel
。EcoreX 模型應(yīng)該被預(yù)選擇為 genmodel
的基模型。單擊 Load 驗證EcoreX.ecore
元模型。
當(dāng)要求指定要生成和從其他生成器模型引用的包時,選擇 Root packages 下面的 EcoreX 包和 Referenced generator models 下面的 Ecore。
現(xiàn)在,向?qū)樵P蛣?chuàng)建一個 genmodel
。突出顯示 genmodel
中的頂級元素之后,從上下文菜單中選擇 Generate All,這樣可以自動生成關(guān)聯(lián)的代碼。根據(jù)在 genmodel
中配置的行為,這將生成 4 個 Eclipse 項目。本文不會關(guān)注 .test 項目,所以您可能不希望生成這個插件。
現(xiàn)在我們繼續(xù)啟動步驟。在大多數(shù) Eclipse 教程中,都會要求您在單獨的 Eclipse過程中啟動所開發(fā)的插件。在本節(jié)中,我們將采用一種不同的方法:我們將在當(dāng)前 Eclipse和工作區(qū)中激活插件。這樣更容易將預(yù)構(gòu)建的元模型與下一節(jié)中具體的模型開發(fā)集成。為此:
單擊 Finish 時,會自動構(gòu)建生成的插件 JAR 文件,并自動將其復(fù)制到插件目錄。此時,您需要重新啟動 Eclipse,激活新插件。現(xiàn)在我們已經(jīng)準(zhǔn)備好啟動編輯器插件了,創(chuàng)建一個新項目來保存我們的具體模型(我們的模型命名為 Test2
)。
在這個新項目中,導(dǎo)航到 New > Other Example EMF Model CreationWizards > Ecorex Model 并提供一個模型名稱。注意:在 EMF 的最新版本 (V2.5+) 中,具體模型的文件擴展名必須被設(shè)為 .ecore,而不是 .ecorex;否則,這個具體的 genmodel
將不能在后續(xù)步驟中被成功創(chuàng)建。選擇 EPackageX
元素。您現(xiàn)在有了一個空的具體模型。后續(xù)小節(jié)將討論如何構(gòu)建這些編程模型元素;完成后的文件 My.ecore 可以在 參考資料 部分找到。
在本節(jié)中,我們將對一個具體的 Java 類(EClassX
的實例)進行建模,這個類包含兩個具體的方法,我們將對這兩個方法的實現(xiàn)進行建模。第一個示例方法接受 String
參數(shù)消息,并輸出消息和一個時間戳 — 這有利于調(diào)試消息。以下是期望結(jié)果的表示。
printTimestampMessage
void printTimestampMessage(String message) { System.out.print(message); System.out.print("; Timestamp= "); System.out.println(System.currentTimeMillis()); } |
第二個示例接受 3 個基于日期的參數(shù),并返回一個數(shù)字值,表示該日期對應(yīng)的是星期幾。
getDayOfWeek
int getDayOfWeek(int year, int month, int date) { int result; Calendar calendar = Calendar.getInstance(); calendar.set(year, month, date); result = calendar.get(Calendar.DAY_OF_WEEK); return result; } |
第一步是填入在上一節(jié)最后一步中創(chuàng)建的新 EPackageX
元素下的 3 個必需屬性。如果在建模窗口下看不到 Properties 選項卡,可以從上下文菜單中選擇 Show Properties View。在這個示例中,我們的包名為 mypackage。
接下來,向 mypackage 添加一個新 EClassX
??梢栽?mypackage 突出顯示時使用上下文菜單完成此任務(wù)。填入 name 屬性,為類提供一個名稱(比如 MyClass),向新類添加兩個 EOperationImpl
元素,并為它們指定方法名 printTimeStampMessage
和 getDayofWeek
。然后,向每一個操作添加 Ecore 參數(shù)。
EOperationImplgetDayOfWeek()
getDayOfWeek()
屬性上面的操作 printTimestampMessage()
接受一個 EString
類型的參數(shù),而 getDayOfWeek()
接受 3 個 EInt
類型的參數(shù)。此外,操作 getDayOfWeek
返回一個 EInt
,這可以在 property 屬性 EType
下進行配置(參見圖 7)。
到現(xiàn)在為止,我們僅使用了繼承的 Ecore 元素和屬性?,F(xiàn)在是時候使用我們擴展的元模型元素來構(gòu)建 Java 實現(xiàn)了。
LocalVariable
printTimestampMessage()
將需要兩個 LocalVariable
元素 — 一個為EString
類型,另一個為 ELong
類型。printTimestampMessage()
LiteralAssignment
在圖 9 中,Value
屬性的字符串被內(nèi)聯(lián)到 LiteralAssignment
。您可以設(shè)想一個不同的元模型,其中的文字值(常量)被建模為單獨的元素。
接下來,我們插入一個 LiteralAssignment
類型的元素,它允許選擇一個 LocalVariable
并為其分配值。在本例中,我們選擇 String
變量并提供上面的原型方法中的文本值(記住在文本兩邊加上引號)。
DataType
SystemType
的 Ecore DataType
,它是 java.lang.System 的一個包裝器。必須將其添加到我們的 mypackage 包,因為它將會被隨后的 Invoke
元素引用。Statement
Statement
是 SystemType
中的靜態(tài)方法 currentTimeMillis()
的一個 Invoke
,已經(jīng)在上面定義了。currentTimeMillis()
屬性根據(jù)我們的元模型(我們將在下一節(jié)提供代碼模板),上面的 Invoke
將轉(zhuǎn)換為 Java 語句:timestamp = java.lang.System.currentTimeMillis();
。
下一個 Invoke
與之前的那個稍有不同。首先,沒有 Assignment
。其次,我們將把 message
參數(shù)的引用作為 Args
屬性的一個參數(shù)。
out.print
屬性另外請注意,Access Name
屬性的值為out.print
— 在這里我們實際上從 Java System
間接引用了字段 out
,然后調(diào)用方法 print
。我們使用了這種快捷方式,而沒有結(jié)合使用一個 FieldReferenceAssignment
和一個 PrintStream
類型的 LocalVariable
。
操作中的第 3 個(最后一個)Invoke
是一個使用 LocalVariable
timestamp
作為單個參數(shù)的 println()
。這就完成了具體操作 printTimestampMessage()
的建模。
讓我們看看第二個 EOperationImpl
getDayOfWeek()
的完整模型。
getDayOfWeek()
DataTypes
DataType
,名為 CalendarType
,這是該操作所必需的。LocalVariables
LocalVariable
中,我們主要關(guān)注稱為 result
的 LocalVariable
,因為它將會保存執(zhí)行完操作的最后一條語句之后返回的值。在 EOperationImpl
屬性中有一個名為 Return Ref
的屬性,而且在我們的實現(xiàn)中,我們使用下拉菜單選擇 LocalVariable
結(jié)果。Statement
LocalVariable
之后是 3 個 Statement
。第一個是 Invoke
,它使用 CalendarType
元素上的 getInstance()
,為 calendar
變量分配一個值,與圖 10 中的操作類似。接下來是對 calendar
變量執(zhí)行的 set()
方法的 Invoke
,現(xiàn)在它傳遞 3 個與 EOperationImpl
參數(shù)(year
、month
和 date
)相對應(yīng)的 Arg
。
set()
FieldReferenceAssignment
根據(jù)我們的元模型,這個元素將會生成與 DAY = Calendar.DAY_OF_WEEK;
類似的 Java 代碼。
在圖 15 中,DAY
變量用于這個 EOperationImpl
的最后一個 Invoke
:一個 get()
,其返回值被分配給變量 result
(我們的實現(xiàn)的 Return Ref
)
Return Ref
我們現(xiàn)在設(shè)計了一個擴展的元模型,并用其描述了一個具體的模型 My.ecore(請參見上述的 EMF V2.5+文件名稱說明)?,F(xiàn)在終于可以用 JET 最終實現(xiàn)一些代碼實現(xiàn)了。要查看 JET 模板的語法突出顯示功能,您需要安裝 JET EditorPlugin(參見 參考資料 和 “JET Editor 局限性”)。
默認(rèn)情況下,在為模型生成代碼時,EMF 不會使用動態(tài)模板。它使用預(yù)構(gòu)建的 Java 類。要開始定制 JET 模板,我們需要從插件 JAR 文件 org.eclipse.emf.codegen.ecore_2.3.0.XYZ.jar 復(fù)制一些文件,其中 XYZ 是 Eclipse 插件文件夾中您的 EMF 版本的時間戳。本文使用 org.eclipse.emf.codegen.ecore_2.3.0.v200706262000.jar。要復(fù)制這些文件,請使用任意一種解壓縮工具打開 JAR 文件,并執(zhí)行以下操作:
由名稱可以看出,第 3 步中的新文件是一個 JET 模板,我們將在其中加入模型對象 EOperationImpl
的代碼生成邏輯。默認(rèn)情況下,這個文件并不存在,因為 EMF 只為每個 EOperation
提供一個空的方法簽名。一旦激活了動態(tài)模板功能,我們的新文件將被作為 Java 方法體自動包括,正如 EOperationImpl
所定義的。
以下是 implementedGenOperation.TODO.override.javajetinc
的完整代碼。
implementedGenOperation
// created by implementedGenOperation.TODO.override.javajetinc <% if ( ! (genOperation.getEcoreOperation() instanceof EOperationImpl) ) { %> // TODO: implement this method // Ensure that you remove @generated or mark it @generated NOT throw new UnsupportedOperationException(); <% } else { %> // ** EOperationX implementation ** <% EOperationImpl opx = (EOperationImpl)genOperation.getEcoreOperation(); Statement stm = null; Iterator iterator = null; EList<LocalVariable> pList = opx.getLocalVariables(); LocalVariable lvar = null; String iname = null; StringBuffer paramsString = null; StringBuffer varString = null; for (int i = 0;i < pList.size(); i++) { lvar = pList.get(i); iname = lvar.getEType().getInstanceClassName();%> <%=iname%> <%=lvar.getName()%><% if (iname.startsWith("java")) { %> = null <% } %>; <% } iterator = opx.getStatements().iterator(); while (iterator.hasNext()) { paramsString = new StringBuffer(); varString = new StringBuffer(); iname = null; stm = (Statement)iterator.next(); if (stm instanceof LiteralAssignment) {%> <%= stm.getAssignment().getName()%> = <%= ((LiteralAssignment)stm).getValue()%>; <%} else // if (stm instanceof FieldReferenceAssignment) { Access ax = (Access)stm; if (stm.getAssignment() != null) { varString.append(stm.getAssignment().getName()); varString.append(" = "); } if ( ax.getStaticType() != null) { // STATIC iname = ax.getStaticType().getInstanceClassName(); } else { // NON STATIC iname = ax.getTarget().getName(); } %> <%=varString.toString()%><%=iname%>.<%=ax.getAccessName()%>; <% } else if (stm instanceof Invoke) { // INVOKE Invoke iv = ((Invoke)stm); if (stm.getAssignment() != null) { varString.append(stm.getAssignment().getName()); varString.append(" = "); } for (int p = 0; p < iv.getArgs().size(); p++) { paramsString.append(iv.getArgs().get(p).getName()); if ( p + 1 < iv.getArgs().size() ) { paramsString.append(" , "); } } if (iv.getStaticType() != null) { // STATIC iname = iv.getStaticType().getInstanceClassName(); } else { // NON STATIC iname = iv.getTarget().getName(); } %> <%=varString.toString()%><%=iname%>.<%=iv.getAccessName() %>(<%=paramsString.toString()%>); <% } } // STATEMENTS if (opx.getReturnRef() != null) { %> return <%=opx.getReturnRef().getName()%>; <% } } // EOPERATIONIMPL %> |
對 JET 的詳細討論超出了本文的范圍。但是,因為 JET 模板對我們的操作過程至關(guān)重要,我們將在偽代碼方面回顧一下模板的內(nèi)容。請記住,在處理模板之前,第一個變量 genOperation
已經(jīng)被 Ecore/JET 預(yù)初始化。
genOperation
被 Ecore/JET 預(yù)初始化 Is this GenOperation is an EOperationImpl? If false, emit default UnsupportedOperationException STOP; Else, cast it to EOperationImpl; continue; Find and declare all elements of type LocalVariable, initializing Java Objects to null; Iterate through all Statements; Emit Java code according to the subtype; Does the implementation return something? If yes, emit the return statement; |
在構(gòu)建具體模型之前,需要執(zhí)行一些操作。首先,在 templates/model/Class.javajet 頂部,我們必須將以下內(nèi)容添加到導(dǎo)入列表(標(biāo)記為粗體的前兩行):
<%@ jet package="org.eclipse.emf.codegen.ecore.templates.model" imports="ecorex.* org.eclipse.emf.common.util.* java.util.* org.eclipse.emf.codegen.ecore.genmodel.*"… |
當(dāng)然,EcoreX 包是經(jīng)過擴展的元模型。接下來,我們需要為我們的具體模型(My.ecore
,類型為 '.ecorex')創(chuàng)建和配置一個 EMF(GenModel
)。為此,在模型上右鍵單擊并選擇 New > Other > Eclipse Modeling Framework > EMF Model(對于 EMF V2.5+ [Galileo, Helios],應(yīng)選擇 New > Other >Eclipse Modeling Framework > EMF Generator Model。)創(chuàng)建完成之后,需要在屬性組 Templates &Merge 下配置 3 個屬性,在 Model 下配置第四個屬性。
GenModel
— Templates & Merge現(xiàn)在可以進行構(gòu)建了,右鍵單擊 GenModel
并選擇 Generate Model Code。如果一切順利,在具體的 Test 項目(我們的項目稱為 Test2)的源文件夾(src)中,您應(yīng)該可以看到生成的 Java源代碼包和類,其中一個名為 mypackage.impl.MyClassImpl.java。打開該文件,您應(yīng)該會看到兩個生成的方法。
public void printTimestampMessage(String message) { // created by implementedGenOperation.TODO.override.javajetinc // ** EOperationX implementation ** java.lang.String timestampStr = null; long timestamp; timestampStr = "; Timestamp = "; timestamp = java.lang.System.currentTimeMillis(); java.lang.System.out.print(message); java.lang.System.out.print(timestampStr); java.lang.System.out.println(timestamp); } public int getDayOfWeek(int year, int month, int date) { // created by implementedGenOperation.TODO.override.javajetinc // ** EOperationX implementation ** int result; int DAY; java.util.Calendar calendar = null; calendar = java.util.Calendar.getInstance(); calendar.set(year , month , date); DAY = java.util.Calendar.DAY_OF_WEEK; result = calendar.get(DAY); return result; } |
可以添加一個 main 方法測試這個類。
在 EMF V2.5 之前,正如上面的幾個屏幕快照所示,從一個擴展了的 Ecore 模型生成的具體模型應(yīng)該保留'.ecorex' 的擴展名(如創(chuàng)建時的向?qū)ㄗh的那樣)。這有助于區(qū)別擴展了的模型與 ‘初級的’ Ecore 模型。然而,在 EMF的最新版本中,genmodel
向?qū)Вㄈ缭趫D 16 之前所解釋的)不接受除 .ecore 之外的其他文件擴展名。
要獲得 JET 模板的語法突出顯示功能,您需要安裝 Eclipse JET Editor(JET Editor Plugin 最近已經(jīng)從 EMF 遷移到 M2T)。
但是,在撰寫本文時,JET Editor 的最新版本不能正確處理 Java 內(nèi)容幫助或嵌套 JET 包含文件(比如.javajetinc 文件)的動態(tài)編譯。此外,為了確保構(gòu)建成功,只能在父文件(比如上面的Class.javajet)中指定導(dǎo)入操作,而不能在包含的文件中指定。
實際上,使用一些額外配置(即,使用項目的上下文菜單),您可以將 EMF 動態(tài)模板項目(本文示例中的 Test2)轉(zhuǎn)換為 JET 項目。在實踐中,上面提及的局限性以及 EMF 和 M2T/JET 之間缺少集成,使得這種方法不太可行。
因此,很難捕獲和改正包含的模板文件中的錯誤。由于在生成最終代碼之前,JET 模板首先會被編譯為一種中間 Java文件(默認(rèn)情況下位于一個隱藏的 Java 項目 JETEmitter 中),所以通過從 Eclipse 的 Package Explorer視圖中刪除過濾器,您能夠看到這些編譯錯誤。如果這只是模板文件中的格式錯誤,在構(gòu)建期間將會出現(xiàn)一個 Eclipse 彈出窗口?;蛟S在未來的 JET版本中,我們會看到更多的改進功能。
本文中的示例未使用 EMF Validation Framework 或 OCL 功能。所以,模型中的不一致性將會導(dǎo)致構(gòu)建失敗,例如,一個 EOperationImpl
可以聲明某種返回類型,但是Return Ref
屬性可以引用不同的類型或者為空。在構(gòu)建模型期間,將不會發(fā)現(xiàn)這些錯誤,而且生成的代碼將無法編譯??梢詫υP瓦M行改進,使用 OCL 增強完整性和約束(參見 參考資料)。
我們看到了如何擴展 Ecore 元模型,將合成的 Java 方法中的編程式行為概念化。通過導(dǎo)入 Ecore 本身,我們擴展了一些 Ecore 模型元素— 尤其是 EOperation
。然后構(gòu)建了元模型,并使用編輯器設(shè)計了一個具體的測試模型,包括以 EOperationImpl
形式建模的兩個 Java 方法。我們配置并構(gòu)建了 JET 模板,用于為 EOperationImpl
生成代碼。
聯(lián)系客服