SAP接口編程 之 JCo3.0系列(01):JCoDestination
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運行時環境負責管理連接和釋放連接。我們先以一個簡單的例子看看jco3.0 JCoDestination類的一些要點。
我使用的編程環境是Eclipse,環境准備如下:
- 新建一個Java項目,項目名為JCo3Demo。
- 將sapjco3.jar加入到項目的build path中。注意前面所說的sapjco3.jar和sapjco3.dll要放在同一個文件夾下。
- 在Eclipse Java項目文件夾下,新建一個文本文件,文件名命名為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,不難理解:

環境准備好了,先來一段最簡單的代碼:
package jco3.demo1; import java.util.Properties; import org.junit.Test; import com.sap.conn.jco.JCoDestination; import com.sap.conn.jco.JCoDestinationManager; import com.sap.conn.jco.JCoException; public class JCoDestinationDemo { public JCoDestination getDestination() throws JCoException { /** * Get instance of JCoDestination from file: ECC.jcodestination * which should be located in the installation folder of project */ JCoDestination dest = JCoDestinationManager.getDestination("ECC"); return dest; } @Test public void pingDestination() throws JCoException { JCoDestination dest = this.getDestination(); dest.ping(); } }
代碼說明:
-
getDestination()
方法中,JCoDestinationManager.getDestination("ECC")
從ECC.jcodestination文件中獲取連接參數,創建JCoDestination
對象的實例。
這里有一個重要的約定,JCoDestinationManager.getDestination("ECC")
方法,會從Eclipse Java項目的根目錄,查找ECC.jcodestination文件(文件路徑和擴展名不能改變)是否存在,如果存在,從文件的內容中獲取連接參數。這是DestinationDataProvider
接口的一個默認實現,在開發和測試的時候還是很方便的,但如果在真實項目中使用,安全性和靈活性就不夠。后面會介紹解決方法。 -
pingDestination()
方法調用JcoDestination
對象的ping()方法測試SAP系統的連接。 -
@Test: 使用junit進行測試
配置文件的生成
剛才我們手工編輯了ECC.jcodestination文件,對於這個配置文件,因為很多連接參數來自於DestinationDataProvider
接口,如果想通過代碼來創建配置文件,可以使用如下代碼:
package jco3.demo2; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; import org.junit.Test; import com.sap.conn.jco.ext.DestinationDataProvider; public class DestinationFile { private Properties setProperties() { // logon parameters and other properties Properties connProps = new Properties(); connProps.setProperty(DestinationDataProvider.JCO_ASHOST, "192.168.65.100"); connProps.setProperty(DestinationDataProvider.JCO_SYSNR, "00"); connProps.setProperty(DestinationDataProvider.JCO_USER, "STONE"); connProps.setProperty(DestinationDataProvider.JCO_PASSWD, "xxxxxx"); connProps.setProperty(DestinationDataProvider.JCO_CLIENT, "001"); connProps.setProperty(DestinationDataProvider.JCO_LANG, "EN"); return connProps; } 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()){ // file not exists // Create file output stream, not using append mode FileOutputStream fOutputStream = new FileOutputStream(cfg, false); // store the properties in file output stream // and also add comments props.store(fOutputStream, "SAP logon parameters:"); fOutputStream.close(); }else{ throw new RuntimeException("File alreay exists."); } } @Test public void createConfigFile() throws IOException { Properties props = this.setProperties(); String fileName = "SAP_AS"; // sap application server // jcodestination suffix is required by JCoDestinationManager this.doCreateFile(fileName, "jcodestination", props); } }
代碼說明:
setProperties()
方法屬性參照DestinationDataProvider
類的常量設置Properties的實例。doCreateFile()
方法根據需求的文件名,擴展名在Eclipse項目的根文件夾下,創建一個文本文件,文件的內容就是Properties實例的內容。createConfigFile()
方法,調用上面的兩個方法,創建配置文件。
更改配置文件名的路徑和擴展名
我們看到,默認情況下,SAP對配置文件的路徑和擴展名都不能改變,如果我們想把文件放在任意位置,擴展名也使用其他的擴展名,有沒有辦法?答案是有,方法是實現DestinationDataProvider
接口,並改寫(override)getDestinationProperties()
方法,然后通過Environment.registerDestinationDataProvider()
方法進行注冊。
OK, 一起來看看代碼,代碼分為三個部分:
- 第一部分: 創建
FileDestinationDataProviderImp
類,實現DestinationDataProvider
接口- 第二部分: 創建
FileDestinationDataProvider
類,注冊FileDestinationDataProviderImp
的實例,並提供getDestination()
方法供調用- 第三部分:調用FileDestinationDataProvider類的getDestination()方法
第一部分:DestinationDataProvider
接口的實現:
package jco3.demo2; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; import com.sap.conn.jco.ext.DestinationDataEventListener; import com.sap.conn.jco.ext.DestinationDataProvider; public class FileDestinationDataProviderImp implements DestinationDataProvider { private File dir; private String destName; // destination name private String suffix; public void setDestinationFile(File dir, String destName, String suffix) { this.dir = dir; this.destName = destName; this.suffix = suffix; } private Properties loadProperties(File dir, String destName, String suffix) throws IOException { Properties props = null; // create a file with name: fullName in destDirectory File destFile = new File(dir, destName+"."+suffix); if (destFile.exists()){ FileInputStream fInputStream = new FileInputStream(destFile); props = new Properties(); props.load(fInputStream); fInputStream.close(); }else{ throw new RuntimeException("Destination file does not exist."); } return props; } @Override public Properties getDestinationProperties(String destName) { Properties props = null; try { props = this.loadProperties(this.dir, this.destName, this.suffix); } catch (IOException e) { e.printStackTrace(); } return props; } @Override public void setDestinationDataEventListener(DestinationDataEventListener listener) { throw new UnsupportedOperationException(); } @Override public boolean supportsEvents() { return false; } }
第二部分: 創建FileDestinationDataProvider
類,注冊FileDestinationDataProviderImp
的實例,並且提供getDestination()
方法。
package jco3.demo2;
import java.io.File; 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; public class FileDestinationDataProvider { public static JCoDestination getDestination() throws JCoException { File directory = new File("."); // current directory; String fileName = "SAP_AS"; String suffix = "txt"; FileDestinationDataProviderImp destDataProvider = new FileDestinationDataProviderImp(); destDataProvider.setDestinationFile(directory, fileName, suffix); Environment.registerDestinationDataProvider(destDataProvider); JCoDestination dest = JCoDestinationManager.getDestination(fileName); return dest; } }
我們看到,getDestination
方法中,文件的路徑,文件的擴展名,都是我們自己定義的。文件名作為JCoDestinationManager.getDestination
方法的destination name。從這里也可可以看到,JCoDestinationManager.getDestination
方法從哪里查找連接參數,是依賴於Environment注冊的DestinationDataProvider實現。
第三部分:測試代碼FileDestinationDataProvider
的getDestination
方法:
package jco3.demo2; import org.junit.Test; import com.sap.conn.jco.JCoDestination; import com.sap.conn.jco.JCoException; public class TestFileDestinationProvider { @Test public void pingSAPDestination() throws JCoException { JCoDestination dest = FileDestinationDataProvider.getDestination(); dest.ping(); } }
DestinationDataProvider另一種實現
記得nco3.0可以將登陸參數寫在代碼中嗎,如果我們也想將連接參數直接寫在代碼中,怎么做呢?剛才說過了,關鍵就是實現DestinationDataProvider接口,並改寫getDestinationProperties()方法。不多說,上代碼。
第一部分:DestinationDataProvider接口實現
package jco3.demo3; import java.util.HashMap; import java.util.Map; import java.util.Properties; import com.sap.conn.jco.ext.DestinationDataEventListener; import com.sap.conn.jco.ext.DestinationDataProvider; public class DestinationDataProviderImp implements DestinationDataProvider { /** * DestinationDataProvider is an interface * We define DestinationDataProviderImp class to implements this interface * so that we can define the logon parameters more flexibly * not just in xxx.jcodestionation file. * * The key point is that we override getDestinationProperties() method * Afterwards, instance of DestinationDataProvider should be registered * using Environment.registerDestinationDataProvider() method to take effect */ @SuppressWarnings("rawtypes") private Map provider = new HashMap(); @SuppressWarnings("unchecked") public void addDestinationProperties(String destName, Properties props) { provider.put(destName, props); } @Override public Properties getDestinationProperties(String destName) { if (destName == null){ throw new NullPointerException("Destinantion name is empty."); } if (provider.size() == 0){ throw new IllegalStateException("Data provider is empty."); } return (Properties) provider.get(destName); } @Override public void setDestinationDataEventListener(DestinationDataEventListener listener) { throw new UnsupportedOperationException(); } @Override public boolean supportsEvents() { return false; } }
第二部分:創建DestinationProivder
類,提供getDestination()
方法,注冊DestinationDataProviderImp
類的實例:
package jco3.demo3; 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; import com.sap.conn.jco.ext.Environment; public class DestinationProvider { private static Properties setProperties() { // logon parameters and other properties Properties connProps = new Properties(); connProps.setProperty(DestinationDataProvider.JCO_ASHOST, "192.168.65.100"); connProps.setProperty(DestinationDataProvider.JCO_SYSNR, "00"); connProps.setProperty(DestinationDataProvider.JCO_USER, "STONE"); connProps.setProperty(DestinationDataProvider.JCO_PASSWD, "xxxxxx"); connProps.setProperty(DestinationDataProvider.JCO_CLIENT, "001"); connProps.setProperty(DestinationDataProvider.JCO_LANG, "EN"); return connProps; } public static JCoDestination getDestination() throws JCoException { String destName = "SAP_AS"; Properties props = setProperties(); DestinationDataProviderImp destDataProvider = new DestinationDataProviderImp(); destDataProvider.addDestinationProperties(destName, props); Environment.registerDestinationDataProvider(destDataProvider); JCoDestination dest = JCoDestinationManager.getDestination(destName); return dest; } }
第三部分:測試DestinationProvider
的getDestination()
方法:
package jco3.demo3; import org.junit.Test; import com.sap.conn.jco.JCoDestination; import com.sap.conn.jco.JCoException; public class TestDestionProvider { @Test public void pingSAPDestination() throws JCoException { JCoDestination dest = DestinationProvider.getDestination(); dest.ping(); } }