Java連接sap,RFC接口

 

 

 

安裝JCo3. 78
建立JCo3鏈接... 79
直連... 79
鏈接池... 81
DestinationDataProvider接口(不需鏈接屬性配置文件)... 82
訪問結構 (Structure)84
訪問表 (Table)84
Java多線程調用有/無狀態RFM.. 86
ABAP類型與JCo類型映射關係表... 96
ABAP訪問Java服務... 97
鏈接異常registrationnot allowed. 99
無狀態訪問... 100
ABAP客戶端代碼... 100
Java服務端代碼... 101
帶狀態訪問... 106
ABAP客戶端代碼(可拷貝兩份進行測試)... 106
Java服務端代碼... 107
JCo RFC函數異常總結
外部系統(Java)調用BAPI函數
在調用BAPI時,SAP爲各編程環境(VB、C++、Java等)提供了RFC庫及SAP鏈接器(如Jco、Nco等)。這些類庫中的RFC API封閉了外部系統和SAP的鏈接細節
安裝JCo3
JCo有32位和64爲之分,32位的JVM選擇32位的JCO, 64位的JVM選擇64位的JCO, 在windows環境,選擇相應的sapjco3.dll, Unix和Linux環境選擇合適的sapjco3.so
32位下載:http://pan.baidu.com/s/1jGr6jSa
64位下載:http://pan.baidu.com/s/1i3mO2rj
解壓後將sapjco3.dll拷貝到c:/windows/system32與C:\Program Files (x86)\Java\jdk1.7.0_51\bin下,將sapjco3.jar加入項目的classpath中。
測試安裝成功與否,很簡單,打開一個命令:
java -jar C:/sapjco3.jar
或者
java -cp C:/sapjco3.jar com.sap.conn.jco.rt.About

建立JCo3鏈接
JCo鏈接到SAP服務器有兩種方法,分別是直連和經過鏈接池進行鏈接。其差異在於,打開直連鏈接後能夠一直保持鏈接;鏈接池則是在須要時才創建鏈接,鏈接暫不須要時,將被釋放回鏈接池,再分配給其餘用戶使用。在網絡服務器應用程序裏,通常採用鏈接池進行鏈接SAP服務器。
若是是老系統,可能要還注意遠程登陸用戶的類型:

直連
import java.io.File;
import java.io.FileOutputStream;
import java.util.Properties;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.ext.DestinationDataProvider;
public class ConnectNoPool {// 直連方式,非鏈接池
// 鏈接屬性配置文件名,名稱能夠隨便取
   static String ABAP_AS = "ABAP_AS_WITHOUT_POOL";
   static {
      Properties connectProperties = new Properties();
      connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
            "192.168.111.137");
      connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
      connectProperties
            .setProperty(DestinationDataProvider.JCO_CLIENT, "800");
      connectProperties.setProperty(DestinationDataProvider.JCO_USER,
            "SAPECC");
      // 注:密碼是區分大小寫的,要注意大小寫
      connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
            "sapecc60");
      connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
      // 須要將屬性配置保存屬性文件,該文件的文件名爲 ABAP_AS_WITHOUT_POOL.jcoDestination,
      // JCoDestinationManager.getDestination()調用時會須要該鏈接配置文件,後綴名須要爲jcoDestination
      createDataFile(ABAP_AS, "jcoDestination", connectProperties);
   }
   // 基於上面設定的屬性生成鏈接配置文件
   static void createDataFile(String name, String suffix, Properties properties) {
      File cfg = new File(name + "." + suffix);
      if (!cfg.exists()) {
         try {
            FileOutputStream fos = new FileOutputStream(cfg, false);
            properties.store(fos, "for tests only !");
            fos.close();
         } catch (Exception e) {
            e.printStackTrace();
         }
      }
   }
   public static void connectWithoutPool() throws JCoException {
      // 到當前類所在目錄中搜索 ABAP_AS_WITHOUT_POOL.jcoDestination
      // 屬性鏈接配置文件,並根據文件中的配置信息來建立鏈接
      JCoDestination destination = JCoDestinationManager
            .getDestination(ABAP_AS);// 只需指定文件名(不能帶擴展名jcoDestination名,會自動加上)
      System.out.println("Attributes:");
      // 調用destination屬性時就會發起鏈接,一直等待遠程響應
      System.out.println(destination.getAttributes());
   }
   public static void main(String[] args) throws JCoException {
      connectWithoutPool();
   }
}
Attributes:
DEST:                  ABAP_AS_WITHOUT_POOL
OWN_HOST:              jiangzhengjun
PARTNER_HOST:          SAPECC6
SYSTNR:                00
SYSID:                 ECC
CLIENT:                800
USER:                  SAPECC
LANGUAGE:              E
ISO_LANGUAGE:          EN
OWN_CODEPAGE:          4102
OWN_CHARSET:           UTF16
OWN_ENCODING:          utf-16
OWN_BYTES_PER_CHAR:    2
PARTNER_CODEPAGE:      4103
PARTNER_CHARSET:       UTF16
PARTNER_ENCODING:      utf-16
PARNER_BYTES_PER_CHAR: 2
OWN_REL:               720
PARTNER_REL:           731
PARTNER_TYPE:          3
KERNEL_REL:            720
TRACE:                 
RFC_ROLE:              C
OWN_TYPE:              E
CPIC_CONVID:           00000000
鏈接池
程序運行結果與上面直接是同樣的
import java.io.File;
import java.io.FileOutputStream;
import java.util.Properties;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.ext.DestinationDataProvider;
public class ConnectPooled {// 鏈接池
   static String ABAP_AS_POOLED = "ABAP_AS_WITH_POOL";
   static {
      Properties connectProperties = new Properties();
      connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
            "192.168.111.137");
      connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
      connectProperties
            .setProperty(DestinationDataProvider.JCO_CLIENT, "800");
      connectProperties.setProperty(DestinationDataProvider.JCO_USER,
            "SAPECC");
      // 注:密碼是區分大小寫的,要注意大小寫
      connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
            "sapecc60");
      connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
      // *********鏈接池方式與直接不一樣的是設置了下面兩個鏈接屬性
      // JCO_PEAK_LIMIT - 同時可建立的最大活動鏈接數,0表示無限制,默認爲JCO_POOL_CAPACITY的值
      // 若是小於JCO_POOL_CAPACITY的值,則自動設置爲該值,在沒有設置JCO_POOL_CAPACITY的狀況下爲0
      connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
            "10");
      // JCO_POOL_CAPACITY - 空閒鏈接數,若是爲0,則沒有鏈接池效果,默認爲1
      connectProperties.setProperty(
            DestinationDataProvider.JCO_POOL_CAPACITY, "3");
      createDataFile(ABAP_AS_POOLED, "jcoDestination", connectProperties);
   }
   static void createDataFile(String name, String suffix, Properties properties) {
      File cfg = new File(name + "." + suffix);
      if (!cfg.exists()) {
         try {
            FileOutputStream fos = new FileOutputStream(cfg, false);
            properties.store(fos, "for tests only !");
            fos.close();
         } catch (Exception e) {
            e.printStackTrace();
         }
      }
   }
   public static void connectWithPooled() throws JCoException {
      JCoDestination destination = JCoDestinationManager
            .getDestination(ABAP_AS_POOLED);
      System.out.println("Attributes:");
      System.out.println(destination.getAttributes());
   }
   public static void main(String[] args) throws JCoException {
      connectWithPooled();
   }
}
DestinationDataProvider接口(不需鏈接屬性配置文件)
上面直接鏈接、鏈接池,兩種鏈接方法都須要先創建一個屬性配置文件,而後JCo再從創建好文件裏讀取鏈接到SAP服務器所須要的鏈接屬性,這個方法很難在實際的環境中應用,存儲SAP鏈接屬性配置信息到一個文件裏,是比較不安全的。然而,JCO爲咱們提供了另一種鏈接的方法:DestinationDataProvider,經過它咱們就能夠將一個鏈接變量信息存放在內存裏
import java.util.HashMap;
import java.util.Properties;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
importcom.sap.conn.jco.ext.DestinationDataEventListener;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.Environment;
public class CustomSAPDestinationDataProvider {
static class MyDestinationDataProvider implements DestinationDataProvider {
   privateDestinationDataEventListenereL;
   private HashMap<String, Properties>destinations;
   private static MyDestinationDataProvider provider = new MyDestinationDataProvider();
   private MyDestinationDataProvider() {// 單例模式
   if (provider == null) {
         destinations = new HashMap<String, Properties>();
       }
   }
   public static MyDestinationDataProvider getInstance() {
      return provider;
   }
   // 實現接口:獲取鏈接配置屬性
   public Properties getDestinationProperties(String destinationName) {
   if (destinations.containsKey(destinationName)) {
         return destinations.get(destinationName);
       } else {
      throw new RuntimeException("Destination " + destinationName
         + " is not available");
       }
   }
   public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
      this.eL = eventListener;
   }
   public boolean supportsEvents() {
      return true;
}
   /**
    * Add new destination 添加鏈接配置屬性
    *
    * @param properties
    *            holds all the required data for a destination
    **/
   void addDestination(String destinationName, Properties properties) {
   synchronized (destinations) {
      destinations.put(destinationName, properties);
       }
   }
}
public static void main(String[] args) throws Exception {
   // 獲取單例
   MyDestinationDataProvider myProvider = MyDestinationDataProvider
      .getInstance();
   // Register the MyDestinationDataProvider 環境註冊
   Environment.registerDestinationDataProvider(myProvider);
   // TEST 01:直接測試
   // ABAP_AS is the test destination name :ABAP_AS爲目標鏈接屬性名(只是邏輯上的命名)
   String destinationName = "ABAP_AS";
   System.out.println("Test destination - " + destinationName);
   Properties connectProperties = new Properties();
   connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
      "192.168.111.123");
   connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
   connectProperties
      .setProperty(DestinationDataProvider.JCO_CLIENT, "800");
   connectProperties.setProperty(DestinationDataProvider.JCO_USER,
      "SAPECC");
   connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
      "sapecc60");
   connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
   // Add a destination
   myProvider.addDestination(destinationName, connectProperties);
   // Get a destination with the name of "ABAP_AS"
   JCoDestination DES_ABAP_AS = JCoDestinationManager
      .getDestination(destinationName);
   // Test the destination with the name of "ABAP_AS"
   try {
       DES_ABAP_AS.ping();
       System.out.println("Destination - " + destinationName + " is ok");
   } catch (Exception ex) {
       ex.printStackTrace();
       System.out.println("Destination - " + destinationName
          + " is invalid");
   }
   // TEST 02:鏈接池測試
   // Add another destination to test
   // ABAP_AS2 is the test destination name
   String destinationName2 = "ABAP_AS2";
   System.out.println("Test destination - " + destinationName2);
   Properties connectProperties2 = new Properties();
   connectProperties2.setProperty(DestinationDataProvider.JCO_ASHOST,
      "192.168.111.123");
   connectProperties2.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
   connectProperties2
      .setProperty(DestinationDataProvider.JCO_CLIENT, "800");
   connectProperties2.setProperty(DestinationDataProvider.JCO_USER,
      "SAPECC");
   connectProperties2.setProperty(DestinationDataProvider.JCO_PASSWD,
      "sapecc60");
   connectProperties2.setProperty(DestinationDataProvider.JCO_LANG, "en");
   connectProperties2.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
      "10");
   connectProperties2.setProperty(
      DestinationDataProvider.JCO_POOL_CAPACITY, "3");
   // Add a destination
   myProvider.addDestination(destinationName2, connectProperties2);
   // Get a destination with the name of "ABAP_AS2"
   JCoDestination DES_ABAP_AS2 = JCoDestinationManager
      .getDestination(destinationName2);
   // Test the destination with the name of "ABAP_AS2"
   try {
       DES_ABAP_AS2.ping();
       System.out.println("Destination - " + destinationName2 + " is ok");
   } catch (Exception ex) {
       ex.printStackTrace();
       System.out.println("Destination - " + destinationName2
          + " is invalid");
   }
    }
}
訪問結構 (Structure)
public static void accessSAPStructure() throws JCoException {
   JCoDestination destination = JCoDestinationManager
      .getDestination(ABAP_AS);
   JCoFunction function = destination.getRepository().getFunction(
      "RFC_SYSTEM_INFO");//從對象倉庫中獲取 RFM 函數
   if (function == null)
   throw new RuntimeException(
      "RFC_SYSTEM_INFO not found in SAP.");
   try {
       function.execute(destination);
   } catch (AbapException e) {
       System.out.println(e.toString());
   return ;
   }
   JCoStructure exportStructure = function.getExportParameterList()
      .getStructure("RFCSI_EXPORT");
   System.out.println("System info for "
      + destination.getAttributes().getSystemID() + ":\n");
   for (int i = 0; i < exportStructure.getMetaData().getFieldCount(); i++) {
       System.out.println(exportStructure.getMetaData().getName(i) + ":\t"
          + exportStructure.getString(i));
   }
   System.out.println();
   // JCo still supports the JCoFields, but direct access via getXX is more
   // efficient as field iterator  也可使用下面的方式來遍歷
   System.out.println("The same using field iterator: \nSystem info for "
      + destination.getAttributes().getSystemID() + ":\n");
   for (JCoField field : exportStructure) {
       System.out.println(field.getName() + ":\t" + field.getString());
   }
   System.out.println();
   //*********也可直接經過結構中的字段名或字段所在的索引位置來讀取某個字段的值
   System.out.println("RFCPROTO:\t"+exportStructure.getString(0));
   System.out.println("RFCPROTO:\t"+exportStructure.getString("RFCPROTO"));
    }
public static void main(String[] args) throws JCoException {
   accessSAPStructure();
}
訪問表 (Table)
public static void workWithTable() throws JCoException {
   JCoDestination destination = JCoDestinationManager
      .getDestination(ABAP_AS);
   JCoFunction function = destination.getRepository().getFunction(
      "BAPI_COMPANYCODE_GETLIST");//從對象倉庫中獲取 RFM 函數:獲取公司列表
   if (function == null)
   throw new RuntimeException(
      "BAPI_COMPANYCODE_GETLIST not found in SAP.");
   try {
       function.execute(destination);
   } catch (AbapException e) {
       System.out.println(e.toString());
   return ;
   }
   JCoStructure return Structure = function.getExportParameterList()
      .getStructure("return ");
   //判斷讀取是否成功
   if (!(return Structure.getString("TYPE").equals("") || return Structure
      .getString("TYPE").equals("S"))) {
   throw new RuntimeException(return Structure.getString("MESSAGE"));
   }
   //獲取Table參數:COMPANYCODE_LIST
   JCoTable codes = function.getTableParameterList().getTable(
      "COMPANYCODE_LIST");
   for (int i = 0; i < codes.getNumRows(); i++) {//遍歷Table
       codes.setRow(i);//將行指針指向特定的索引行
       System.out.println(codes.getString("COMP_CODE") + '\t'
          + codes.getString("COMP_NAME"));
   }
   // move the table cursor to first row
   codes.firstRow();//從首行開始從新遍歷 codes.nextRow():若是有下一行,下移一行並返回True
   for (int i = 0; i < codes.getNumRows(); i++, codes.nextRow()) {
   //進一步獲取公司詳細信息
       function = destination.getRepository().getFunction(
      "BAPI_COMPANYCODE_GETDETAIL");
   if (function == null)
      throw new RuntimeException(
         "BAPI_COMPANYCODE_GETDETAIL not found in SAP.");
       function.getImportParameterList().setValue("COMPANYCODEID",
          codes.getString("COMP_CODE"));
   // We do not need the addresses, so set the corresponding parameter
   // to inactive.
   // Inactive parameters will be either not generated or at least
   // converted. 不須要返回COMPANYCODE_ADDRESS參數(但服務器端應該仍是組織了此數據,只是未通過網絡傳送?)
       function.getExportParameterList().setActive("COMPANYCODE_ADDRESS",
      false);
   try {
      function.execute(destination);
       } catch (AbapException e) {
      System.out.println(e.toString());
      return ;
       }
       return Structure = function.getExportParameterList().getStructure(
      "return ");
   if (!(return Structure.getString("TYPE").equals("")
          || return Structure.getString("TYPE").equals("S") || return Structure
          .getString("TYPE").equals("W"))) {
      throw new RuntimeException(return Structure.getString("MESSAGE"));
       }
       JCoStructure detail = function.getExportParameterList()
          .getStructure("COMPANYCODE_DETAIL");
       System.out.println(detail.getString("COMP_CODE") + '\t'
          + detail.getString("COUNTRY") + '\t'
          + detail.getString("CITY"));
   }// for
}
Java多線程調用有/無狀態RFM
有狀態調用:指屢次調用某個程序(如屢次調用某個RFC函數、調用某個函數組中的多個RFC函數、及BAPI函數——由於BAPI函數也是一種特殊的具備RFC功能的函數,它也有本身的函數組)時,在這一屢次調用過程當中,程序運行時的內存狀態(即全局變量的值)能夠在每次調用後保留下來,供下一次繼續使用,而不是每次調用後,程序所在的內存狀態被清除。這種調用適用於那些使用到函數組中的全局變量的RFC函數的調用
無狀態調用:每次的調用都是獨立的一次調用(上一次調用與當前以及下一次調用之間不會共享任何全局變量),調用後不會保留內存狀態,這種調用適用於那些沒有使用到函數組中的全局變量的RFC函數調用
若是主調程序爲Java時,須要經過遠程鏈接來調用RFC函數,此種狀況下的有狀態調用的前提是:
l  屢次調用RFC函數時,Java端要確保每次調用所使用的鏈接與上次是同一個(應該不須要是同一物理鏈接,只須要確保是同一遠程會話,從下面演示程序來看,用的是鏈接池,但同一任務執行時並未去特地使用同一物理鏈接去發送遠程調用,而只是要求是同一遠程會話)
l  ABAP端須要在每次調用後,保留每一次被調用後函數組的內存狀態,直到最後一次調用完成止,這須要Java與ABAP配合來完成(Java在第一次調用時,調用JCoContext.begin、JCoContext.end這兩個方法,告訴SAP這一調用過程將是有狀態調用,而後SAP端會自動保留內存狀態)
若是主調程序是ABAP(即ABAP程序調用ABAP函數),此種狀況下沒有特殊的要求,直接調用就便可,只要是在同一程序的同一運行會話其間(會話至關於Java中的同一線程吧),不論是屢次調用同一個函數、仍是調用同一函數組中的不一樣函數,則都會自動保留內存狀態,直到程序運行結束,這是系統本身完成的。實質上一個函數組就至關於一個類,函數組中不一樣的函數就至關於類中不一樣的方法、全局變量就至關於類中的屬性,因此只要是在同一程序的同一運行會話期間,調用的同一函數所在的函數組中的全局變量都是共享的,就比如調用一類的某個方法時,該方法設置了某個類的屬性,再去調用該類的其它方法時,該屬性值仍是保留了之前其它方法修改後的狀態值。
該示例採用線程方式來演示,狀態調用只要保證同一線程中屢次遠程方法調用採用的都是同一會話便可。固然更簡單的方法是不使用多線程,而直接在主線程中使用同一個物理遠程鏈接調用便可(但仍是須要調用JCoContext.begin、JCoContext.end這兩個方法,告訴SAP端須要保留內存狀態,直接程序結束)
這裏使用的函數是從標準程序COUNTER函數組拷貝而來,只不過系統中提供的不支持RFC調用,拷貝過來後修改爲RFM:
 
 

import java.io.File;
import java.io.FileOutputStream;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.sap.conn.jco.JCoContext;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoFunctionTemplate;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.Environment;
import com.sap.conn.jco.ext.JCoSessionReference;
import com.sap.conn.jco.ext.SessionException;
import com.sap.conn.jco.ext.SessionReferenceProvider;
/**
* MultiThreadedExample is rather complex. It demonstrates演示 how to use the
* SessionReferenceProvider會話提供者 defined in the package com.sap.conn.jco.ext.
*
* Before discussing討論 situations狀況 requiring要求 SessionReferenceProvider, we
* provide a short description of how the JCo Runtime handles the stateful(有狀態)
* and stateless(無狀態) calls by default. By default all RFC calls
* 默認狀況下全部JCoFunction.execute執行都是無狀態的 (JCoFunction.execute(JCoDestination)) are
* stateless. That means the ABAP context associated with the connection will be
* destroyed(意味着上下鏈接被銷燬). Some RFC modules save a particular state/data in the
* ABAP context's area(有些函數組下的多個RFM會共用一全局變量). In order to keep a JCo connection
* and use it for subsequent (stateful) calls(爲了保持多個RFM在同一鏈接中順序調用), the
* JCoConext.begin(JCoDestination) API can be used. In the case of multithreaded
* applications some calls to a destination can be executed concurrently(同時,併發),
* so JCo Runtime(JCo運行時環境需) needs to associate a particular call or connection
* to an internal session. By default JCo Runtime associates each thread with a
* session of its(默認狀況下每一個線程都有它本身的會話) own, so that most applications that execute
* all stateful requests en bloc(總體) or at least in the same thread will run
* correctly.
*
* Applications that wish to execute calls belonging to a stateful sequence by
* employing(採用) different threads have to implement and register the
* SessionReferenceProvider. The main goal of(主要目標) the implementation is to
* determine to which session the calls executing in the current thread belong.
*
* This example defines MultiStepJob having several execution
* steps(該示例的任務有多個步驟). The test starts a certain number of threads (see
* runJobs). Each thread is designed to take a job, execute one step, and put
* the job back to the shared job list. There are two jobs as an example:
* StatelessMultiStepExample and StatefulMultiStepExample. Both invoke the same
* RFC modules, but StatefulMultiStepExample uses JCoContext.begin and
* JCoContext.end to specify the stateful calls.
*
* To be able to execute a stateful call sequence distributed over several
* steps, we register a custom implementation of SessionReferenceProvider called
* MySessionReferenceProvider. The idea behind MySessionReferenceProvider is
* simple: each thread holds the current session reference in its local storage.
* To achieve(實現) that WorkerThread.run sets this session reference before
* executing the next step and removes it after the step is finished.
*/
public class MultiThreadedExample {
   private static BlockingQueue<MultiStepJob>queue = new LinkedBlockingQueue<MultiStepJob>();
   private static JCoFunctionTemplate incrementCounterTemplate,
         getCounterTemplate;
   // 任務接口
   interface MultiStepJob {
      String getName();//任務名
      boolean isFinished();//任務是否
      public void runNextStep();//運行任務
      public void cleanUp();//清除任務
   }
   // 無狀態遠程RFM的調用(增長計數與讀取計數RFM雖然在這裏是在同一會話中調用的——不必定是在同一鏈接中,
   // 但沒有調用JCoContext.begin方法讓ABAP保留每次被調用後的內存狀態:計數器全局變量 count的值)
   static class StatelessMultiStepExample implements MultiStepJob {
      static AtomicInteger JOB_COUNT = new AtomicInteger(0);
      int jobID = JOB_COUNT.addAndGet(1);// 任務編號
      int calls;// 須要調用多少次
      JCoDestination destination;// 遠程目標
      int executedCalls = 0;// 記錄調用次數,即任務步驟
      Exception ex = null;// 記錄任務執行過程出現的異常
      int remoteCounter;// 計數結果
      StatelessMultiStepExample(JCoDestination destination, int calls/* 調用次數 */) {
         this.calls = calls;
         this.destination = destination;
      }
      public boolean isFinished() {
         // 若是Z_INCREMENT_COUNTER已經調用了10次,或者調用過程當中出現了異常時,表示任務已完成
         return executedCalls == calls || ex != null;
      }
      public String getName() {// 任務名
         return "無狀態調用 Job-" + jobID;
      }
      // 任務的某一步,究竟有多少步則外界來傳遞進來的calls變量來控制
      public void runNextStep() {
         try {
            //注:在調用遠程RFC功能函數(如這裏的incrementCounter、getCounter)以前,JCo框架會去調用
            // SessionReferenceProvider的getCurrentSessionReference()方法,
            // 取得當前任務所對應的遠程會話,確保同一任務是在同一遠程會話中執行的
            JCoFunction incrementCounter = incrementCounterTemplate
                   .getFunction();// 增長計數:即RFM中的count全局變量加一
            incrementCounter.execute(destination);
            executedCalls++;// 調用了多少次
            if (isFinished()) {// 任務完後(這裏調用10次),纔讀取計數器
                JCoFunction getCounter = getCounterTemplate.getFunction();
                getCounter.execute(destination);
                remoteCounter = getCounter.getExportParameterList().getInt(
                      "GET_VALUE");// 讀取計數:即讀取RFM中的count全局變量
            }
         } catch (JCoException je) {
            ex = je;
         } catch (RuntimeException re) {
            ex = re;
         }
      }
      public void cleanUp() {// 任務結束後,清除任務
         StringBuilder sb = new StringBuilder("任務 ").append(getName())
                .append(" 結束:");
         if (ex != null) {
            sb.append("異常結束 ").append(ex.toString());
         } else {
            sb.append("成功執行完,計數器值 = ").append(remoteCounter);
         }
         System.out.println(sb.toString());
      }
   }
   // 有狀態遠程RFM調用(增長計數與讀取計數RFM在同一遠程會話中執行,保留了內存狀態:計數器全局變量 count的值)
   static class StatefulMultiStepExample extends StatelessMultiStepExample {
      StatefulMultiStepExample(JCoDestination destination, int calls) {
         super(destination, calls);
      }
      @Override
      public String getName() {
         return "有狀態調用 Job-" + jobID;
      }
      @Override
      public void runNextStep() {
         // 若是是任務的第一步,則須要讓ABAP端保留函數運行後的上下文(內存)狀態
         if (executedCalls == 0) {
            // begin()與end()之間表示多個RFM執行會在同一個鏈接中執行,而且這之間的多個RFM屬於同一個LUW,而且按照調用的順序來執行
            // ****不論是否有無狀態RFM調用(加begin後無狀態調用至少還能夠保證同一任務中多個函數調用的順序),都要確保同一任務
            // ****(多個RFM所組成的遠程調用任務)在同一會話中執行,要作到這一點,在Java端須要保證不一樣線程(同一線程也是)
            // ****在執行同一任務時,JCo鏈接與遠程會話都要是同一個
            JCoContext.begin(destination);// 開啓狀態調用,會話在begin與end之間不會被重置與關閉,這樣
            // SAP端用戶的上下文件就會被保持
         }
         super.runNextStep();
      }
      @Override
      public void cleanUp() {
         try {
            JCoContext.end(destination);
         } catch (JCoException je) {
            ex = je;
         }
         super.cleanUp();
      }
   }
   static class MySessionReference implements JCoSessionReference {// 遠程會話實現
      static AtomicInteger atomicInt = new AtomicInteger(0);
      // 遠程會話ID
      private String id = "session-" + String.valueOf(atomicInt.addAndGet(1));;
      public void contextFinished() {
      }
      public void contextStarted() {
      }
      public String getID() {
         return id;
      }
   }
   // 工做線程,用來執行前面定義的任務:StatelessMultiStepExample、StatefulMultiStepExample
   static class WorkerThread extends Thread {
      // 任務與遠程會話映射關係表:確保同一任務要在同一遠程會話中執行
      static Hashtable<MultiStepJob, MySessionReference>sessions = new Hashtable<MultiStepJob, MySessionReference>();
      // ThreadLocal:線程全局變量局部化,即將本來共享的屬性全局變量在每一個線程中都拷貝一份,不會讓它們再在不一樣的線程中共享,
      // 每一個線程拿到的都是本身所獨享的,因此看似全局共享的屬性在多線程狀況下,也不會出現多線程併發問題
      // 當前線程所使用的遠程會話
      static ThreadLocal<MySessionReference>localSessionReference = new ThreadLocal<MySessionReference>();
      // 同步器:倒計時閉鎖;threadCount爲倒計數值,直到該數爲0時,await()纔會結束繼續往下執行
      // CountDownLatch同步器的做用就是讓全部線程都準備好之後,真正同時開始執行,這樣不會由於先建立的
      // 的線程就會先執行,能夠真正模擬多線程同時執行的狀況,這樣在研究多線程在訪問同一臨界資源時,容易發現線程併發問題
      private CountDownLatch startSignal;// 開始閥:因此線程都已啓動並就緒時,全部線程再也不阻塞
      private CountDownLatch doneSignal;// 結束閥:因此線程結束後,主線程才結束
      WorkerThread(CountDownLatch startSignal, CountDownLatch doneSignal) {
         this.startSignal = startSignal;
         this.doneSignal = doneSignal;
      }
      // 工做線程
      public void run() {
         startSignal.countDown();
         try {
            startSignal.await();// 全部線程都已經運行到這裏後,纔開始一塊兒同時向下執行,不然一直阻塞
            // 某一時間段內(即一次循環)只執行某個任務的一個步驟
            for (;;) {// 直到任務隊列中沒有任務時退出
                // 出隊,工做線程從任務隊列中取任務:若是等10秒都未取到,則返回NULL
                MultiStepJob job = queue.poll(10, TimeUnit.SECONDS);
                // stop if nothing to do
               if (job == null) {// 若是任務隊列中沒有任務後,工做線程將退出
                   return ;
                }
                // 取任務所對應的遠程會話,確保每一個任務使用同一遠程會話
                MySessionReference sesRef = sessions.get(job);
                if (sesRef == null) {// 若是是第一次,則新建立一個遠程會話,再將任務與該會話進行綁定
                   sesRef = new MySessionReference();
                  sessions.put(job, sesRef);
                }
                // 存儲當前線程所使用的遠程會話。該值的讀取是在調用遠程RFM前,由JCo框架的
                // SessionReferenceProvider的getCurrentSessionReference()方法來讀取
                // ****不論是否有無狀態RFM調用,最好都要確保同一任務(多個RFM所組成的遠程調用任務)在同一會話中執行
                // ****,要作到這一點,在Java端須要保證不一樣線程(同一線程也是)在執行同一任務時,遠程會話要是同一個
                // 注:同一任務須要設置爲同一遠程會話,不一樣任務不能設置爲相同的遠程會話,不然計數器會在多個任務中共用
                localSessionReference.set(sesRef);
                System.out.println("任務 " + job.getName() + " 開始執行.");
                try {
                   // 執行任務
                   job.runNextStep();
                } catch (Throwable th) {
                   th.printStackTrace();
                }
                // 若是任務完成(調用遠程RFM計數器函數10次)
                if (job.isFinished()) {
                   System.out.println("任務 " + job.getName() + " 執行完成.");
                   // 若是任務執行完了,則從映射表是刪除任務與遠程會話映射記錄
                   sessions.remove(job);
                   job.cleanUp();// 任務的全部步驟執行完後,輸出任務結果
                } else {
                   System.out.println("任務 " + job.getName()
                         + " 未完成,從新放入任務隊列,等待下次繼續執行.");
                   // 若是發現任務尚未執行完,則從新放入任務隊列中,等待下一次繼續執行。從這裏能夠看出
                   // 計數器的增長與讀取多是由不一樣的工做線程來完成的,但要確保同一任務是在同一遠程會話中調用的
                   queue.add(job);
                }
                // 當某個任務某一步執行完後,清除當前線程所存儲的遠程會話。注:這裏的工做線程某一時間段內(即一次循環內)只能執行一個任務
                localSessionReference.set(null);
            }
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            doneSignal.countDown();
         }
      }
   }
   // 遠程會話提供者:負責拿到當前任務的遠程會話
   static class MySessionReferenceProvider implements SessionReferenceProvider {
      public JCoSessionReference getCurrentSessionReference(String scopeType) {
         // 從當前線程中讀取相應的遠程會話,這樣確保了同一任務中多個RFM的調用是在同一遠程會話鏈接中執行的
         MySessionReference sesRef = WorkerThread.localSessionReference
                .get();
         if (sesRef != null) {
            return  sesRef;
         }
         throw new RuntimeException("Unknown thread:"
                + Thread.currentThread().getId());
      }
      // 遠程會話是否活着,JCo框架調用此來決定此鏈接是否銷燬?
      public boolean isSessionAlive(String sessionId) {
         Collection<MySessionReference> availableSessions = WorkerThread.sessions
                .values();
         for (MySessionReference ref : availableSessions) {
            if (ref.getID().equals(sessionId)) {
                return true;
            }
         }
         return false;
      }
      public void jcoServerSessionContinued(String sessionID)
            throws SessionException {
      }
      public void jcoServerSessionFinished(String sessionID) {
      }
      public void jcoServerSessionPassivated(String sessionID)
            throws SessionException {
      }
      public JCoSessionReference jcoServerSessionStarted()
            throws SessionException {
         return null;
      }
   }
   // 建立任務與工做線程並拉起
   static void runJobs(JCoDestination destination, int jobCount,
         int threadCount) {
      System.out.println(">>>啓動");
      for (int i = 0; i < jobCount; i++) {// 5*2=10 個任務(一半是狀態調用,一半是無狀態調用)
         // 添加RFM無狀態調用任務
         queue.add(new StatelessMultiStepExample(destination, 10/*
                                                      * 每一個任務須要調用10次
                                                      * Z_INCREMENT_COUNTER
                                                      * 後,任務纔算完成
                                                      */));
         // 添加RFM有狀態調用任務
         queue.add(new StatefulMultiStepExample(destination, 10));
      }
      CountDownLatch startSignal = new CountDownLatch(threadCount);
      CountDownLatch doneSignal = new CountDownLatch(threadCount);
      for (int i = 0; i < threadCount; i++) {
         // 2 個工做線程,共同來完成10 個任務
         new WorkerThread(startSignal, doneSignal).start();// 建立並啓動工做線程
      }
      System.out.println(">>>等待執行任務... ");
      try {
         doneSignal.await();// 主線程等待全部工做任務線程完成後,才結束
      } catch (InterruptedException ie) {
         ie.printStackTrace();
      }
      System.out.println(">>>完成");
   }
   public static void main(String[] argv) {
      // JCo.setTrace(5, ".");
      Environment
            .registerSessionReferenceProvider(new MySessionReferenceProvider());
      try {
         JCoDestination destination = JCoDestinationManager
                .getDestination(ABAP_AS);
         // 遠程函數模板
         incrementCounterTemplate = destination.getRepository()
                .getFunctionTemplate("Z_INCREMENT_COUNTER");// 增長計數:即RFM中的count全局變量加一
         getCounterTemplate = destination.getRepository()
                .getFunctionTemplate("Z_GET_COUNTER");// 讀取計數:RFM中的count全局變量
         if (incrementCounterTemplate == null || getCounterTemplate == null) {
            throw new RuntimeException(
                   "This example cannot run without Z_INCREMENT_COUNTER and Z_GET_COUNTER functions");
         }
         // 2 個工做線程,5*2=10 個任務(一半是狀態調用,一半是無狀態調用)
         runJobs(destination, 5, 2);
      } catch (JCoException je) {
         je.printStackTrace();
      }
   }
   // 鏈接屬性配置文件名,名稱能夠隨便取
   static String ABAP_AS = "ABAP_AS_WITH_POOL";
   static {
      Properties connectProperties = new Properties();
      connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
            "192.168.111.137");
      connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
      connectProperties
            .setProperty(DestinationDataProvider.JCO_CLIENT, "800");
      connectProperties.setProperty(DestinationDataProvider.JCO_USER,
            "SAPECC");
      // 注:密碼是區分大小寫的,要注意大小寫
      connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
            "sapecc60");
      connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
      // ****使用鏈接池的方式
      connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
            "10");
      connectProperties.setProperty(
            DestinationDataProvider.JCO_POOL_CAPACITY, "3");
      // 須要將屬性配置保存屬性文件,該文件的文件名爲 ABAP_AS_WITHOUT_POOL.jcoDestination,
      // JCoDestinationManager.getDestination()調用時會須要該鏈接配置文件,後綴名須要爲jcoDestination
      createDataFile(ABAP_AS, "jcoDestination", connectProperties);
   }
   // 基於上面設定的屬性生成鏈接屬性配置文件
   static void createDataFile(String name, String suffix, Properties properties) {
      File cfg = new File(name + "." + suffix);
      if (!cfg.exists()) {
         try {
            FileOutputStream fos = new FileOutputStream(cfg, false);
            properties.store(fos, "for tests only !");
            fos.close();
         } catch (Exception e) {
            e.printStackTrace();
         }
      }
   }
}
>>>啓動
>>>等待執行任務...
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-4 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-1 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-2 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務無狀態調用 Job-3 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-6 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-4 執行完成.
任務有狀態調用 Job-4 結束:成功執行完,計數器值 = 10
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-1 執行完成.
任務無狀態調用 Job-1 結束:成功執行完,計數器值 = 0
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-7 未完成,從新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-2 執行完成.
任務有狀態調用 Job-2 結束:成功執行完,計數器值 = 10
任務無狀態調用 Job-5 開始執行.
任務無狀態調用 Job-9 未完成,從新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-5 執行完成.
任務無狀態調用 Job-5 結束:成功執行完,計數器值 = 0
任務無狀態調用 Job-3 開始執行.
任務有狀態調用 Job-10 執行完成.
任務無狀態調用 Job-3 執行完成.
任務無狀態調用 Job-3 結束:成功執行完,計數器值 = 0
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-8 執行完成.
任務有狀態調用 Job-8 結束:成功執行完,計數器值 = 10
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-6 執行完成.
任務有狀態調用 Job-10 結束:成功執行完,計數器值 = 10
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-6 結束:成功執行完,計數器值 = 10
任務無狀態調用 Job-9 開始執行.
任務無狀態調用 Job-9 執行完成.
任務無狀態調用 Job-9 結束:成功執行完,計數器值 = 0
任務無狀態調用 Job-7 執行完成.
任務無狀態調用 Job-7 結束:成功執行完,計數器值 = 0
>>>完成
ABAP類型與JCo類型映射關係表
 

ABAP訪問Java服務
ABAP(做爲Clint端),調用JAVA(做爲服務器端)。
SAP經過JCO反向調用JAVA的RFC服務其實也是相對簡單的,只是在JAVA端須要使用JCO建立一個RFC服務,而後在SAP端註冊這個服務程序。
首先,JCo服務器程序需在網關中進行註冊,在SM59中,定義一個鏈接類型爲T的遠程目標
 

RFC目標系統:是ABAP RFC調用Java時,需指定的目標系統名。
Program ID:是JAVA程序中使用的

Gateway Host與Gateway service值來自如下界面(Tcode:SMGW):

這裏的Geteway Host就是下圖的應用程序服務器地址。 TCP服務sapgw是固定的,後面的00就是下圖的系統編號。
 

全部配置好且Java服務器代碼跑起來後,點擊「Connection Test」按鈕,如不出現紅色文本,則表示連接成功(注:此時須要ServerDataProvider.JCO_PROGID設置的Program ID要與SM59中設置的相同,不然測試不成功。另要注意的是:即便Java服務器設置的Program ID亂設置,Java服務端仍是能啓起來,但ABAP測試鏈接時會不成功,也就表明ABAP不能調用Java):

此時能夠經過SMGW來觀測鏈接:

鏈接異常registrationnot allowed
Java服務啓動時,如出現如下異常,則需在SAP中修改網關參數:
com.sap.conn.jco.JCoException: (113) JCO_ERROR_REGISTRATION_DENIED: CPIC-CALL: SAP_CMACCPTP3 on convId:       
LOCATION    SAP-Gateway on host LRP-ERP / sapgw00
ERROR       registration of tp JCOTEST from host JIANGZHENGJUN not allowed
TIME        Wed Apr 16 21:25:39 2014
RELEASE     720
COMPONENT   SAP-Gateway
VERSION     2
RC          720
MODULE      gwxxrd.c
LINE        3612
COUNTER     275
 

 


請參考JCo3包中的示例
無狀態訪問 ABAP客戶端代碼
DATA: resptext LIKE sy-lisel,
      echotext LIKE sy-lisel.
DATA: rfc_mess(128).
"注:這裏調用的不是本地SAP系統中的STFC_CONNECTION,而是遠程Java系統中所對應的
"函數,此函數的功能由Java服務器端實現,但此函數必定要在本地定義簽名(無需實現功能),
"不然須要在Java服務器端動態的經過JCoCustomRepository來建立函數對象庫
"CALL FUNCTION 'STFC_CONNECTION'"因爲STFC_CONNECTION已存在於SAP本地中,因此無需在Java服務端建立函數對象庫,與此函數遠程調用的配套示例代碼爲 Java 端的simpleServer() 方法
CALL FUNCTION 'ZSTFC_CONNECTION'"ZSTFC_CONNECTION在ABAP本地不存,所以還須要在Java服務端建立此函數對象庫,與此函數遠程調用的配套示例代碼爲 Java 端的staticRepository() 方法
  DESTINATION 'JCOTEST'"SM59中配置的RFC目標系統
  EXPORTING
    requtext              = 'ABAP端發送的消息,並須要回傳'"需發送的文本
  IMPORTING
    resptext              = resptext"服務器響應文本
    echotext              = echotext"回顯文本
  EXCEPTIONS
    system_failure        = 1  MESSAGE rfc_mess
    communication_failure = 2  MESSAGE rfc_mess.
IF sy-subrc NE 0.
  WRITE: / 'Call function error SY-SUBRC = ', sy-subrc.
  WRITE: / rfc_mess.
ELSE.
  WRITE:/ resptext,echotext.
ENDIF.
*****************************下面ABAP程序是用來配合測試Java端的transactionRFCServer() 方法的
DATA: resptext LIKE sy-lisel,
      echotext LIKE sy-lisel.
DATA: rfc_mess(128).
CALL FUNCTION 'STFC_CONNECTION'
  IN BACKGROUND TASK
  DESTINATION 'JCOTEST'
  EXPORTING
    requtext              = 'ABAP端發送的消息'
  EXCEPTIONS
    system_failure        = 1  message rfc_mess
    communication_failure = 2  message rfc_mess.
DATA: tid TYPE arfctid.
CALL FUNCTION 'ID_OF_BACKGROUNDTASK'
  IMPORTING
    tid = tid."此事務ID與Java端打印出來的是同樣的
WRITE:/ 'TID = ',tid.
COMMIT WORK AND WAIT .
DATA: errortab TYPE STANDARD TABLE OF arfcerrors WITH HEADER LINE.
DO.
  "獲取事務狀態,若是事務運行完,則sy-subrc爲0
  CALL FUNCTION 'STATUS_OF_BACKGROUNDTASK'
    EXPORTING
      tid           = tid
    TABLES
      errortab      = errortab[]
    EXCEPTIONS
      communication = 1"鏈接不可用:過會重試
      recorded      = 2"異步RFC調用處於計劃中
      rollback      = 3."目標系統已經回滾
  WRITE:/ sy-subrc.
  IF sy-subrc = 0.
    EXIT.
  ELSE.
    LOOP AT errortab.
      WRITE: / errortab.
    ENDLOOP.
    "若是事務未完成,則等一秒後再判斷其狀態,直到事務完成
    WAIT UP TO 1 SECONDS.
  ENDIF.
ENDDO.
Java服務端代碼
import java.io.File;
import java.io.FileOutputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import com.sap.conn.jco.JCo;
import com.sap.conn.jco.JCoCustomRepository;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoFunctionTemplate;
import com.sap.conn.jco.JCoListMetaData;
import com.sap.conn.jco.JCoMetaData;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.ServerDataProvider;
import com.sap.conn.jco.server.DefaultServerHandlerFactory;
import com.sap.conn.jco.server.JCoServer;
import com.sap.conn.jco.server.JCoServerContext;
import com.sap.conn.jco.server.JCoServerContextInfo;
import com.sap.conn.jco.server.JCoServerErrorListener;
import com.sap.conn.jco.server.JCoServerExceptionListener;
import com.sap.conn.jco.server.JCoServerFactory;
import com.sap.conn.jco.server.JCoServerFunctionHandler;
import com.sap.conn.jco.server.JCoServerState;
import com.sap.conn.jco.server.JCoServerStateChangedListener;
import com.sap.conn.jco.server.JCoServerTIDHandler;
public class StepByStepServer {
   static String SERVER_NAME1 = "SERVER";
   static String DESTINATION_NAME1 = "ABAP_AS_WITHOUT_POOL";
   static String DESTINATION_NAME2 = "ABAP_AS_WITH_POOL";
   static MyTIDHandler myTIDHandler = null;
   static {
      JCo.setTrace(4, null);// 打開調試
      Properties connectProperties = new Properties();
      // ******直連
      connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
            "192.168.111.137");
      connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
      connectProperties
            .setProperty(DestinationDataProvider.JCO_CLIENT, "800");
      connectProperties.setProperty(DestinationDataProvider.JCO_USER,
            "SAPECC");
      connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
            "sapecc60");
      connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
      createDataFile(DESTINATION_NAME1, "jcoDestination", connectProperties);
      // ******鏈接池
      connectProperties.setProperty(
            DestinationDataProvider.JCO_POOL_CAPACITY, "3");
      connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
            "10");
      createDataFile(DESTINATION_NAME2, "jcoDestination", connectProperties);
      // ******JCo sever
      Properties servertProperties = new Properties();
      servertProperties.setProperty(ServerDataProvider.JCO_GWHOST,
            "192.168.111.137");
      // TCP服務sapgw是固定的,後面的00就是SAP實例系統編號,也可直接是端口號(端口號能夠在
      // etc/server文件中找sapgw00所對應的端口號)
      servertProperties.setProperty(ServerDataProvider.JCO_GWSERV, "sapgw00");
      // 這裏的程序ID來自於SM59中設置的Program ID,必須相同
      servertProperties
            .setProperty(ServerDataProvider.JCO_PROGID, "JCO_TEST");
      servertProperties.setProperty(ServerDataProvider.JCO_REP_DEST,
            DESTINATION_NAME2);
      servertProperties.setProperty(ServerDataProvider.JCO_CONNECTION_COUNT,
            "2");
      createDataFile(SERVER_NAME1, "jcoServer", servertProperties);
   }
   static void createDataFile(String name, String suffix, Properties properties) {
      File cfg = new File(name + "." + suffix);
      if (!cfg.exists()) {
         try {
            FileOutputStream fos = new FileOutputStream(cfg, false);
            properties.store(fos, "for tests only !");
            fos.close();
         } catch (Exception e) {
            throw new RuntimeException(
                   "Unable to create the destination file "
                         + cfg.getName(), e);
         }
      }
   }
   // 處理來自ABAP端的調用請求,實現註冊過的虛擬函數真正功能
   static class StfcConnectionHandler implements JCoServerFunctionHandler {
      public void handleRequest(JCoServerContext serverCtx,
            JCoFunction function) {// 處理遠程調用請求
         System.out
               .println("----------------------------------------------------------------");
         System.out.println("call              : " + function.getName());// ABAP調用的是哪一個函數
         System.out.println("ConnectionId      : "
                + serverCtx.getConnectionID());
         System.out.println("SessionId         : "
                + serverCtx.getSessionID());
         System.out.println("TID               : " + serverCtx.getTID());
         System.out.println("repository name   : "
                + serverCtx.getRepository().getName());
         System.out.println("is in transaction : "
                + serverCtx.isInTransaction());
         System.out.println("is stateful       : "
                + serverCtx.isStatefulSession());
         System.out
               .println("----------------------------------------------------------------");
         System.out.println("gwhost: "
                + serverCtx.getServer().getGatewayHost());
         System.out.println("gwserv: "
                + serverCtx.getServer().getGatewayService());
         System.out.println("progid: "
                + serverCtx.getServer().getProgramID());
         System.out
               .println("----------------------------------------------------------------");
         System.out.println("attributes  : ");
         System.out.println(serverCtx.getConnectionAttributes().toString());
         System.out
               .println("----------------------------------------------------------------");
         System.out.println("CPIC conversation ID: "
                + serverCtx.getConnectionAttributes()
                      .getCPICConversationID());
         System.out
               .println("----------------------------------------------------------------");
         System.out.println("req text: "
                + function.getImportParameterList().getString("REQUTEXT"));
         // function.getExportParameterList().setValue("ECHOTEXT",
         // function.getImportParameterList().getString("REQUTEXT"));
         // function.getExportParameterList().setValue("RESPTEXT",
         // "Java服務端響應的消息");
      }
   }
   static class MyThrowableListener implements JCoServerErrorListener,
         JCoServerExceptionListener {// 服務異常監聽器
      public void serverErrorOccurred(JCoServer jcoServer,
            String connectionId, JCoServerContextInfo serverCtx, Error error) {
         System.out.println(">>> Error occured on "
                + jcoServer.getProgramID() + " connection " + connectionId);
         error.printStackTrace();
      }
      public void serverExceptionOccurred(JCoServer jcoServer,
            String connectionId, JCoServerContextInfo serverCtx,
            Exception error) {
         System.out.println(">>> Error occured on "
                + jcoServer.getProgramID() + " connection " + connectionId);
         error.printStackTrace();
      }
   }
   static class MyStateChangedListener implements
         JCoServerStateChangedListener {// 服務狀態改變監聽器
      public void serverStateChangeOccurred(JCoServer server,
      JCoServerState oldState, JCoServerState newState) {
         // Defined states are: STARTED啓動, DEAD死, ALIVE活, STOPPED中止;
         // see JCoServerState class for details.
         // Details for connections managed by a server instance
         // are available via JCoServerMonitor.
         System.out.println("Server state changed from "
                + oldState.toString() + " to " + newState.toString()
                + " on server with program id " + server.getProgramID());
      }
   }
   // 簡單調用:提供的函數須要在ABAP簽名
   static void simpleServer() {
      JCoServer server;
      try {
         server = JCoServerFactory.getServer(SERVER_NAME1);
      } catch (JCoException ex) {
         throw new RuntimeException("Unable to create the server "
                + SERVER_NAME1 + " because of " + ex.getMessage(), ex);
      }
      JCoServerFunctionHandler stfcConnectionHandler = new StfcConnectionHandler();
      DefaultServerHandlerFactory.FunctionHandlerFactory factory = new DefaultServerHandlerFactory.FunctionHandlerFactory();
      // 向SAP服務器註冊可提供的函數有哪些,告訴SAP系統,Java這邊能夠提供STFC_CONNECTION這樣一個遠程函數,但具體的功能由StfcConnectionHandler來完成
      // 注:因該能夠註冊多個這樣的虛擬函數,都由 JCoServerFunctionHandler
      // 的實現類來處理,在處理時能夠由JCoFunction參數來判斷具體是哪一個函數,走不一樣的處理邏輯
      // 注:STFC_CONNECTION須要先在SAP端定義(但不須要在ABAP中實現),不然須要在Java端動態建立函數對象倉庫(請參考staticRepository方法)
      factory.registerHandler("STFC_CONNECTION", stfcConnectionHandler);
      server.setCallHandlerFactory(factory);
      // ********* 添加一些鏈接狀態監聽處理器,便於在鏈接過程當中定位問題(能夠不用設置)
      MyThrowableListener eListener = new MyThrowableListener();// 異常監聽,在鏈接過程當中出問題時會被監聽到
      server.addServerErrorListener(eListener);
      server.addServerExceptionListener(eListener);
      MyStateChangedListener slistener = new MyStateChangedListener();// 鏈接狀態監聽
      server.addServerStateChangedListener(slistener);
      server.start();
   }
   // 在Java服務端定義遠程函數(不須要在ABAP端進行函數的簽名定義)
   static void staticRepository() {
      JCoListMetaData impList = JCo.createListMetaData("IMPORT");
      impList.add("REQUTEXT", JCoMetaData.TYPE_CHAR, 100, 50, 0, null, null,
            JCoListMetaData.IMPORT_PARAMETER, null, null);
      impList.lock();// 鎖住,不容許再修改
      JCoListMetaData expList = JCo.createListMetaData("EXPORT");
      expList.add("RESPTEXT", JCoMetaData.TYPE_CHAR, 100, 50, 0, null, null,
            JCoListMetaData.EXPORT_PARAMETER, null, null);
      expList.add("ECHOTEXT", JCoMetaData.TYPE_CHAR, 100, 50, 0, null, null,
            JCoListMetaData.EXPORT_PARAMETER, null, null);
      expList.lock();
      // 注:ZSTFC_CONNECTION函數沒必要要在ABAP端時行定義了(只定義簽名,不須要實現),由於在這裏(Java)
      // 進行了動態的函數對象建立的建立與註冊,這與上面simpleServer方法示例是不同的
      JCoFunctionTemplate fT = JCo.createFunctionTemplate("ZSTFC_CONNECTION",
            impList, expList, null, null, null);
      JCoCustomRepository cR = JCo
            .createCustomRepository("MyCustomRepository");
      cR.addFunctionTemplateToCache(fT);
      JCoServer server;
      try {
         server = JCoServerFactory.getServer(SERVER_NAME1);
      } catch (JCoException ex) {
         throw new RuntimeException("Unable to create the server "
                + SERVER_NAME1 + " because of " + ex.getMessage(), ex);
      }
      String repDest = server.getRepositoryDestination();
      if (repDest != null) {
         try {
            cR.setDestination(JCoDestinationManager.getDestination(repDest));
         } catch (JCoException e) {
            e.printStackTrace();
            System.out
                   .println(">>> repository contains static function definition only");
         }
      }
      server.setRepository(cR);
      JCoServerFunctionHandler requestHandler = new StfcConnectionHandler();
      DefaultServerHandlerFactory.FunctionHandlerFactory factory = new DefaultServerHandlerFactory.FunctionHandlerFactory();
      factory.registerHandler(fT.getName(), requestHandler);
      server.setCallHandlerFactory(factory);
      server.start();
   }
   /*
    * 該類用於在ABAP進行事務調用(CALL FUNCTION func IN BACKGROUND TASK DESTINATION dest)
    * 時, Java端須要實時告訴ABAP端目前事務處理的狀況(狀態),即Java與ABAP之間的事務狀態的交流
    */
   static class MyTIDHandler implements JCoServerTIDHandler {
      // 存儲事務狀態信息
      Map<String, TIDState>availableTIDs = new Hashtable<String, TIDState>();
      // 18662702337
      // 當一個事務性RFM從ABAP端進行調用時,會觸發此方法
      public boolean checkTID(JCoServerContext serverCtx, String tid) {
         // This example uses a Hashtable to store status information.
         // Normally, however,
         // you would use a database. If the DB is down throw a
         // RuntimeException at
         // this point. JCo will then abort the tRFC and the R/3 backend will
         // try again later.
         System.out.println("TID Handler: checkTID for " + tid);
         TIDState state = availableTIDs.get(tid);
         if (state == null) {
            availableTIDs.put(tid, TIDState.CREATED);
            return true;
         }
         if (state == TIDState.CREATED || state == TIDState.ROLLED_BACK) {
            return true;
         }
         return false;
         // "true" means that JCo will now execute the transaction, "false"
         // means
         // that we have already executed this transaction previously, so JCo
         // will
         // skip the handleRequest() step and will immediately return an OK
         // code to R/3.
      }
      // 事件提交時觸發
      public void commit(JCoServerContext serverCtx, String tid) {
         System.out.println("TID Handler: commit for " + tid);
         // react on commit, e.g. commit on the database;
         // if necessary throw a RuntimeException, if the commit was not
         // possible
         availableTIDs.put(tid, TIDState.COMMITTED);
      }
      // 事務回滾時觸發
      public void rollback(JCoServerContext serverCtx, String tid) {
         System.out.println("TID Handler: rollback for " + tid);
         availableTIDs.put(tid, TIDState.ROLLED_BACK);
         // react on rollback, e.g. rollback on the database
      }
      public void confirmTID(JCoServerContext serverCtx, String tid) {
         System.out.println("TID Handler: confirmTID for " + tid);
         try {
            // clean up the resources
         }
         // catch(Throwable t) {} //partner(代碼ABAP對方) won't react on an
         // exception at
         // this point
         finally {
            availableTIDs.remove(tid);
         }
      }
      private enum TIDState {
         CREATED, COMMITTED, ROLLED_BACK, CONFIRMED;
      }
   }
   /**
    * Follow server example demonstrates how to implement the support for tRFC
    * calls, calls executed BACKGROUND TASK. At first we write am
    * implementation for JCoServerTIDHandler interface. This implementation is
    * registered by the server instance and will be used for each call send in
    * "background task". Without such implementation JCo runtime deny any tRFC
    * calls. See javadoc for interface JCoServerTIDHandler for details.
    */
   // 支持事務性調用,但究竟若是使用,有什麼做用不清楚!!!
   static void transactionRFCServer() {
      JCoServer server;
      try {
         server = JCoServerFactory.getServer(SERVER_NAME1);
      } catch (JCoException ex) {
         throw new RuntimeException("Unable to create the server "
                + SERVER_NAME1 + " because of " + ex.getMessage(), ex);
      }
      JCoServerFunctionHandler stfcConnectionHandler = new StfcConnectionHandler();
      DefaultServerHandlerFactory.FunctionHandlerFactory factory = new DefaultServerHandlerFactory.FunctionHandlerFactory();
      factory.registerHandler("STFC_CONNECTION", stfcConnectionHandler);
      server.setCallHandlerFactory(factory);
      // ***添加事務處理器
      myTIDHandler = new MyTIDHandler();
      server.setTIDHandler(myTIDHandler);
      server.start();
   }
   public static void main(String[] a) {
      simpleServer();
      // staticRepository();
      //transactionRFCServer();
   }
}
帶狀態訪問
仍是以計數器函數爲例,參照Java多線程調用有(無)狀態RFM章節,只是Z_INCREMENT_COUNTER、Z_GET_COUNTER兩個函數是在Java服務器端實現,而不ABAP中建立的函數。
當ABAP客戶端屢次調用Z_INCREMENT_COUNTER函數計數後,最後調用Z_GET_COUNTER函數來獲取計數器結果,這一過程要屢次調用Java端RFC函數,這要保證是在同一會話中完成的
ABAP客戶端代碼(可拷貝兩份進行測試)
DATA value TYPE i.
DATA loops TYPE i VALUE 5.
"屢次調用RFM,表示該任務LUW單元有多步,但須要保存同一LUW單元在Java端爲同一會話中執行
DO loops TIMES.
  CALL FUNCTION 'Z_INCREMENT_COUNTER' DESTINATION 'JCOTEST'.
  wait UP TO 10 SECONDS.
ENDDO.
CALL FUNCTION 'Z_GET_COUNTER' DESTINATION 'JCOTEST'
  IMPORTING
    get_value = value.
IF value <> loops.
  WRITE: / 'Error expecting ', loops, ', but get ', value, ' as counter value'.
ELSE.
  WRITE: / 'success!! COUNTER =', value.
ENDIF.
Java服務端代碼
import java.io.File;
import java.io.FileOutputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import com.sap.conn.jco.JCo;
import com.sap.conn.jco.JCoCustomRepository;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoFunctionTemplate;
import com.sap.conn.jco.JCoListMetaData;
import com.sap.conn.jco.JCoMetaData;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.ServerDataProvider;
import com.sap.conn.jco.server.JCoServer;
import com.sap.conn.jco.server.JCoServerContext;
import com.sap.conn.jco.server.JCoServerFactory;
import com.sap.conn.jco.server.JCoServerFunctionHandler;
import com.sap.conn.jco.server.JCoServerFunctionHandlerFactory;
public class StatefulServerExample {
   static String SERVER_NAME1 = "SERVER";
   static String DESTINATION_NAME1 = "ABAP_AS_WITHOUT_POOL";
   static String DESTINATION_NAME2 = "ABAP_AS_WITH_POOL";
   static {
      JCo.setTrace(4, null);// 打開調試
      Properties connectProperties = new Properties();
      // ******直連
      connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
            "192.168.111.137");
      connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
      connectProperties
            .setProperty(DestinationDataProvider.JCO_CLIENT, "800");
      connectProperties.setProperty(DestinationDataProvider.JCO_USER,
            "SAPECC");
      connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
            "sapecc60");
      connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
      createDataFile(DESTINATION_NAME1, "jcoDestination", connectProperties);
      // ******鏈接池
      connectProperties.setProperty(
            DestinationDataProvider.JCO_POOL_CAPACITY, "3");
      connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
            "10");
      createDataFile(DESTINATION_NAME2, "jcoDestination", connectProperties);
      // ******JCo sever
      Properties servertProperties = new Properties();
      servertProperties.setProperty(ServerDataProvider.JCO_GWHOST,
            "192.168.111.137");
      // TCP服務sapgw是固定的,後面的00就是SAP實例系統編號,也可直接是端口號(端口號能夠在
      // etc/server文件中找sapgw00所對應的端口號)
      servertProperties.setProperty(ServerDataProvider.JCO_GWSERV, "sapgw00");
      servertProperties
            .setProperty(ServerDataProvider.JCO_PROGID, "JCO_TEST");
      servertProperties.setProperty(ServerDataProvider.JCO_REP_DEST,
            DESTINATION_NAME2);
      servertProperties.setProperty(ServerDataProvider.JCO_CONNECTION_COUNT,
            "2");
      createDataFile(SERVER_NAME1, "jcoServer", servertProperties);
   }
   static void createDataFile(String name, String suffix, Properties properties) {
      File cfg = new File(name + "." + suffix);
      if (!cfg.exists()) {
         try {
            FileOutputStream fos = new FileOutputStream(cfg, false);
            properties.store(fos, "for tests only !");
            fos.close();
         } catch (Exception e) {
            throw new RuntimeException(
                   "Unable to create the destination file "
                         + cfg.getName(), e);
         }
      }
   }
   static class MyFunctionHandlerFactory implements
         JCoServerFunctionHandlerFactory {
      // 會話上下文,其實就是每個ABAP端都有這樣一個全局對象,同一ABAP客戶端共享着同一個這個的會話上下文對象
      class SessionContext {
         // 用於存儲(緩存)會話中的數據
         Hashtable<String, Object>cachedSessionData = new Hashtable<String, Object>();
         // 其餘會話數據......
      }
      // 每一個任務單元LUW都有本身的會話上下文,Key爲任務會話ID(好比同時多個ABAP客戶端調用當前同一Java服務端時,
      // 會產會多個任務會話,並且每次調用時,同一ABAP客戶端所傳遞過來的會話ID是同一個)
      private Map<String, SessionContext>statefulSessions = new Hashtable<String, SessionContext>();
      private ZGetCounterFunctionHandler zGetCounterFunctionHandler = new ZGetCounterFunctionHandler();
      private ZIncrementCounterFunctionHandler zIncrementCounterFunctionHandler = new ZIncrementCounterFunctionHandler();
      public JCoServerFunctionHandler getCallHandler(
            JCoServerContext serverCtx, String functionName) {
         JCoServerFunctionHandler handler = null;
         if (functionName.equals("Z_INCREMENT_COUNTER")) {
            handler = zIncrementCounterFunctionHandler;
         } else if (functionName.equals("Z_GET_COUNTER")) {
            handler = zGetCounterFunctionHandler;
         }
         if (handler instance of StatefulFunctionModule) {
            SessionContext cachedSession;
            // 若是是某任務LUW中第一次調用時,則serverCtx服務上下文爲非狀態,需設置爲狀態調用
            if (!serverCtx.isStatefulSession()) {
                // 設置爲狀態調用,這樣在有狀態調用的狀況下,上下文中會攜帶會話ID
                serverCtx.setStateful(true);
                cachedSession = new SessionContext();// 新建會話
                // 將會話存儲在映射表中,以便某個任務裏每次遠程調用均可以拿到同一會話
               statefulSessions.put(serverCtx.getSessionID(),
                      cachedSession);
            } else {// 非第一次調用
                cachedSession = statefulSessions.get(serverCtx
                      .getSessionID());
                if (cachedSession == null)
                   throw new RuntimeException(
                         "Unable to find the session context for session id "
                               + serverCtx.getSessionID());
            }
            // 爲遠程函數處理器設置會話
            ((StatefulFunctionModule) handler)
                   .setSessionData(cachedSession.cachedSessionData);
            return  handler;
         }
         // null leads to a system failure on the ABAP side
         retur nnull;
      }
      public void sessionClosed(JCoServerContext serverCtx, String message,
            boolean error) {
         System.out.println("Session " + serverCtx.getSessionID()
                + " was closed " + (error ? message : "by SAP system"));
         statefulSessions.remove(serverCtx.getSessionID());
      }
   }
   static abstract class StatefulFunctionModule implements
         JCoServerFunctionHandler {
      Hashtable<String, Object>sessionData;
      public void setSessionData(Hashtable<String, Object> sessionData) {
         this.sessionData = sessionData;
      }
   }
   // 獲取計數器值
   static class ZGetCounterFunctionHandler extends StatefulFunctionModule {
      public void handleRequest(JCoServerContext serverCtx,
            JCoFunction function) {
         System.out.println("ZGetCounterFunctionHandler: return counter");
         Integer counter = (Integer) sessionData.get("COUNTER");
         if (counter == null) {
            function.getExportParameterList().setValue("GET_VALUE", 0);
         } else {
            function.getExportParameterList().setValue("GET_VALUE",
                   counter.intValue());
         }
      }
   }
   static class ZIncrementCounterFunctionHandler extends
         StatefulFunctionModule {
      public void handleRequest(JCoServerContext serverCtx,
            JCoFunction function) {
         System.out
                .println("ZIncrementCounterFunctionHandler: increase counter");
         Integer counter = (Integer) sessionData.get("COUNTER");
         if (counter == null)
            // 向會話上下文中存儲存儲計數結果:若是是第一次調用計數功能,則爲1
            sessionData.put("COUNTER", new Integer(1));
         else
            // 向會話上下文中存儲存儲計數結果:若是不是第一次調用計數功能,則在原有計數器值基礎之上加1
            sessionData.put("COUNTER", new Integer(counter.intValue() + 1));
      }
   }
   public static void main(String[] args) {
      // Z_GET_COUNTER、Z_INCREMENT_COUNTER兩個函數無需在ABAP中定義,經過Java註冊了這兩個函數對象
      JCoListMetaData expList = JCo.createListMetaData("EXPORT");
      expList.add("GET_VALUE", JCoMetaData.TYPE_INT, 2, 2, 0, null, null,
            JCoListMetaData.EXPORT_PARAMETER, null, null);
      expList.lock();
      JCoFunctionTemplate fT1 = JCo.createFunctionTemplate("Z_GET_COUNTER",
            null, expList, null, null, null);
      JCoCustomRepository cR = JCo
            .createCustomRepository("MyCustomRepository");
      cR.addFunctionTemplateToCache(fT1);
      JCoFunctionTemplate fT2 = JCo.createFunctionTemplate(
            "Z_INCREMENT_COUNTER", null, null, null, null, null);
      cR.addFunctionTemplateToCache(fT2);
      String serverName = "SERVER";
      JCoServer server;
      try {
         server = JCoServerFactory.getServer(serverName);
      } catch (JCoException ex) {
         throw new RuntimeException("Unable to create the server "
                + serverName + ", because of " + ex.getMessage(), ex);
      }
      String repDest = server.getRepositoryDestination();
      if (repDest != null) {
         try {
            cR.setDestination(JCoDestinationManager.getDestination(repDest));
         } catch (JCoException e) {
            e.printStackTrace();
            System.out
                   .println(">>> repository contains static function definition only");
         }
      }
      server.setRepository(cR);
      MyFunctionHandlerFactory factory = new MyFunctionHandlerFactory();
      server.setCallHandlerFactory(factory);
      server.start();
   }
}
JCo RFC函數異常總結
異常:(103)RFC_ERROR_LOGON_FAILURE: ##.#####,####
緣由:該異常通常是帳戶密碼錯誤致使,首先檢查帳號密碼(最好是大寫),其次頗有多是JCO的鏈接池配置文件在Server上properties文件沒有實時的更新過來,致使還在使用原登陸配置鏈接,該文件通常在Server的/bin目錄下面。
其餘一些異常的蒐集總結:
(103) RFC_ERROR_LOGON_FAILURE: Name or password is incorrect (repeat logon)
用戶名密碼多是錯誤或者用戶無權限,確認用戶,必要時聯繫SAP負責人,檢查用戶
(101) RFC_ERROR_PROGRAM: Missing R3NAME=... or ASHOST=... in connect_param in RfcOpenEx
call信息沒有填寫完整,檢查配置文件各個SAP配置信息是否完整
com.sap.mw.jco.JCO$Exception: (102) RFC_ERROR_COMMUNICATION: Connect to SAP gateway failed
IP地址錯誤
(102) RFC_ERROR_COMMUNICATION:Connect to message server failed
組權限訪問 server文件沒更新
(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
找不到行
(122)JCO_ERROR_CONVERSION: Integer '4234243' has to many digits at field PO_ITEM
輸入參數不能插入SAP函數輸入字段中

轉載:https://blog.csdn.net/u011229925/article/details/72841001java

相關文章
相關標籤/搜索