最近加入的新项目需要和SAP进行接口对接,想尝试将项目代码中关于SAP连接部分剥离出来,可能存在些错误,欢迎大家指正!!!
项目环境:Windows, IDEA 2023.1, jdk11, Spring Boot 2.7.2
SAP Java Connector(SAP JCo)
SAP Jco 是一个可以使JAVA应用程序能够通过SAP的RFC协议与SAP进行通信的开发库。SAP JCo支持双向通信:入站远程函数调用(Java调用ABAP)以及出站远程函数调用(ABAP调用Java)。JCo同时也支持Connection Pools和Direct两种方式的连接。直接连接需要开发者来控制连接的创建和释放,使用连接池方式可以让池来管理连接的分配、管理和释放 。
1.下载
官网地址: SAP Java Connector,里面有详细的操作步骤和一些要求(因为项目是web开发我就没有进行下载,直接进行了下一步操作)
2.安装
Wriprin's Cloudreve 相关文件下载链接
- 在 Windows 服务器中,将 sapjco3.jar 和 sapjco3.dll 文件放到 项目的 lib 文件夹。
- 在 Linux 服务器中,将 sapjco3.jar 和 libsapjco3.so 放到 项目的 lib 文件夹中。
- 在pom中引入依赖,引入jar包
<dependency> <groupId>com.sap</groupId> <artifactId>sapjco3</artifactId> <version>1.0</version> <scope>system</scope> <systemPath>${project.basedir}/lib/sapjco3.jar</systemPath> </dependency>
连接SAP
这个版本是比较复杂的,为了解耦合将很多内容进行了拆分,但是认真看也能看到懂,目录结构也是比较干净的,纯净版无废话版本(等找个时间再撸...)
1.编辑yml
- 根据SAP信息编辑application.yml,同时在为它写一个文件用于读取这个配置,注意保证前缀的对应
2.创建一个配置对象
内容上其实和配置文件上高度重合的,与配置文件不同的是多了个defaultkey,用于记录自定义的destinationName
3.创建一个类实现DestinationDataProvider
DestinationDataProvider相比较于存储在xxx.jcodestination文件中可以更灵活的存放SAP连接参数,在同时还能存放了多个destination。
在实现这个类的同时,必须完成两件事:
①:改写getDestinationProperties();
②:通过Environment.registerDestinationDataProvide() 方法进行注册,使其生效
这个代码主要可以分为四部分,可以根据自己的需求书写:
(1)为了实现线程安全加入了内部类的懒汉单例模式
(2) 注册
(3)重写getDestinationProperties(),这部分重写是因为我把destination以hashMap的方式存储了,key=settings.defaultKey
(4)创建destination,并且放入hashMap中
package com.example.jcodemo.config;
import com.example.jcodemo.client.semaphore.JCoClientCreatedOnErrorSemaphore;
import com.sap.conn.jco.ext.*;
import org.apache.commons.lang.StringUtils;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
/**
* FileName: JCoDataProvider
* @Author: Miao
* Date: 2024/3/14:11:42
* Description: JCo destination and server data provider
* 将连接变量信息存放在内存里,安全性更高
*/
public class JCoDataProvider implements DestinationDataProvider {
/**
* 定义成员变狼
*/
private final Map<String, Properties> clientSettingsProviders;
private DestinationDataEventListener destinationDataEventListener;
/**
* 初始化成员
*/
private JCoDataProvider() {
this.clientSettingsProviders = new ConcurrentHashMap<>();
}
/**
* 利用内部类懒汉加载的单例模式,保证只有一个实例对象
*/
private static class JCoDataProviderInstance {
private static final JCoDataProvider INSTANCE = new JCoDataProvider();
}
public static JCoDataProvider getSingleton() { // singleton
return JCoDataProviderInstance.INSTANCE;
}
/**
* 注册provider,使其生效
*/
public static void registerInEnvironment(){
Environment.registerDestinationDataProvider(JCoDataProviderInstance.INSTANCE);
}
/**
* 重写getDestinationProperties()方法,后面用来获取和执行SAP 函数的时候会用到,所以一定要写对
* @param destinationName
* @return
*/
@Override
public Properties getDestinationProperties(String destinationName) {
if(clientSettingsProviders.containsKey(destinationName)){
return clientSettingsProviders.get(destinationName);
}
return null;
}
@Override
public boolean supportsEvents() {
return false;
}
@Override
public void setDestinationDataEventListener(DestinationDataEventListener destinationDataEventListener) {
this.destinationDataEventListener = destinationDataEventListener;
}
/**
* 创建destination,并且放到成员变量中
* @param settings
*/
public void registerClientSettings(JCoSettings settings) {
clientSettingsProviders.compute(settings.getDefaultKey(), (clientName, clientSettings) -> {
// exist check
if (clientSettings != null) {
throw new RuntimeException("Destination: [" + clientName + "] has been already registered.");
}
// refer
return referDestinationData(settings);
});
}
public static Properties referDestinationData
(JCoSettings settings){
Properties properties = new Properties();
if(StringUtils.isNotEmpty( settings.getAshost())){
properties.setProperty(DestinationDataProvider.JCO_ASHOST,settings.getAshost());
}
properties.setProperty(DestinationDataProvider.JCO_SYSNR, settings.getSysnr());
properties.setProperty(DestinationDataProvider.JCO_CLIENT, settings.getClient());
properties.setProperty(DestinationDataProvider.JCO_USER, settings.getUser());
properties.setProperty(DestinationDataProvider.JCO_PASSWD,settings.getPassword());
properties.setProperty(DestinationDataProvider.JCO_LANG, settings.getLanguage());
// pool settings
properties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, settings.getPoolCapacity());
properties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, settings.getPeakLimit());
//zhengqh
if (StringUtils.isNotEmpty(settings.getMshost())) {
properties.setProperty(DestinationDataProvider.JCO_MSHOST, settings.getMshost());
}
if (StringUtils.isNotEmpty(settings.getGroup())) {
properties.setProperty(DestinationDataProvider.JCO_GROUP, settings.getGroup());
}
if (StringUtils.isNotEmpty(settings.getR3name())) {
properties.setProperty(DestinationDataProvider.JCO_R3NAME, settings.getR3name());
}
if (StringUtils.isNotEmpty(settings.getMsserv())) {
properties.setProperty(DestinationDataProvider.JCO_MSSERV, settings.getMsserv());
}
return properties;
}
}
4.创建一个JCoClient对象
用于管理和SAP系统的一些列操作(初始化连接,释放,获取配置,函数处理等等),并且在启动程序中直接注入到Spring容器中,后面就可以直接使用啦
package com.example.jcodemo.client;
import com.example.jcodemo.config.JCoDataProvider;
import com.example.jcodemo.config.JCoSettings;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoFunction;
/**
* FileName: DefaultJCoClient
* @Author: Miao
* Date: 2024/3/13:15:03
* Description: Deafault implement of {@link JCoClient}
*/
public class JCoClient{
private final JCoSettings settings;
public JCoClient(JCoSettings settings) {
this.settings = settings;
initJCoConnection(settings);
}
/**
* 创建client
* @param settings {@link JCoSettings}
*/
public static void initJCoConnection(JCoSettings settings) {
try {
// register client properties.
JCoDataProvider.getSingleton().registerClientSettings(settings);
//ping test
JCoDestinationManager
.getDestination(settings.getDefaultKey())
.ping();
} catch (JCoException e) {
throw new RuntimeException("Unable to create:["+settings.getDefaultKey()+"]",e);
}
}
/**
* get Jco destination
* @return The destination {@link JCoDestination}
*/
public JCoDestination getDestination() {
try {
return JCoDestinationManager
.getDestination(this.settings.getDefaultKey());
}catch (JCoException ex) {
throw new RuntimeException(ex);
}
}
/**
* Release a connection
*/
public void release() {
JCoDataProvider.getSingleton().unRegisterClientSettings(settings.getDefaultKey());
}
/**
* Get client configuration
* @return The configuration {@link JCoSettings}
*/
public JCoSettings getSettings() {
return this.settings;
}
public void close() throws Exception {
this.release();
}
}
5.调用RFC函数
RFC函数传入参数接口/返回接口的方法有较多,想要了解更多的可以阅读官方文档SAP Java Connector (Netweaver 2004S SPS 09)
package com.example.jcodemo.demos.web;
import com.example.jcodemo.client.JCoClient;
import com.example.jcodemo.demos.web.Pojo.ZWMSI01Res;
import com.sap.conn.jco.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
*/
@Controller
public class BasicController {
@Autowired
private JCoClient client;
// http://127.0.0.1:8080/hello?name=lisi
@RequestMapping("/executeFunction")
@ResponseBody
public String executeFunction(@RequestParam(name = "name", defaultValue = "ZWMSI01") String name) throws JCoException {
//1.获取函数RFC函数对象
JCoFunction function = client.getDestination().getRepository().getFunctionTemplate(name).getFunction();
//2.设置输入参数(如果有)
// JCoParameterList importParameterList = function.getImportParameterList();
// importParameterList.setValue("I_FLAG","X");
//3.调用并获取返回值
JCoResponse response = new DefaultRequest(function).execute(client.getDestination());
//4.封装返回值
Map<String, Object> invokeResult = new HashMap<>();
for (JCoField jCoField : response) {
String fieldName = jCoField.getName();
JCoTable tableValue = function.getTableParameterList().getTable(fieldName);
if(tableValue.isEmpty()){
invokeResult.put(fieldName,null);
break;
}
ArrayList<ZWMSI01Res> resArrayList = new ArrayList<>();
ZWMSI01Res zwmsi01Res = new ZWMSI01Res();
for (int i = 0; i < tableValue.getNumColumns(); i++) {
tableValue.setRow(i);
zwmsi01Res.setWerks(tableValue.getString("WERKS"));
zwmsi01Res.setVkorg(tableValue.getString("VKORG"));
zwmsi01Res.setName(tableValue.getString("NAME1"));
resArrayList.add(zwmsi01Res);
}
invokeResult.put(fieldName,resArrayList);
}
return invokeResult.toString();
}
static class DefaultRequest extends com.sap.conn.jco.rt.DefaultRequest {
DefaultRequest(JCoFunction function) {
super(function);
}
}
}