Java(JCo3)与SAP系统相互调用

外部系统(Java)调用BAPI函数

在调用BAPI时,SAP为各编程环境(VBC++Java等)提供了RFC库及SAP连接器(如JcoNco等)。这些类库中的RFC API封闭了外部系统和SAP的连接细节

安装JCo3

JCo32位和64为之分,32位的JVM选择32位的JCO, 64位的JVM选择64位的JCO, windows环境,选择相应的sapjco3.dll, UnixLinux环境选择合适的sapjco3.so

32位下载:http://pan.baidu.com/s/1jGr6jSa

64位下载:http://pan.baidu.com/s/1i3mO2rj

 

解压后将sapjco3.dll拷贝到c:/windows/system32C:\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

image210

创建JCo3连接

JCo连接到SAP服务器有两种方法,分别是直连和通过连接池进行连接。其差别在于,打开直连连接后可以一直保持连接;连接池则是在需要时才建立连接,连接暂不需要时,将被释放回连接池,再分配给其他用户使用。在网络服务器应用程序里,一般采用连接池进行连接SAP服务器。

 

如果是老系统,可能要还注意远程登录用户的类型:

image211

直连

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端需要在每次调用后,保留每一次被调用后函数组的内存状态,直到最后一次调用完成止,这需要JavaABAP配合来完成(Java在第一次调用时,调用JCoContext.beginJCoContext.end这两个方法,告诉SAP这一调用过程将是有状态调用,然后SAP端会自动保留内存状态)

 

如果主调程序是ABAP(即ABAP程序调用ABAP函数),此种情况下没有特殊的要求,直接调用就即可,只要是在同一程序的同一运行会话其间(会话相当于Java中的同一线程吧),不管是多次调用同一个函数、还是调用同一函数组中的不同函数,则都会自动保留内存状态,直到程序运行结束,这是系统自己完成的。实质上一个函数组就相当于一个类,函数组中不同的函数就相当于类中不同的方法、全局变量就相当于类中的属性,所以只要是在同一程序的同一运行会话期间,调用的同一函数所在的函数组中的全局变量都是共享的,就好比调用一类的某个方法时,该方法设置了某个类的属性,再去调用该类的其它方法时,该属性值还是保留了以前其它方法修改后的状态值。

 

该示例采用线程方式来演示,状态调用只要保证同一线程中多次远程方法调用采用的都是同一会话即可。当然更简单的方法是不使用多线程,而直接在主线程中使用同一个物理远程连接调用即可(但还是需要调用JCoContext.beginJCoContext.end这两个方法,告诉SAP端需要保留内存状态,直接程序结束)

 

这里使用的函数是从标准程序COUNTER函数组拷贝而来,只不过系统中提供的不支持RFC调用,拷贝过来后修改成RFM

image212

image213

image214

 

 

 

 

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 RuntimeJCo运行时环境需) 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功能函数(如这里的incrementCountergetCounter)之前,JCo框架会去调用

            // SessionReferenceProvidergetCurrentSessionReference()方法,

            // 取得当前任务所对应的远程会话,确保同一任务是在同一远程会话中执行的

            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);// 开启状态调用,会话在beginend之间不会被重置与关闭,这样

            // 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;

      }

 

   }

 

   // 工作线程,用来执行前面定义的任务:StatelessMultiStepExampleStatefulMultiStepExample

   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框架的

                // SessionReferenceProvidergetCurrentSessionReference()方法来读取

                // ****不管是否有无状态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类型映射关系表

image215

image216

ABAP访问Java服务

ABAP(作为Clint端),调用JAVA(作为服务器端)。

 

SAP通过JCO反向调用JAVARFC服务其实也是相对简单的,只是在JAVA端需要使用JCO创建一个RFC服务,然后在SAP端注册这个服务程序。

 

 

首先,JCo服务器程序需在网关中进行注册,在SM59中,定义一个连接类型为T的远程目标

image217

image218

RFC目标系统:是ABAP RFC调用Java时,需指定的目标系统名。

Program ID:是JAVA程序中使用的

image219

Gateway HostGateway service值来自以下界面(TcodeSMGW

image220

这里的Geteway Host就是下图的应用程序服务器地址。 TCP服务sapgw是固定的,后面的00就是下图的系统编号

image221

image222

 

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

image223

此时可以通过SMGW来观测连接:

image224

连接异常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

image225

image226

 

请参考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_CONNECTIONABAP本地不存,因此还需要在Java服务端创建此函数对象库,与此函数远程调用的配套示例代码为 Java 端的staticRepository() 方法
  DESTINATION
'JCOTEST'"SM59中配置的RFC目标系统
 
EXPORTING
    requtext             
= 'ABAP端发送的消息,并需要回传'"需发送的文本
 
IMPORTING
    resptext             
= resptext"服务器响应文本
    echotext             
= echotext"回显文本
 
EXCEPTIONS
    system_failure       
= MESSAGE rfc_mess
    communication_failure
= 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       
= message rfc_mess
    communication_failure
= message rfc_mess.

DATA: tid TYPE arfctid.
CALL FUNCTION 'ID_OF_BACKGROUNDTASK'
 
IMPORTING
    tid
= tid."此事务IDJava端打印出来的是一样的
WRITE
:/ 'TID = ',tid.

COMMIT WORK AND WAIT .

DATA: errortab TYPE STANDARD TABLE OF arfcerrors WITH HEADER LINE.

DO.
 
"获取事务状态,如果事务运行完,则sy-subrc0
 
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端目前事务处理的情况(状态),即JavaABAP之间的事务状态的交流

    */

   static class MyTIDHandler implements JCoServerTIDHandler {

      // 存储事务状态信息

      Map<String, TIDState>availableTIDs = new Hashtable<String, TIDState>();

 

      // 18662702337

      // 当一个事务性RFMABAP端进行调用时,会触发此方法

      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);