Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数...

安装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
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第1张图片

创建JCo3连接
JCo连接到SAP服务器有两种方法,分别是直连和通过连接池进行连接。其差别在于,打开直连连接后可以一直保持连接;连接池则是在需要时才建立连接,连接暂不需要时,将被释放回连接池,再分配给其他用户使用。在网络服务器应用程序里,一般采用连接池进行连接SAP服务器。
如果是老系统,可能要还注意远程登录用户的类型:
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第2张图片

直连
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 HashMapdestinations;
   private static MyDestinationDataProvider provider = new MyDestinationDataProvider();
   private MyDestinationDataProvider() {// 单例模式
   if (provider == null) {
         destinations = new HashMap();
       }
   }
   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:
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第3张图片
 
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第4张图片
 
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第5张图片

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 BlockingQueuequeue = new LinkedBlockingQueue();
   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 Hashtablesessions = new Hashtable();
      // ThreadLocal:线程全局变量局部化,即将原本共享的属性全局变量在每个线程中都拷贝一份,不会让它们再在不同的线程中共享,
      // 每个线程拿到的都是自己所独享的,所以看似全局共享的属性在多线程情况下,也不会出现多线程并发问题
      // 当前线程所使用的远程会话
      static ThreadLocallocalSessionReference = new ThreadLocal();
      // 同步器:倒计时闭锁;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 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类型映射关系表
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第6张图片
 
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第7张图片

ABAP访问Java服务
ABAP(作为Clint端),调用JAVA(作为服务器端)。
SAP通过JCO反向调用JAVA的RFC服务其实也是相对简单的,只是在JAVA端需要使用JCO创建一个RFC服务,然后在SAP端注册这个服务程序。
首先,JCo服务器程序需在网关中进行注册,在SM59中,定义一个连接类型为T的远程目标
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第8张图片
 
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第9张图片

RFC目标系统:是ABAP RFC调用Java时,需指定的目标系统名。
Program ID:是JAVA程序中使用的
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第10张图片

Gateway Host与Gateway service值来自以下界面(Tcode:SMGW):
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第11张图片

这里的Geteway Host就是下图的应用程序服务器地址。 TCP服务sapgw是固定的,后面的00就是下图的系统编号。
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第12张图片
 
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第13张图片

所有配置好且Java服务器代码跑起来后,点击“Connection Test”按钮,如不出现红色文本,则表示链接成功(注:此时需要ServerDataProvider.JCO_PROGID设置的Program ID要与SM59中设置的相同,否则测试不成功。另要注意的是:即使Java服务器设置的Program ID乱设置,Java服务端还是能启起来,但ABAP测试连接时会不成功,也就代表ABAP不能调用Java):
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第14张图片

此时可以通过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
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第15张图片
 
Java(JCo3)与SAP系统相互调用 外部系统(Java)调用BAPI函数..._第16张图片

请参考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 {
      // 存储事务状态信息
      MapavailableTIDs = new Hashtable();
      // 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 {
         // 用于存储(缓存)会话中的数据
         HashtablecachedSessionData = new Hashtable();
         // 其他会话数据......
      }
      // 每个任务单元LUW都有自己的会话上下文,Key为任务会话ID(比如同时多个ABAP客户端调用当前同一Java服务端时,
      // 会产会多个任务会话,而且每次调用时,同一ABAP客户端所传递过来的会话ID是同一个)
      private MapstatefulSessions = new Hashtable();
      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 {
      HashtablesessionData;
      public void setSessionData(Hashtable 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函数输入字段中

你可能感兴趣的:(jco)