1

今年早些时候,我CustomDestinationProvider为我的一个 Spring MVC tomcat 应用程序开发了 SAP JCO 的实现。在我的应用程序中,我使用此实现在我的 SAP R/3 系统中调用 BAPI 来检索数据。

我现在正在开发第二个 Spring MVC tomcat 应用程序,我想在我的 SAP R/3 系统中调用 BAPI 来检索数据。我将调用不同的 BAPI,因此我将检索不同的数据。由于这是一个调用不同 BAPI 的不同应用程序,我想在我的配置中使用不同的 SAP 系统用户。这个新应用程序将在与第一个应用程序相同的物理 tomcat 服务器上运行。

我的问题是我应该CustomDestinationProvider为这个新应用程序开发另一个 SAP JCO 实现,还是应该以某种方式重用第一个实现?如果答案是我应该为这个新应用程序开发另一个实现,那么我希望我会为我开发的每个需要与 SAP 对话的新 Spring MVC tomcat 应用程序开发另一个实现。这是正确的想法吗?

如果我为我的这个新应用程序执行不同的实现,我应该在代码中使用相同的目标名称,还是应该使用不同的名称?下面是我第一次实现的代码CustomDestinationDataProvider

public class CustomDestinationDataProvider {
public class MyDestinationDataProvider implements DestinationDataProvider {
    private DestinationDataEventListener eL;
    private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();

    public Properties getDestinationProperties(String destinationName) {
        try {
            Properties p = secureDBStorage.get(destinationName);
            if(p!=null) {
                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);
        }
    }
    public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
        this.eL = eventListener;
    }
    public boolean supportsEvents() {
        return true;
    }
    public void changeProperties(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
            }
        }
    }
}

public ArrayList<String> executeSAPCall(Properties connectProperties, ArrayList<String> partnumbers) throws Exception {
    String destName = "ABAP_AS";
    SAPDAO sapDAO = new SAPDAO(); 
    ArrayList<MaterialBean> searchResults = new ArrayList<MaterialBean>();
    MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
    boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
    JCoDestination dest;
    try {
        if (!destinationDataProviderRegistered) {
            com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
            myProvider.changeProperties(destName, connectProperties);
        }
    } catch(IllegalStateException providerAlreadyRegisteredException) {
        logger.error("executeSAPCall: providerAlreadyRegisteredException!");
    }
    try {
        dest = JCoDestinationManager.getDestination(destName);
        searchResults = sapDAO.searchSAP(dest, partnumbers);
    } catch(JCoException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return searchResults;
}  
}

如果答案是我不需要为我的第二个应用程序实现另一个 CustomDestinationDataProvider,我还需要记住哪些其他注意事项?

4

2 回答 2

1

您只能注册一个DestinationDataProvider,因此您设置的一个必须能够处理两个(或多个)不同的连接。为此,您需要为每个连接提供唯一名称,即destName不能是固定值ABAP_AS,您需要为每个连接创建一个。

您当前对提供程序的实现对我来说看起来不错,但是在我看来,调用 RFC 时您的方法将连接的创建和实际的 RFC 调用混为一谈。恕我直言,您应该将前者分离到它自己的方法中,这样您就可以从应用程序的其他部分调用它,例如执行 RFC 调用之外的其他事情。

于 2017-09-20T20:51:32.153 回答
0

我想通了!我发现了两种不同的方法来实现 CustomDestinationDataProvider 以便我可以使用多个目的地。

我所做的对两种不同解决方案都有帮助的事情是更改 CustomDestinationDataProvider 中实例化 MyDestinationDataProvider 内部类的方法,以便它返回 JCoDestination 而不是返回 ArrayList。我将此方法的名称从 executeSAPCall 更改为 getDestination。

我发现允许我使用多个目的地并成功更改目的地的第一种方法是为 MyDestinationDataProvider 引入一个类变量,以便我可以保留我的实例化版本。请注意,对于此解决方案,CustomDestinationDataProvider 类仍嵌入在我的 java 应用程序代码中。

我发现这个解决方案只适用于一个应用程序。我无法在同一个tomcat服务器上的多个应用程序中使用这种机制,但至少我终于能够成功切换目的地。这是第一个解决方案的 CustomDestinationDataProvider.java 的代码:

public class CustomDestinationDataProvider {

private MyDestinationDataProvider gProvider;    // class version of MyDestinationDataProvider

public class MyDestinationDataProvider implements DestinationDataProvider {
    private DestinationDataEventListener eL;
    private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
    public Properties getDestinationProperties(String destinationName) {
        try {
            Properties p = secureDBStorage.get(destinationName);
            if(p!=null) {
                if(p.isEmpty())
                    throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
                return p;
            }

            return null;
        } catch(RuntimeException re) {
            System.out.println("getDestinationProperties: Exception detected!!! message = " + re.getMessage());
            throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
        }
    }
    public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
        this.eL = eventListener;
    }
    public boolean supportsEvents() {
        return true;
    }
    public void changeProperties(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
            }
        }
    }
}
public JCoDestination getDestination(String destName, Properties connectProperties) {
    MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
    boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
    if (!destinationDataProviderRegistered) {
        try {
            com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
            gProvider = myProvider; // save our destination data provider in the class var
        } catch(IllegalStateException providerAlreadyRegisteredException) {
            throw new Error(providerAlreadyRegisteredException);
        }
    } else {
        myProvider = gProvider; // get the destination data provider from the class var.
    }
    myProvider.changeProperties(destName, connectProperties);
    JCoDestination dest = null;
    try {
        dest = JCoDestinationManager.getDestination(destName);
    } catch(JCoException e) {
        e.printStackTrace();
    } catch (Exception e) {
        // TODO Auto-generated catch block
    }
    return dest;
}
}

这是我的 servlet 类中的代码,用于在我的应用程序代码中实例化和调用 CustomDestinationDataProvider:

    CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
    SAPDAO sapDAO = new SAPDAO();

    Properties p1 = getProperties("SAPSystem01");
    Properties p2 = getProperties("SAPSystem02");
    try {
        JCoDestination dest = cddp.getDestination("SAP_R3_USERID_01", p1);  // establish the first destination
        sapDAO.searchEmployees(dest, searchCriteria);   // call the first BAPI
        dest = cddp.getDestination("SAP_R3_USERID_02", p2); // establish the second destination
        sapDAO.searchAvailability(dest);    // call the second BAPI
    } catch (Exception e) {
        e.printStackTrace();
    }

同样,此解决方案仅适用于一个应用程序。如果您将此代码直接实现到多个应用程序中,则调用此代码的第一个应用程序会获取资源,而另一个应用程序将出错。

我想出的第二个解决方案允许多个 java 应用程序同时使用 CustomDestinationDataProvider 类。我打破了我的应用程序代码中的 CustomDestinationDataProvider 类,并为它创建了一个单独的 java spring 应用程序(不是 Web 应用程序),以创建一个 jar。然后我将 MyDestinationDataProvider 内部类转换为单例。这是 CustomDestinationDataProvider 的单例版本的代码:

public class CustomDestinationDataProvider {
public static class MyDestinationDataProvider implements DestinationDataProvider {

////////////////////////////////////////////////////////////////////
    // The following lines convert MyDestinationDataProvider into a singleton. Notice 
    // that the MyDestinationDataProvider class has now been declared as static.
    private static MyDestinationDataProvider myDestinationDataProvider = null;
    private MyDestinationDataProvider() {
    }
    public static MyDestinationDataProvider getInstance() {
        if (myDestinationDataProvider == null) {
            myDestinationDataProvider = new MyDestinationDataProvider();
        }
        return myDestinationDataProvider;
    }
    ////////////////////////////////////////////////////////////////////

    private DestinationDataEventListener eL;
    private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
    public Properties getDestinationProperties(String destinationName) {
        try {
            Properties p = secureDBStorage.get(destinationName);
            if(p!=null) {
                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);
        }
    }
    public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
        this.eL = eventListener;
    }
    public boolean supportsEvents() {
        return true;
    }
    public void changeProperties(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
            }
        }
    }
}
public JCoDestination getDestination(String destName, Properties connectProperties) throws Exception {
    MyDestinationDataProvider myProvider = MyDestinationDataProvider.getInstance();
    boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
    if (!destinationDataProviderRegistered) {
        try {
            com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
        } catch(IllegalStateException providerAlreadyRegisteredException) {
            throw new Error(providerAlreadyRegisteredException);
        }
    }
    myProvider.changeProperties(destName, connectProperties);
    JCoDestination dest = null;
    try {
        dest = JCoDestinationManager.getDestination(destName);
    } catch(JCoException ex) {
        ex.printStackTrace();
        throw ex;
    } catch (Exception ex) {
        ex.printStackTrace();
        throw ex;
    }
    return dest;
}
}

将此代码放入 jar 文件应用程序并创建 jar 文件(我称之为 JCOConnector.jar)后,我将 jar 文件放在我的 tomcat 服务器的共享库类路径中并重新启动 tomcat 服务器。就我而言,这是 /opt/tomcat/shared/lib。检查您的 /opt/tomcat/conf/catalina.properties 文件中的 shared.loader 行,了解您的共享库类路径的位置。我的看起来像这样:

shared.loader=\
${catalina.home}/shared/lib\*.jar,${catalina.home}/shared/lib

我还将这个 jar 文件的副本放在我工作站上的“C:\Users\userid\Documents\jars”文件夹中,以便测试应用程序代码可以看到 jar 中的代码并进行编译。然后我在我的两个测试应用程序的 pom.xml 文件中引用了这个 jar 文件的副本:

    <dependency>
        <groupId>com.mycompany</groupId>
        <artifactId>jcoconnector</artifactId>
        <version>1.0</version>
        <scope>system</scope>
        <systemPath>C:\Users\userid\Documents\jars\JCOConnector.jar</systemPath>
    </dependency>

将此添加到 pom.xml 文件后,我右键单击每个项目,选择 Maven -> 更新项目...,然后我再次右键单击每个项目并选择“刷新”。我学到的非常重要的一点是不要将 JCOConnector.jar 的副本直接添加到我的任何一个测试项目中。这样做的原因是因为我希望使用 /opt/tomcat/shared/lib/JCOConnector.jar 中的 jar 文件中的代码。然后,我构建并部署了我的每个测试应用程序到 tomcat 服务器。

在我的第一个测试应用程序中调用我的 JCOConnector.jar 共享库的代码如下所示:

    CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
    JCoDestination dest = null;
    SAPDAO sapDAO = new SAPDAO();

    Properties p1 = getProperties("SAPSystem01");
    try {
        dest = cddp.getDestination("SAP_R3_USERID_01", p1);
        sapDAO.searchEmployees(dest);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

我的第二个测试应用程序中调用 JCOConnector.jar 共享库的代码如下所示:

    CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
    JCoDestination dest = null;
    SAPDAO sapDAO = new SAPDAO();

    Properties p2 = getProperties("SAPSystem02");
    try {
        dest = cddp.getDestination("SAP_R3_USERID_02", p2);
        sapDAO.searchAvailability(dest);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

我知道我已经遗漏了首先在您的工作站和服务器上安装 SAP JCO 3 库所涉及的许多步骤。我确实希望这有助于至少另一个人越过尝试在同一台服务器上与 SAP 对话的多个 spring mvc java spplications。

于 2017-10-26T18:41:31.160 回答