3

我有一个要创建的 java 项目,它将建立在一些供应商 API 之上。API 连接到运行所述供应商软件的服务器并执行各种操作。

我有 2 个不同版本的服务器,由 2 个不同的 API 版本支持。对 API 的任何更改仅在内部实现中。IE 旧版本中我可用的类、接口、方法等在新版本中存在。因此,我编写的代码应该使用任一 API 版本编译和运行。使用 API 进行连接时,向服务器提供的 API 中有一个版本号,可防止您在该特定服务器上使用不同版本的 API。

  1. 有没有办法在运行时动态切换 JAR 文件?(像 ac/c++ DLL 之类的东西??)

  2. 如果无法在运行时切换 API 版本,那么处理该问题的最优雅方法是什么。构建代码 2x(每个 api 版本一个)?

我希望我遗漏了一些东西,但方法 2 似乎并不理想。下面是一个更具体的例子:

package org.myhypotheticalwrapper.analyzer;

import org.myhypothetical.worker;
import org.myhypothetical.comparator;

public class Analyzer {

    Worker w1 = new Worker();
    Worker w2 = new Worker();

    Comparator c = new Comparator(w1.connectAndDoStuff(),w2.connectAndDoStuff());
    c.generateReport();
}

这是我的困境。我希望 w1 使用旧 API 构建,w2 使用新 API 构建,以便它们可以连接到适当的服务器。除了它们所在的 API 之外,它们是相同的(相同的代码)。我是否必须为 W1 和 W2 创建两个唯一命名的类类型,即使它们的代码相同,只是为了适应不同的 API 版本?如果我有许多想要与之交互的 API 版本,这似乎会很快变得笨拙。

非常感谢任何建议和意见。

-新人

4

4 回答 4

4

最简单的可能是在不在默认类路径中的类中加载类加载器。

来自http://www.exampledepot.com/egs/java.lang/LoadClass.html

    // Convert File to a URL
    URL url = file.toURL();          // file:/c:/myclasses/
    URL[] urls = new URL[]{url};

    // Create a new class loader with the directory
    ClassLoader cl = new URLClassLoader(urls);

    // Load in the class; MyClass.class should be located in
    // the directory file:/c:/myclasses/com/mycompany
    Class cls = cl.loadClass("com.mycompany.MyClass");
于 2009-07-22T18:40:29.150 回答
2

一旦加载了类文件,您就无法真正更改它,因此实际上无法在运行时替换类。请注意,像JavaRebel这样的项目通过 javaagent 巧妙地使用了一些工具来解决这个问题——但即使你能用它做的事情也是有限的。

从它的声音来看,您只需要在您的环境中同时有两个并行实现,并且不需要在运行时重新加载类。这可以很容易地完成。假设您的运行时包含以下文件:

  • Analyzer.jar - 这包含上面的分析器/测试代码
  • api.jar - 这是常见的前向 API 代码,例如接口
  • api-impl-v1.jar - 这是旧版本的实现
  • api-impl-v2.jar - 这是较新版本的实现

假设您的工作接口代码如下所示:

package com.example.api;

public interface Worker {
  public Object connectAndDoStuff();
}

您的实现(在 v1 和 v2 中)如下所示:

package com.example.impl;

import com.example.api.Worker;

public class WorkerImpl implements Worker {
  public Object connectAndDoStuff() {
    // do stuff - this can be different in v1 and v2
  }
}

然后你可以这样写分析器:

package com.example.analyzer;

import com.example.api.Worker;

public class Analyzer {
  // should narrow down exceptions as needed
  public void analyze() throws Exception {  
    // change these paths as need be
    File apiImplV1Jar = new File("api-impl-v1.jar"); 
    File apiImplV2Jar = new File("api-impl-v2.jar");

    ClassLoader apiImplV1Loader = 
      new URLClassLoader(new URL[] { apiImplV1Jar.toURL() });
    ClassLoader apiImplV2Loader = 
      new URLClassLoader(new URL[] { apiImplV2Jar.toURL() });

    Worker workerV1 = 
      (Worker) apiImplV1Loader.loadClass("com.example.impl.WorkerImpl")
                              .newInstance();
    Worker workerV2 = 
      (Worker) apiImplV2Loader.loadClass("com.example.impl.WorkerImpl").
                              .newInstance();

    Comparator c = new Comparator(workerV1.connectAndDoStuff(),
                                  workerV2.connectAndDoStuff());
    c.generateReport();
  }
}

要运行分析器,您需要在类路径中包含 analyzer.jar 和 api.jar,但忽略api-impl-v1.jar 和 api-impl-v2.jar。

于 2009-07-22T19:00:27.753 回答
0

您需要确保这些类位于不同的包中。您不能导入具有相同包列表的两个 jar 文件,并期望一个比另一个被识别。如果它们在不同的包中,您可以使用:

com.foo.Worker w1;
com.bar.Worker w2;
于 2009-07-22T18:42:08.117 回答
0

您的工作人员需要有一个实现接口的委托。您将不得不编写两个委托,一个用于旧 api,一个用于新 api。选择在运行时实例化哪个委托。就像是:

public class Worker {
private WorkerDelegate delegate;

public void foo() { delegate.foo(); }

public Object bar(){ return delegate.bar(); }

public Enum API{v1,v2};

public Worker(API api) {
try { switch(api){
case v1: delegate = Class.forName("my.v1.impl").newInstance(); break
case v2: delegate = Class.forName("my.v2.impl").newInstance(); break
}
}catch(...){throw new Error(e);}
}

}

以后可以轻松添加更多实现。

于 2009-07-22T19:02:17.873 回答