我的 ABAP 系统有个函数名叫 ZDIS_GET_UPSELL_MATERIALS,输入一个 customer ID 和 product ID,会输出为这对客户和 product 组合维护的一组 Upsell product ID 和描述信息。
测试如下:下面是使用 Java 消费该函数的代码:
package jco;
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.JCoFunction;
import com.sap.conn.jco.JCoParameterList;
import com.sap.conn.jco.JCoRepository;
import com.sap.conn.jco.JCoTable;
import com.sap.conn.jco.ext.DestinationDataProvider;
/**
* basic examples for Java to ABAP communication
* See help: https://help.sap.com/saphelp_nwpi711/helpdata/en/48/70792c872c1b5ae10000000a42189c/frameset.htm
*/
public class StepByStepClient
{
static String DESTINATION_NAME = "ABAP_AS_WITHOUT_POOL";
static public final String ABAP_DURATION = "abapLayerDuration";
static public final String UPSELL_PRODUCT = "upsellProducts";
static public final String PRODUCT_ID = "productID";
static public final String PRODUCT_TEXT = "productText";
static private Properties prepareProperty(){
Properties connectProperties = new Properties();
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, "your abap system host name");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, "111");
connectProperties.setProperty(DestinationDataProvider.JCO_USER, "WANGJER");
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, "your password");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
createDestinationDataFile(DESTINATION_NAME, connectProperties);
connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, "3");
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, "10");
createDestinationDataFile(DESTINATION_NAME, connectProperties);
return connectProperties;
}
static public void main(String[] arg) {
createDestinationDataFile(DESTINATION_NAME, prepareProperty());
JCoDestination destination = null;
try {
destination = JCoDestinationManager.getDestination(DESTINATION_NAME);
JCoRepository repo = destination.getRepository();
JCoFunction stfcConnection = repo.getFunction("ZDIS_GET_UPSELL_MATERIALS");
JCoParameterList imports = stfcConnection.getImportParameterList();
String customerID = "1000040";
String materialID = "11";
imports.setValue("IV_CUSTOMER_ID", customerID);
imports.setValue("IV_MATERIAL_ID", materialID);
stfcConnection.execute(destination);
JCoParameterList exports = stfcConnection.getExportParameterList();
// int result = exports.getInt("EV_RESULT");
int abapDuration = exports.getInt("EV_DURATION");
StringBuilder sb = new StringBuilder();
sb.append("{ \"" + ABAP_DURATION + "\": " + abapDuration + ",");
sb.append("\"" + UPSELL_PRODUCT + "\":[");
JCoTable codes = exports.getTable("ET_MATERIALS");
int row = codes.getNumRows();
System.out.println("Total rows: " + row);
System.out.println("ABAP duration: " + abapDuration);
for( int i = 0; i < row; i++){
codes.setRow(i);
System.out.println(codes.getString("MATERIAL_ID") + '\t' + codes.getString("MATERIAL_TEXT"));
sb.append("{\"" + PRODUCT_ID + "\":" + codes.getString("MATERIAL_ID") + ","
+ "\"" + PRODUCT_TEXT + "\":\"" + codes.getString("MATERIAL_TEXT") + "\"");
if( i < row - 1){
sb.append("},");
}
else{
sb.append("}");
}
}
sb.append("]}");
System.out.println("Final json: " + sb.toString());
} catch (JCoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
static private void createDestinationDataFile(String destinationName, Properties connectProperties)
{
File destCfg = new File(destinationName+".jcoDestination");
try
{
FileOutputStream fos = new FileOutputStream(destCfg, false);
connectProperties.store(fos, "for tests only !");
fos.close();
}
catch (Exception e)
{
throw new RuntimeException("Unable to create the destination files", e);
}
}
}
复制代码
为简单起见没有使用 Google 的 gson 库进行 Json 的序列化。执行结果:
ABAP
ABAP help 文档里对**LOAD-OF-PROGRAM"的关键字是这样描述的:
This event keyword defines the program constructor of an executable program, a module pool, a function group, or a subroutine pool. The program constructor is an event block whose event is raised by the ABAP-runtime environment when one of the executable programs mentioned above is loaded into the internal session.
以 Function group 为例,每当一个 function group 里的任意一个 function module 第一次被调用时,对应的 ABAP program 被加载到 internal session 里,同时 ABAP 运行时抛出 LOAD-OF-PROGRAM, 执行应用程序员编写的事件处理逻辑。
现在我有一个名为 ZTOMCAT 的 function group。其 LOAD-OF-PROGRAM 就负责弹出调试器。
我有两个 report。Report 2 的源代码:
REPORT ZJERRY_RE2.
call FUNCTION 'ZTEST_FM_1'.
复制代码
Report 1:
CALL FUNCTION 'ZTEST_FM_1'.
SUBMIT zjerry_re2 AND RETURN.
复制代码
那么我执行 report1,断点会触发一次还是两次?
答案是两次。
LOAD-OF-PROGRAM 在这种场景下的行为,ABAP help 已经说的很清楚了:
When a program is called using SUBMIT or using a transaction code, a new internal session is opened in every call and the event block is executed once in every call.
每次 program 通过 SUBMIT 或者事务码的方式调用时,会起一个新的 internal session,在此新的 session 里 LOAD-OF-PROGRAM 会触发一次。
下图也直观表明了每次调用 SUBMIT( calling programs)时会新起一个 Internal Session。
Tomcat 库文件的重复加载问题
我的 pom.xml 里定义了一个 gson 的依赖关系,ABAPer 可以把其类比成在我的 Java 代码里调用 Google 提供的 gson API。
打成 war 包之后,该库文件位于 WEB-INF/lib 文件夹下。
那么如果我有多个 Web 应用都用到了 gson, 则每个应用的 WEB-INF\lib 文件夹下面都有 gson 的 jar 文件。
问题:在运行时,Tomcat 只会将一份 gson.jar 的内容加载到内存么?
答案是不会。根据 Tomcat 的官方文档,Tomcat 会为每个 Web 应用创建一个专属的 ClassLoader 实例,每个应用的 WEB-INF\lib 下的资源,对于其他应用来说不可见,彼此隔离。
当然如果想只用一份库文件,可以把它放到目录 [tomcat-installation-directory]/common/lib 下面。更多细节参考 stackoverflow 上的讨论.
WebClient UI
我们在 WebClient UI 的开发工具里点了 Test 按钮,
会在浏览器以测试模式打开选中的 view。这背后发生了什么事?注意浏览器地址栏的 bspwd_cmp_test,这是什么东西?Jerry 倾向于把它当作是 CRM WebClient UI component 在测试模式下的启动器。
当我们以测试模式打开一个 CRM WebClient UI component 时,这个 component 的页面实际上是显示在一个 iframe 内,该 iframe 在上述提到的 bspwd_cmp_test 里实现:
点击 Test 按钮,在下图的 iframe 里会把真正被测试的 WebClient UI component 的页面嵌入进去。这个 iframe 加载时,会执行 onload 事件绑定的 JavaScript 处理函数:startBSPApplication。
这个函数为 document 动态生成 HTML 源代码:
启动器 bspwd_cmp_test 的页面 session_single_frame.htm 中立即执行的 Javascript:
运行时展开如下:
Tomcat
最初我们部署 Web 应用到 tomcat 上的做法,一般是首先用 Maven 将 Web 项目打包成 war 文件,再手动或者写脚本将 war 文件拷贝到 tomcat 对应目录下。现在我们可以在项目 pom.xml 文件里使用下面这个插件:
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/jerry</path>
<port>9090</port>
<uriEncoding>UTF-8</uriEncoding>
</configuration>
</plugin>
复制代码
然后在命令行里执行命令:mvn tomcat7:run
这个 tomcat7-maven-plugin 插件可以作为启动器,帮我们启动 Tomcat 并且加载 war 文件,
之后我们可以直接使用 http://localhost:9090/jerry 访问 Web 应用了:
评论