23

假设我有一个名为NameGenerator. 我可以使用它根据给定的逻辑生成名称。然后我写了一个TestNameGeneration类,它的方法要求用户提供一封信并根据该方法生成一个名称。现在我想更改NameGeneration类中的逻辑并在不停止应用程序的情况下应用该特定更改。

我这样做是为了了解有关类加载器的更多信息,有人可以解释我必须学习做类似事情的关键概念或站点任何参考吗?

4

6 回答 6

34

这是一个工作测试。每 5 秒 Test.main() 从文件系统重新加载 test.Test1.class 并调用 Test1.hello()

package test;

public class Test1 {
    public void hello() {
        System.out.println("Hello !");
    }
}

public class Test {

    static class TestClassLoader extends ClassLoader {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (name.equals("test.Test1")) {
                try {
                    InputStream is = Test.class.getClassLoader().getResourceAsStream("test/Test1.class");
                    byte[] buf = new byte[10000];
                    int len = is.read(buf);
                    return defineClass(name, buf, 0, len);
                } catch (IOException e) {
                    throw new ClassNotFoundException("", e);
                }
            }
            return getParent().loadClass(name);
        }
    }

    public static void main(String[] args) throws Exception {
        for (;;) {
            Class cls = new TestClassLoader().loadClass("test.Test1");
            Object obj = cls.newInstance();
            cls.getMethod("hello").invoke(obj);
            Thread.sleep(5000);
        }
    }
}

运行。然后更改并重新编译Test1

System.out.println("Hello !!!");

在测试运行时。你会看到 Test1.hello 输出改变了

...
Hello !
Hello !
Hello !!!
Hello !!!

这就是例如 Tomcat 重新加载 webapps 的方式。它为每个 web 应用程序都有一个单独的 ClassLoader,并在新的 ClassLoader 中加载新版本。旧的就像任何 Java 对象以及旧的类一样是 GCed。

请注意,我们使用 TestClassLoader 加载了 Test1,并使用反射调用了它的第一个方法。但是所有的 Test1 依赖都会被 Test1 类加载器隐式加载,也就是说所有的 Test1 应用程序都会被 JVM 加载到 TestClassLoader 中。

于 2013-01-14T11:21:17.600 回答
1

有2种方式:

  1. 要覆盖您正在使用的类加载器,首先使用现有的类加载器来引导您的应用程序,特别是对于您需要动态更新的类,您必须使用覆盖的类加载器。
  2. 使用 OSGi 框架。根据您的应用程序的规模,OSGi 框架可能不是一个好的选择,因为它要求您遵循它的编码约定。

希望能帮助到你

于 2013-01-14T10:55:32.340 回答
0

那么策略模式呢?它可能是解决您问题的更好解决方案,而不是使用类加载器。

于 2013-01-14T10:49:27.710 回答
0

这真的很简单,您将利用 OOP 的优势并使用接口,不需要管理类加载器您可以使用 setter 注入实现:

创建一个名为 NameGeneration 的接口创建 n 个实现 NameGenerationImpl1,例如 NameGenerationimpl2 在您的客户端类中定义一个变量:

NameGeneration nameGeneration ;

和二传手:

 public void setNameGeneration(NameGeneration nameGeneration) {
 this.nameGeneration = nameGeneration ;
}

nameGeneration 会生成你想要的。

当您更改算法时,您可以通过以下方式更改实现:

setNameGeneration(new NameGenerationImpl1()) ;

或者

setNameGeneration(new NameGenerationImpl2()) ;
于 2013-01-14T10:56:10.660 回答
0

您可以编写自己的自定义类加载器。当类文件或包含类文件的资源/jar 发生更改时(检查时间戳),销毁以前的类加载器实例并创建一个新的类加载器实例,该实例反过来将加载新的类文件。

于 2013-01-14T14:39:15.997 回答
0

如果您在企业级应用程序中工作,并且希望避免每次更改类时都重新加载应用程序上下文,那么使用DCEVMHotSwapAgent可以更好地帮助您。下面的命令会将 DCEVM 添加到您的 JDK,

java -jar DCEVM-8u181-installer.jar

并且您必须将以下 VM 参数添加到您的运行命令(或)配置中

-XXaltjvm=dcevm -javaagent:Your_directory_location/hotswap-agent-1.3.0.jar .jar=autoHotswap=true

参考:https ://dzone.com/articles/hot-swap-java-bytecode-on-runtime

于 2019-03-23T13:29:18.887 回答