// 獲取RFC返回的字段值 11 JCoParameterList exportParam = function.getExportParameterList(); 12 String exParamA = exportParam.getString("field_A"); 13 String exParamB = exportParam.getString("field_B"); 14 // 遍歷RFC返回的表對象 15 JCoTable tb = function.getTableParameterList().getTable("table_name"); 16 for (int i = 0; i < tb.getNumRows(); i++) { 17 tb.setRow(i); 18 System.out.println(tb.getString("field01")); 19 System.out.println(tb.getString("field02")); 20 } 21 }
SAP的R/3系統與Java平台一樣有着許多類似的技術理念,以及同樣廣泛的企業級用戶,但是它們完全是兩個不同的世界。當用戶面臨流程或者數據整合方面的需求的時候,就迫切需要一種高效的方式,在R/3系統和Java平台之間實時地交換數據。基於這樣的需求,SAP提供了一套高效的基於RFC的ABAP和Java進程間通訊組件:SAP Java Connector(JCo)。 本文將介紹JCo組件的架構,配置,基本使用方法以及調試,同時還將討論如何對RFC方式調用的ABAP函數進行遠程調試。 JCo庫提供了可以直接在Java程序中使用的API。該API通過JNI調用部署在客戶端的SAP的RFC庫。該RFC庫是用C語言實現的,並且與JCo庫相互獨立發布。但是,在下載的JCo庫壓縮包中也會提供。 該組件支持Inbound(在Java代碼中調用ABAP函數)和Outbound(在ABAP代碼中調用Java)兩種模式。本文只專注於介紹Inbound模式中,如何在Java代碼遠程調用ABAP函數。 安裝與配置 最新版本的JCo庫可以在如下網址下載(請選擇適合你的軟件和硬件平台的版本,本文的示例都基於32位Windows操作系統): http://service.sap.com/connectors 你可能需要提供SAP Service MarketPlace的用戶帳號。解壓縮以后,將名為librfc32.dll的文件復制到目錄 system32下面。如果該文件已經存在,則覆蓋它。這個文件就是SAP的RFC協議實現,可以在http://service.sap.com/connectors下載其最新版本。 然后,確保CLASSPATH環境變量中包含文件sapjco.jar所在的目錄。因為這個JAR包中含有在Java程序中需要直接調用的類和接口。 下載的壓縮包中還包含了JavaDoc,文檔以及示例程序供參考。 建立連接 在執行任何操作之前,必須先建立到SAP系統的連接。本文示例使用的用戶名是“DDIC”,登錄密碼是“minisap” 類JCO是Jco庫中最主要的一個入口,它提供了許多靜態方法。其中有一系列重載的createClient方法可以用來創建與SAP系統的連接信息。這些信息都保存在返回的JCO.Client類的實例中。常用的兩種方式如下: 直接輸入參數: JCO.Client myConnection = JCO.createClient("000", "DDIC", "minisap", "EN", "10.0.0.11", "00"); 使用Java Properties: Properties logonProperties = new Properties(); logonProperties.put("jco.client.ashost","10.0.0.11"); logonProperties.put("jco.client.client","000"); logonProperties.put("jco.client.passwd","minisap"); logonProperties.put("jco.client.sysnr","00"); logonProperties.put("jco.client.user","DDIC"); JCO.Client myConnection = JCO.createClient( logonProperties ). 第一種方式比較簡單,所有的參數都直接寫在代碼中。相比之下,第二種方式使用Java Properties,它好處在於,除了硬編碼這種方式之外,用戶也可以將連接信息保存在一個單獨的.properties文件中。這樣即使連接信息改變也無需改變代碼,只需要修改.properties文件中的數據即可。關於.properties文件的用法,請參考相關的Java語言教程。 JCO.Client提供方法connect方法來建立從當前Java進程到SAP服務器的連接。 this.myConnection.connect(); 可以使用isAlive方法來獲取一個連接的狀態,還可以使用disconnect方法來關閉一個連接: if ( myConnection != null && myConnection.isAlive()) 很多情況下,頻繁創建新的連接可能導致嚴重的性能問題。典型的情況就是在Web應用程序中,如果每個session創建一個連接,那么用戶數量很多的時候系對系統來說就是一場災難。JCo庫支持以連接池的形式重用已創建的連接。只需要調用JCO類的靜態方法addClientPool即可創建一個連接池,並且可以在參數中指定連接池的名字和允許同時激活的最大連接數。 如下代碼演示了如何創建一個名為“Sample_Pool”的JCo連接池: public static final String POOL_NAME = "Sample_Pool"; public static final int max_connection = 2; …… JCO.Pool pool = JCO.getClientPoolManager().getPool(POOL_NAME); if (pool == null) { Properties logonProperties = new Properties(); logonProperties.put("jco.client.ashost","10.0.0.11"); logonProperties.put("jco.client.client","000"); logonProperties.put("jco.client.passwd","minisap"); logonProperties.put("jco.client.sysnr","00"); logonProperties.put("jco.client.user","DDIC"); JCO.addClientPool( POOL_NAME, // pool name max_connection, // max num of connections, logonProperties); // properties } 創建好連接池之后,可以通過如下代碼來從連接池中獲取一個連接: mConnection = JCO.getClient(POOL_NAME); 在連接使用完畢之后,不要忘記使用releaseClient方法釋放當前連接: JCO.releaseClient( myConnection ). 如果需要移除連接池,則可以使用如下代碼: JCO.removeClientPool(POOL_NAME); 移除連接池將導致其中所有的活動連接被強行關閉,所以必須在確保連接池中所有的連接都不再被使用的時候才能執行該操作。 調用Function Modules 為了演示如何使用JCo庫來調用遠程的ABAP函數,本文示例中使用NetWeaver ABAP試用版系統中的一個樣例函數BAPI_FLIGHT_GETLIST。 JCo庫使用RFC的方式來調用ABAP中的函數,所以被調用的函數必須已經勾選“Remote-enabled”屬性。 調用一個函數之前,需要知道函數的元數據,比如函數名字,輸入輸出參數等等。在JCo庫中,必須通過類JCO.Repository來獲取所有的ABAP函數的元數據,所以第一步是創建一個JCO.Repository類的對象: JCO.Repository myRepository = new JCO.Repository("Repository", myConnection); JCO.Repository類的構造函數有兩個參數,第一個是可以任意指定的名字,第二個是當前使用的連接。此處也可以直接指定一個連接池的名字,JCo庫將自動從該連接池中獲取連接。 此時,必須保證該連接使用的用戶名在目標SAP服務器上有足夠的權限。 獲得JCO.Repository類的實例之后,就可以通過該實例來獲得函數的信息。如下代碼演示了如何獲取函數BAPI_FLIGHT_GETLIST的信息以及如何設置簡單類型的參數: String strFunc = "BAPI_FLIGHT_GETLIST"; IFunctionTemplate ft = myRepository.getFunctionTemplate(strFunc.toUpperCase()); JCO.Function funGetList = ft.getFunction(); // set up scalar parameter JCO.ParameterList input = funGetList.getImportParameterList(); input.setValue(10, "MAX_ROWS"); JCO.Function對象提供了對應的方法來獲取ABAP函數的參數列表。例如,上例中的getImportParameterList方法返回該函數的Import參數列表。 在上面的示例代碼中,僅僅設置了一個最簡單的int類型的參數。事實上,setValue方法有許多重載形式,允許設置各種復雜類型的參數,比如structure類型和table類型的參數。而且,除了通過參數名字引用要設置的參數之外,還可以通過參數的索引來引用一個參數。 在設置structure和table類型的參數之前,需要通過JCO.Function對象的方法獲取相應的JCO.Structure和JCO.Table對象,然后才可以使用對每個字段進行賦值。 在我們使用的函數BAPI_FLIGHT_GETLIST中,Import參數中的DESTINATION_FROM是一個structure,其中包含一個CITY字段。如下代碼演示了如何將CITY字段賦值為“NEW YORK”: // set up structure parameter JCO.Structure sFrom = input.getStructure("DESTINATION_FROM"); sFrom.setValue("NEW YORK", "CITY"); input.setValue(sFrom, "DESTINATION_FROM"); 類似地,可以使用JCO.Function對象的getTableParameterList方法拿到Table參數列表。下面的代碼演示了如何拿到一個名為DATE_RANGE的Table參數並且為它創建兩行: // set up table parameter JCO.Table tDateRange = funGetList.getTableParameterList() .getTable("DATE_RANGE"); tDateRange.appendRow(); tDateRange.setRow(0); tDateRange.setValue("I", "SIGN"); tDateRange.setValue("EQ", "OPTION"); tDateRange.setValue("20070606", "LOW"); tDateRange.appendRow(); tDateRange.setRow(1); tDateRange.setValue("I", "SIGN"); tDateRange.setValue("EQ", "OPTION"); tDateRange.setValue("20070704", "LOW"); 參數設置完畢之后,可以通過JCO.Client對象的execute方法執行遠程調用: myConnection.execute(funGetList); 獲得輸出參數的方法與輸入參數完全一樣。下面的代碼演示了如何獲取一個包含返回值的Table參數,並且輸出它的內容: // get table results JCO.Table flights = funGetList.getTableParameterList().getTable( "FLIGHT_LIST"); for (int i = 0; i < flights.getNumRows(); i++) { flights.setRow(i); System.out.println("Airline [" + flights.getString("AIRLINE") + "] from city " + flights.getString("CITYFROM") + " to city " + flights.getString("CITYTO") + ", departure time is " + flights.getDate("FLIGHTDATE")); } JCO.Structure和JCO.Table都繼承自類JCO.Record。JCO.Record對每種類型的參數都提供了對應的get和set方法,並且在運行時自動進行Java數據類型和ABAP數據類型之間的轉換。限於篇幅,本文不再詳敘,請參考JCo庫的JavaDoc文檔。 在使用JCo庫的過程中,主要有兩種類型的異常需要處理: JCO.AbapException 如果ABAP函數執行過程中出現異常,則在Java進程中會觸發該異常。 JCO.ConversionException 當執行參數的get和set方法時,如果在Java類型和ABAP類型之間轉換失敗,則會觸發該異常。 作為一種最佳實踐,建議使用try-catch封裝使用JCo庫進行參數設置和函數調用的代碼,處理上述兩種異常,並且在finally代碼塊中,釋放當前所使用的連接。 遠程調試 一般的情況下,在SAP服務器上通過事務代碼SE37可以測試ABAP函數。在保證ABAP函數的正確性之后,Java客戶端只需要檢查輸入輸出參數是否正確即可。這時可以利用JCo庫為了方便調試而提供的了一個很強大的功能,把所有繼承自JCO.Record的類的對象格式化輸出到一個指定的HTML文檔中。通過這種方式,我們可以檢查輸入輸出參數是否正確。比如如下代碼輸出前面得到的FLIGHT_LIST參數的內容: JCO.Table flights = funGetList.getTableParameterList().getTable( "FLIGHT_LIST"); flights.writeHTML("c:flight_list.html"); 更進一步地,通過啟動ABAP的遠程調試功能,可以像調試普通程序一樣調試遠程調用的ABAP函數。 要實現遠程調試,首先需要在Java代碼中,通過調用JCO.Client類或者JCO.Pool類的setAbapDebug方法激活JCo的ABAP調試功能。如下代碼演示了如何激活一個連接池的ABAP調試功能: JCO.Pool pool = JCO.getClientPoolManager().getPool(POOL_NAME); pool.setAbapDebug(true); 如果一個連接池的ABAP調試功能被激活,那么其中的所有連接的ABAP調試功能都會被激活。使用這樣一個連接來調用ABAP函數的時候,SAP系統會自動彈出一個調試器窗口(如下圖所示)。當然,前提是客戶端機器上已經安裝了SAPGUI。 這時候調試器僅僅停留在RFC調用的入口處,而並未進入所調用的ABAP函數。 為了讓調試器直接進入ABAP函數,需要在事務代碼SE37中為該函數設置一個外部斷點(External BreakPoint)。 設置好外部斷點之后,還必須通過事務代碼SRDEBUG激活遠程調試功能。 如果之前沒有直接在代碼上設置過外部斷點的話,也可以直接在SRDEBUG中設置斷點所在的Function Module。 要注意的是,在調試結束之前,不要關閉SRDEBUG的窗口,否則系統將會立即關閉遠程調試功能。 此時,再執行Java代碼,將會發現系統自動打開的DEBUG窗口自動停留在我們所設置的外部斷點的位置,而Java進程在調試結束之前將會被掛起。 在ABAP調試器中,可以檢查通過RFC協議傳過來的參數,以及ABAP程序運行的結果。通過這種方式,可以更清晰地跟蹤整個執行過程。 調試結束的時候,不要忘記關閉SRDEBUG的窗口和清除外部斷點。 更多資源 限於篇幅,本文只能介紹關於JCo庫的基本使用方法。更進一步的技術細節,可以查看JCo庫的下載文件中自帶的文檔。 開發者還可以在如下連接的SAP的在線幫助文檔中找到豐富的文檔資料和示例程序: http://help.sap.com/saphelp_nw70/helpdata/EN/6f/1bd5c6a85b11d6b28500508b5d5211/frameset.htm
報錯信息
RFC接口調用SAP如果有異常會通過com.sap.mw.jco.JCO$Exception: 拋出異常 在開發中遇到的異常有如下 用戶名密碼可能是錯誤或者用戶無權限,確認用戶,必要時聯系SAP負責人,檢查用戶 (103) RFC_ERROR_LOGON_FAILURE: ##.#####,#### (103) RFC_ERROR_LOGON_FAILURE: Name or password is incorrect (repeat logon) call信息沒有填寫完整,檢查配置文件各個SAP配置信息是否完整 (101) RFC_ERROR_PROGRAM: Missing R3NAME=... or ASHOST=... in connect_param in RfcOpenEx ip地址失敗: com.sap.mw.jco.JCO$Exception: (102) RFC_ERROR_COMMUNICATION: Connect to SAP gateway failed 組權限訪問 server文件沒更新. (102) RFC_ERROR_COMMUNICATION:Connect to message server failed C:\WINNT\system32\drivers\etc 端口號錯誤報錯信息: (103) RFC_ERROR_LOGON_FAILURE: ## 502 ######## 超時: (103) RFC_ERROR_LOGON_FAILURE: Timeout 執行函數,函數的問題 (104) RFC_ERROR_SYSTEM_FAILURE: Error in module RSQL of the database interface.執行函數 (104) RFC_ERROR_SYSTEM_FAILURE: An error occurred when receiving a complex parameter. (106) JCO_ERROR_RESOURCE: Trying to access row values in a table which does not have any rows yet 返回的表沒有值.那個表連第一行都沒有,取不到 (106) JCO_ERROR_RESOURCE: Trying to access row values in a table which does not have any rows yet 語法錯誤 (104) RFC_ERROR_SYSTEM_FAILURE: Syntax error in program SAPMV50A 找不到行 (106) JCO_ERROR_RESOURCE: Trying to access row values in a table which does not ha:ve any rows yet 輸入參數不能插入SAP函數輸入字段中. (122) JCO_ERROR_CONVERSION: Integer '4234243' has to many digits at field PO_ITEM 原文地址:http://hi.baidu.com/gary_c/blog/calendar/200809/index/2 函數不能遠程調用 (104) RFC_ERROR_SYSTEM_FAILURE: The function module "RP_CHECK_PERNR" cannot be used for 'remote' calls.