是否可以在运行时使用Java切换Class版本?

时间:2009-07-22 18:36:58

标签: java dll build classloader

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

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

  1. 有没有办法在运行时动态切换JAR文件? (类似于c / c ++ DLL ??)

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

  3. 我希望我错过了一些东西但是方法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();
    }
    

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

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

    -new guy

4 个答案:

答案 0 :(得分: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");

答案 1 :(得分: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。 / p>

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

您的工作人员需要拥有实现接口的委托。你必须写两个代表,一个代表旧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);}
}

}

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

相关问题