【问题标题】:Is it possible to switch Class versions at runtime with Java?是否可以在运行时使用 Java 切换 Class 版本?
【发布时间】:2009-07-22 18:36:58
【问题描述】:

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

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

  1. 有没有办法在运行时动态切换 JAR 文件? (类似于 c/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 版本,这似乎会很快变得笨拙。

非常感谢任何建议和cmets。

-新人

【问题讨论】:

    标签: java dll build classloader


    【解决方案1】:

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

    来自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 - 包含上面的分析器/测试代码
      • 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。

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

        • 只有当你有两个被同一个类加载器引用的 jar 文件时才适用。如果您使用相同的类加载器,则将使用一组类,而不会使用另一组。但是,可以在同一个 JVM 中加载两个具有相同包和相同名称的类。它们只需要加载不同的类加载器。
        • 有趣,有一个链接到一个很好的阅读吗?谷歌让我失望了:(
        【解决方案4】:

        您的工作人员需要有一个实现接口的委托。您将不得不编写两个委托,一个用于旧 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);}
        }
        
        }
        

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

        【讨论】:

          猜你喜欢
          • 2015-07-08
          • 2015-08-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-01
          • 2021-08-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多