JCo3.0 是 Java 语言与 ABAP 语言双向通信的中间件。与之前 1.0/2.0 相比,是重新设计的产品,调用 API 和架构设计与 NCo3.0 比较类似。实际上,NCo3.0 的设计参考了 JCo3.0。从本篇开始,系统介绍 JCo3.0 代码编写的技术要点。
JCo3.0 安装
从 https://service.sap.com/connectors 可以下载 JCo3.0,注意下载的版本要与 操作系统和 JVM 版本(32 位还是 64位)匹配。将文件解压到目标文件夹。以 Windows 系统为例,主要的文件包括:
- sapjco3.dll
- sapjco3.jar
SAP 强烈推荐将这两个文件放在同一文件夹下。测试安装是否成功,可以在命令窗口下,进入安装文件夹,运行下面的命令:
java -jar sapjco3.jar
如果安装成功,应该显示如下界面:
JCoDestination
对象
JCoDestination
代表后端 SAP 系统,与之前版本显式连接到 SAP 系统不同,JCo3.0 运行时环境负责管理连接,包括建立连接和释放连接,开发者不需要关心与 SAP 的连接。我们先通过一个简单的例子,了解 JCo3.0 JCoDestination
类的一些要点。以下讲述基于 IDEA Community 版本,如果编程环境是 Eclipse,也大同小异。
- 新建一个 Java 项目,项目名为 JCo3Demo。
- 在项目文件夹下新建一个 packages 文件夹,将 sapjco3.jar 和 sapjco3.dll 拷贝到该文件夹下。选中该文件夹,右键,选择菜单项 Add as Library,作用是将 jar 包加入到 Build Path 中。
- 在项目的根文件夹下,新建一个文本文件,文件名命名为 ECC.jocdestination,在这个文件中设置与 SAP 系统连接的的相关参数。文件的内容如下:
#SAP Logon parameters!
#Tue Dec 08 16:41:30 CST 2015
jco.client.lang=EN
jco.client.client=001
jco.client.passwd=xxxxxx
jco.client.user=STONE
jco.client.sysnr=00
jco.client.ashost=192.168.65.100
对照SAP GUI,不难理解各个参数的作用:
环境准备好了,先来一段最简单的代码,测试是否可以连接到 SAP 系统:
package jco3.demo1;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import org.junit.Test;
public class JCoDestinationDemo {
public JCoDestination getDestination() throws JCoException {
/**
* Get instance of JCoDestination from file: ECC.jcodestination
* which should be located in the root folder of project
*/
JCoDestination dest = JCoDestinationManager.getDestination("ECC");
return dest;
}
@Test
public void pingDestination() throws JCoException {
JCoDestination dest = this.getDestination();
dest.ping();
}
}
代码说明:
JCoDestinationManager.getDestination("ECC")
从 ECC.jcodestination 文件中获取连接参数,实例化JCoDestination
对象。这里有一个重要的约定,JCoDestinationManager.getDestination("ECC")
方法,在项目的根目录中查找 ECC.jcodestination 文件,文件名为参数,在本例中即为 ECC, 文件的扩展名固定为 jcodestination。如果找到文件,从文件中获取连接参数。这是DestinationDataProvider
接口的一个默认实现,在开发和测试的时候还是很方便的,但如果在真实项目中使用,安全性和灵活性就不够。本文的后面介绍介绍解决方法。pingDestination()
方法调用JcoDestination
对象的ping()
方法测试与 SAP 系统的连接。
生成配置文件
刚才我们是手工编辑 ECC.jcodestination 文件,对于这个配置文件,很多连接参数来自于 DestinationDataProvider
接口,可以根据配置参数生成该配置文件,代码如下:
package jco3.demo2;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
import com.sap.conn.jco.ext.DestinationDataProvider;
import org.junit.Test;
public class DestinationFile {
public Properties setProperties() {
Properties props = new Properties();
props.setProperty(DestinationDataProvider.JCO_ASHOST, "192.168.44.100");
props.setProperty(DestinationDataProvider.JCO_SYSNR, "00"); // instance number
props.setProperty(DestinationDataProvider.JCO_USER, "STONE");
props.setProperty(DestinationDataProvider.JCO_PASSWD, "w123456");
props.setProperty(DestinationDataProvider.JCO_CLIENT, "001");
props.setProperty(DestinationDataProvider.JCO_LANG, "EN");
return props;
}
private void doCreateFile(String fName, String suffix, Properties props) throws IOException {
/**
* Write contents of properties into a text file
* which was named [fName+suffix.jcodestination]
*/
File cfg = new File(fName + "." + suffix);
if (!cfg.exists()){
// Create file output stream, not using append mode
FileOutputStream outStream = new FileOutputStream(cfg, false);
// store the properties in file output stream
// and also add comments
props.store(outStream, "SAP Logon parameters");
outStream.close();
}else{
throw new RuntimeException("Configuration file already exits.");
}
}
@Test
public void testCreateCfgFile() throws IOException {
Properties props = this.setProperties();
String fileName = "SAP_AS";
this.doCreateFile(fileName, "jcodestination", props);
}
}
代码说明:
-
setProperties()
方法属性参照DestinationDataProvider
类的常量设置 Properties 对象,并且返回。 -
doCreateFile()
方法根据要求的文件名和扩展名在项目的根文件夹下,创建名为 SAP_AS.jcodestination 的文本文件,文件的内容就是 Properties 实例的内容。 -
testCreateCfgFile()
方法,调用上面的方法,创建配置文件。
自定义配置文件
我们看到,默认情况下,SAP 对配置文件的路径和扩展名都不能改变,如果我们想把文件放在任意位置,扩展名也使用其他的扩展名,有没有办法?答案是有,方法是实现 DestinationDataProvider
接口,并改写 (override) getDestinationProperties()
方法,然后通过Environment.registerDestinationDataProvider()
方法进行注册。OK, 一起来看看代码:
第一步: 创建 DestinationDataProviderImpl
类,实现 DestinationDataProvider
接口;
DestinationDataProvider
接口实现的代码:
package jco3.demo3;
import com.sap.conn.jco.ext.DestinationDataEventListener;
import com.sap.conn.jco.ext.DestinationDataProvider;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class DestinationDataProviderImpl implements DestinationDataProvider {
private File dir;
private String destName;
private String suffix;
@Override
public Properties getDestinationProperties(String s) {
Properties props = null;
try {
props = this.loadProperties();
} catch (IOException e) {
e.printStackTrace();
}
return props;
}
@Override
public boolean supportsEvents() {
return false;
}
@Override
public void setDestinationDataEventListener(DestinationDataEventListener destinationDataEventListener) {
throw new UnsupportedOperationException();
}
public void setDestinationFile(File dir, String destName, String suffix) {
/**
* 指定 Destination file
*/
this.dir = dir;
this.destName = destName;
this.suffix = suffix;
}
private Properties loadProperties() throws IOException {
Properties props = null;
File cfgFile = new File(this.dir, this.destName + "." + this.suffix);
if (cfgFile.exists()){
FileInputStream inputStream = new FileInputStream(cfgFile);
props = new Properties();
props.load(inputStream);
inputStream.close();
}else{
throw new RuntimeException("Configuration file does not exits");
}
return props;
}
}
第二步: 创建 FileDestinationManager
类,在该类中,提供 getDestination()
方法,在方法中通过 Environment.registerDestinationDataProvider()
方法,将 DestinationDataProviderImpl
对象,注册到 Environment。代码如下:
package jco3.demo3;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.ext.Environment;
import java.io.File;
public class FileDestinationManager {
public static JCoDestination getDestination(String destName) throws JCoException {
File dir = new File("."); // current directory
String suffix = "txt";
DestinationDataProviderImpl providerImpl = new DestinationDataProviderImpl();
providerImpl.setDestinationFile(dir, destName, suffix);
Environment.registerDestinationDataProvider(providerImpl);
JCoDestination dest = JCoDestinationManager.getDestination(destName);
return dest;
}
}
我们看到,getDestination
方法中,文件的路径、文件名和扩展名,都是我们自己定义的。文件名通过参数来指定,从这里也可以看出,JCoDestinationManager.getDestination()
方法从哪里查找连接参数,取决于 Environment 注册的 DestinationDataProvider 接口的实现。
测试 FileDestinationDataManager, 代码如下:
package jco3.demo3;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoException;
import org.junit.Test;
public class TestFileDestinationManager {
@Test
public void pingDest() throws JCoException {
JCoDestination dest = FileDestinationManager.getDestination("SAP_AS");
dest.ping();
}
}
代码中设置 JCoDestination 参数
还记得 NCo3.0 可以将连接 SAP 的参数写在代码中吗? JCo3.0 也是可以的,方法的关键就是实现
DestinationDataProvider
接口,并改写 getDestinationProperties()
方法。不多说,上代码。
第一步:实现 DestinationDataProvider
接口
package jco3.demo4;
import com.sap.conn.jco.ext.DestinationDataEventListener;
import com.sap.conn.jco.ext.DestinationDataProvider;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class DestinationDataProviderImpl implements DestinationDataProvider {
/**
* DestinationDataProvider is of type interface.
* We define DestinationDataProviderImpl class to implements this interface
* so that we can define the SAP connection parameters more flexibly,
* not just in xxx.jcodestionation file.
*
* The point is that we override getDestinationProperties() method.
* Afterwards, instance of DestinationDataProvider should be registered
* using Environment.registerDestinationDataProvider() method to take effect
*/
private Map provider = new HashMap();
@Override
public Properties getDestinationProperties(String destName) {
if (destName == null){
throw new NullPointerException("Destination name is empty.");
}
if (provider.size() == 0){
throw new IllegalStateException("Data provider is empty.");
}
return (Properties) provider.get(destName);
}
@Override
public boolean supportsEvents() {
return false;
}
@Override
public void setDestinationDataEventListener(DestinationDataEventListener destinationDataEventListener) {
throw new UnsupportedOperationException();
}
public void addDestinationProps(String destName, Properties props){
provider.put(destName, props);
}
}
第二步:创建 DestinationManager
类,提供 getDestination()
方法,将 DestinationDataProviderImp
对象注册到 Environment:
package jco3.demo4;
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;
import com.sap.conn.jco.ext.Environment;
import java.util.Properties;
public class DestinationManager {
private static Properties setProperties(String destName)
{
// SAP connection parameters and other properties
Properties props = new Properties();
if (destName == "SAP_AS") {
props.setProperty(DestinationDataProvider.JCO_ASHOST, "192.168.44.100");
props.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
props.setProperty(DestinationDataProvider.JCO_USER, "STONE");
props.setProperty(DestinationDataProvider.JCO_PASSWD, "w123456");
props.setProperty(DestinationDataProvider.JCO_CLIENT, "001");
props.setProperty(DestinationDataProvider.JCO_LANG, "EN");
}
return props;
}
public static JCoDestination getDestination (String destName) throws JCoException {
Properties props = setProperties(destName);
DestinationDataProviderImpl providerImpl = new DestinationDataProviderImpl();
providerImpl.addDestinationProps(destName, props);
Environment.registerDestinationDataProvider(providerImpl);
JCoDestination dest = JCoDestinationManager.getDestination(destName);
return dest;
}
}
测试 DestinationManager
:
package jco3.demo4;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoException;
import org.junit.Test;
public class TestDestinationManager {
@Test
public void pingDest() throws JCoException {
JCoDestination dest = DestinationManager.getDestination("SAP_AS");
dest.ping();
}
}