Java中是否可以在运行时切换类版本?

3
我有一个Java项目,想要在某些供应商API的基础上构建。这些API连接到运行该供应商软件的服务器,并执行各种操作。
我有两个不同版本的服务器支持两个不同的API版本。任何对API的更改都只是内部实现。也就是说,在旧版本中可用的类、接口、方法等在新版本中都存在。因此,我编写的代码应该能够编译并与任一API版本一起运行。当使用API连接时,向服务器呈现API版本号可以防止在特定服务器上使用不同版本的API。
1. 在运行时是否有一种切换JAR文件的方式?(类似于C/C++ DLL?) 2. 如果无法在运行时切换API版本,则如何处理问题最为优雅?将代码构建两次(每个API版本构建一次)?
希望我遗漏了什么,但是第二种方法似乎并不理想。以下是一个更具体的例子:
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

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

来自 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");

2

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

从听起来的意思来看,你只需要在同一时间在环境中拥有两个并行的实现,而不需要在运行时重新加载类。这可以很容易地完成。假设你的运行时包含以下文件:

  • analyzer.jar - 这个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。

0

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

com.foo.Worker w1;
com.bar.Worker w2;

只有在同一个类加载器引用了两个JAR文件时才会出现这种情况。如果使用相同的类加载器,则将使用一组类,而另一组则不会被使用。然而,在同一个JVM中可以加载具有相同包和名称的两个类。它们只需要使用不同的类加载器加载即可。 - Peter Dolberg
有趣,你有好的阅读链接吗?谷歌没找到 :( - amischiefr

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);}
}

}

更多的实现可以轻松地添加。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接