前言

名词说明

RFC:(Remote Function Call,远程功能调用)。
SAP系统:是ERP解决方案的先驱,也是全世界排名第一的ERP软件,可以为各种行业、不同规模的企业提供全面的解决方案。(只需要记住,里面存了很多各种各样的数据,我们在从sap取数据的时候,sap提供了一种rfc接口的方式)

代码结构

在这里插入图片描述

各个文件的作用

  1. CallRfc:调用方法。
  2. JCOProvider:它实现了DestinationDataProvider类,用于安全的自定义连接参数。
  3. RfcManager:rfc接口的调用管理类,会读取源文件下的.properties的参数配置,然后进行连接测试,并保持连接。

连接方式

连接方式分为直连方式和连接池方式:
直连方式:

static String ABAP_AS = "ABAP_AS_WITHOUT_POOL";

连接池方式:

static String ABAP_AS_POOLED = "ABAP_AS_WITH_POOL";

连接池方式与直接连接方式主要是以下有所区别:
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");

sapjco3.jar包导入

部分小伙伴导入sapjco3.jar包的时候可能会出现无法找到依赖包的问题,这里就windows系统给出解决方案。
下载链接: sapjco3(没有积分的小伙伴私聊获取)
下载后,将sapjco3.dll文件放入window/system32下即可。

源码示例

CallRfc.java

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoParameterList;
import com.sap.conn.jco.JCoTable;


/** 
 * @Description: rfc调用接口
 * @author jiangzl
 * @date 2020-12-21 
 * @ClassName: CallRfc
 *  
 */
@Service
public class CallRfc
{
    public List<Map<String,Object>> CallSpreadRfc(Map<String,Object> paramMap, List<Map<String, Object>> paramList) {
        List<Map<String,Object>> list = new ArrayList<>();
        // 获取RFC 对象
        JCoFunction function = RfcManager.getFunction("RFC_NAME");
        //获取参数列表
        JCoParameterList importParam = function.getImportParameterList();
        importParam.setValue("PARAM_1", paramMap.get("date"));
        //获取表字段
        JCoParameterList inTableParam = function.getTableParameterList();
        if (null!=paramMap.get("params") && !StringUtil.isEmpty(paramMap.get("params").toString()))
        {
            JCoTable tableInD = inTableParam.getTable("PARAMS");
            String[] werks = paramMap.get("params").toString().split(",");
            for (int i = 0; i < werks.length; i++)
            {
                tableInD.appendRow(); 
                tableInD.setValue("PARAMS",werks[i]);
            }
        }
        
        // 执行RFC
        RfcManager.execute(function);
        
        // 获取RFC返回的字段值
        JCoParameterList exportParam = function.getExportParameterList();
        // 遍历RFC返回的表对象
        JCoTable tb = function.getTableParameterList().getTable("RESULT_NAME");
        for (int i = 0; i < tb.getNumRows(); i++) {
            tb.setRow(i);
            Map<String,Object> map = new LinkedHashMap<>();
            map.put("name1", tb.getString("NAME1"));
            ......
            list.add(map);
        }
        paramList.addAll(list);
        return list;
    }
}

JCOProvider

import java.util.HashMap;
import java.util.Properties;

import com.sap.conn.jco.ext.DataProviderException;
import com.sap.conn.jco.ext.DestinationDataEventListener;
import com.sap.conn.jco.ext.DestinationDataProvider;

/** 
 * @Description: jco服务提供
 * @author jiangzl
 * @date 2020-12-21 
 * @ClassName: JCOProvider
 *  
 */
public class JCOProvider implements DestinationDataProvider {
    private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
    private DestinationDataEventListener eL;

    @Override
    public Properties getDestinationProperties(String destinationName) {
        try
        {
            //read the destination from DB
            Properties p = secureDBStorage.get(destinationName);

            if(p!=null)
            {
                //check if all is correct, for example
                if(p.isEmpty())
                    throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
                return p;
            }

            return null;
        }
        catch(RuntimeException re)
        {
            throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
        }
    }

    @Override
    public void setDestinationDataEventListener(
            DestinationDataEventListener eventListener) {
        this.eL = eventListener;

    }

    @Override
    public boolean supportsEvents() {
        return true;
    }

    //implementation that saves the properties in a very secure way
    public void changePropertiesForABAP_AS(String destName, Properties properties) {
        synchronized(secureDBStorage)
        {
            if(properties==null)
            {
                if(secureDBStorage.remove(destName)!=null)
                    eL.deleted(destName);
            }
            else
            {
                secureDBStorage.put(destName, properties);
                eL.updated(destName); // create or updated
            }
        }
    }
}

RfcManager

/** 
 * @Description: rfc调用管理类
 * @author jiangzl
 * @date 2020-12-21 
 * @ClassName: RfcManager
 *  
 */
public class RfcManager
{
    private static Logger logger = Logger.getLogger(RfcManager.class);

    private static String ABAP_AS_POOLED = "ABAP_AS_POOL";


    private static JCOProvider provider = null;

    private static JCoDestination destination = null;

    static {
        Properties properties = loadProperties();

        provider = new JCOProvider();

        // catch IllegalStateException if an instance is already registered
        try {
            Environment.registerDestinationDataProvider(provider);
        } catch (IllegalStateException e) {
            logger.debug(e);
        }

        provider.changePropertiesForABAP_AS(ABAP_AS_POOLED, properties);
    }

    public static Properties loadProperties() {
        RfcManager manager = new RfcManager();
        Properties prop = new Properties();
        try {
            prop.load(manager.getClass().getResourceAsStream(
                    "/sap_conf.properties"));
        } catch (IOException e) {
            logger.debug(e);
        }
        return prop;
    }

    public static JCoDestination getDestination() throws JCoException {
        if (destination == null) {
            destination = JCoDestinationManager.getDestination(ABAP_AS_POOLED);
        }
        return destination;
    }

    public static JCoFunction getFunction(String functionName) {
        JCoFunction function = null;
        try {
            function = getDestination().getRepository()
                    .getFunctionTemplate(functionName).getFunction();
        } catch (JCoException e) {
            logger.error(e);
        } catch (NullPointerException e) {
            logger.error(e);
        }
        return function;
    }

    public static void execute(JCoFunction function) {
        logger.debug("SAP Function Name : " + function.getName());
        JCoParameterList paramList = function.getImportParameterList();

        if (paramList != null) {
            logger.debug("Function Import Structure : " + paramList.toString());
        }

        try {
            function.execute(getDestination());
        } catch (JCoException e) {
            try
            {
                logger.info("Destination client:"+getDestination().getClient());
                logger.info("Destination serverHost:"+getDestination().getApplicationServerHost());
                logger.info("Destination destinationName:"+getDestination().getDestinationName());
            } catch (JCoException e1)
            {
                e1.printStackTrace();
            }
            logger.error("Destination error : " + e);
        }
        paramList = function.getExportParameterList();

        if (paramList != null) {
            logger.debug("Function Export Structure : " + paramList.toString());
        }
    }

    public static String ping() {
        String msg = null;
        try {
            getDestination().ping();
            msg = "Destination " + ABAP_AS_POOLED + " is ok";
        } catch (JCoException ex) {
            //msg = StringUtil.getExceptionTrace(ex);
            logger.info(ex.getMessage());
        }
        logger.debug(msg);
        return msg;
    }

    public static void main(String[] args) {
        RfcManager.ping();
    }
}

pom文件

<dependency>
	<groupId>com.github.easonjim</groupId>
	<artifactId>com.sap.conn.jco.sapjco3</artifactId>
	<version>3.0.11</version>
</dependency>

报错信息解决

错误描述

JAVA连接SAP时出现的错误(102) JCO_ERROR_COMMUNICATION: Initialization of repository destination ABAP_AS_WITH_POOL failed: Connect to SAP gateway failed
OCATION    CPIC (TCP/IP) on local host with Unicode
ERROR       partner 'ip:port' not reached
TIME        Sun Apr 28 18:47:59 2019
RELEASE     721
COMPONENT   NI (network interface)
VERSION     40
RC          -10
MODULE      nixxi.cpp
LINE        3289
DETAIL      NiPConnect2: ip:port
SYSTEM CALL connect
ERRNO       10060
ERRNO TEXT  WSAETIMEDOUT: Connection timed out
COUNTER     2

错误分析

     对于gateway 这种错误,一般都是网关配置的错误。使用外部系统调用sap时(解决方法以java代码为例),请求需要知道目标的地址和端口。两个缺一不可。
地址

  1. IP地址填错,仔细检查即可;
  2. java配置方式出错,以私有配置的方式取代properties的方式。可看组/服务器选择连接方式的配置示例;
  3. 检查GROUP分组参数,以及连接方式。

注意:JCO_SAPROUTER并不是可选的,值得是默认路由端口,一般为3600,如果有特殊规定,记得在这里进行设置,或者在services中进行设置。

端口
5. 未建立 Jco Serever 监听服务时相关设置。

windous

	进入 %SystemRoot%\System32\drivers\etc
    1.修改 services文件,在services文件尾部 将  jco.server.gwserv:sapgw00 属性值 sapgw00 加入 SAP 端口映射
      sapdp00  3200/tcp #SAP Server
      sapgw00  3300/tcp #SAP Gateway
    2.修改 hosts文件,在 hosts中 将 jco.server.gwhost:gmdev01  属性值  gmdev01 加入 SAP服务器IP 地址映射
      10.86.95.121       gmdev01
    3.具体示例
           参考项目目录内的 services/hosts 文件

linux

1.执行  vi /etc/hosts
              修改 hosts文件,在 hosts中 将 jco.server.gwhost:gmdev01  属性值  gmdev01 加入 SAP服务器IP 地址映射
       10.86.95.121       gmdev01
     2.执行 vi /etc/services
              修改 services文件,在services文件尾部 将  jco.server.gwserv:sapgw00 属性值 sapgw00 加入 SAP 端口映射
       sapdp00  3200/tcp #SAP Server
       sapgw00  3300/tcp #SAP Gateway

组/服务器选择连接方式的配置

        connectProperties.setProperty(DestinationDataProvider.JCO_MSHOST, "0.0.0.0");
        connectProperties.setProperty(DestinationDataProvider.JCO_R3NAME,  "XXX");
        connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, "888");
        connectProperties.setProperty(DestinationDataProvider.JCO_USER,   "XXX");
        connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, "XXX");
        connectProperties.setProperty(DestinationDataProvider.JCO_GROUP, "RFC");
        connectProperties.setProperty(DestinationDataProvider.JCO_SAPROUTER,"   xxxxxx   ");
        connectProperties.setProperty(DestinationDataProvider.JCO_LANG,   "en");
        createDataFile(ABAP_MS, "jcoDestination", connectProperties);

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐