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 "

                +

相关文章
相关标签/搜索