3天前  Java系列 |   抢沙发  2 
文章评分 0 次,平均分 0.0
[收起] 文章目录

在用Java开发web应用程序的UI时,以下工作流程可能很常见:

  • 对某个源文件进行更改;
  • 重新编译项目;
  • 重启服务器;
  • 测试

每次进行更改时重新启动服务器并不理想。即使你的应用程序启动很快。

如果我们能够在运行时重新加载类定义,则可能并不总是需要重新启动。

在这篇文章中,将展示如何在运行时重新加载单个类的定义。

类重载器

我们需要一个可以在运行时加载和重新加载单个类定义的类。我们将把类命名为ClassReloader。让我们从测开始。

Subject类

在我们的测试中,我们将使用重载器加载以下类定义:

package reloader;

public class Subject {
  @Override
  public String toString() {
    return "I'm the original!";
  }
}

然后,我们将创建加载类的实例。我们希望它的toString方法返回“我是原始的!”值。

接下来,我们将重新加载Subject类型的第二个版本:

package reloader;

public class Subject {
  @Override
  public String toString() {
    return "Reloading successful!";
  }
}

然后,我们将创建一个新的重载类定义实例。我们希望它的toString现在返回“Reloading successful!”值。

创建测试类的十六进制转储

我们手动编译Subject类的两个版本。接下来我们:

  • 使用xxd工具创建类文件的十六进制转储;
  • 在我们的测试中使用文本块来存储十六进制转储。

以下是我们测试类的当前迭代:

public class ClassReloaderTest {

  private static final String ORIGINAL = """
  cafebabe0000004100110a000200030700040c000500060100106a617661\
  2f6c616e672f4f626a6563740100063c696e69743e010003282956080008\
  01001149276d20746865206f726967696e616c2107000a01001072656c6f\
  616465722f5375626a656374010004436f646501000f4c696e654e756d62\
  65725461626c65010008746f537472696e6701001428294c6a6176612f6c\
  616e672f537472696e673b01000a536f7572636546696c6501000c537562\
  6a6563742e6a617661002100090002000000000002000100050006000100\
  0b0000001d00010001000000052ab70001b100000001000c000000060001\
  000000030011000d000e0001000b0000001b00010001000000031207b000\
  000001000c000000060001000000060001000f000000020010\
  """;

  private static final String UPDATED = """
  cafebabe0000004100110a000200030700040c000500060100106a617661\
  2f6c616e672f4f626a6563740100063c696e69743e010003282956080008\
  01001552656c6f6164696e67207375636365737366756c2107000a010010\
  72656c6f616465722f5375626a656374010004436f646501000f4c696e65\
  4e756d6265725461626c65010008746f537472696e6701001428294c6a61\
  76612f6c616e672f537472696e673b01000a536f7572636546696c650100\
  0c5375626a6563742e6a6176610021000900020000000000020001000500\
  060001000b0000001d00010001000000052ab70001b100000001000c0000\
  00060001000000030011000d000e0001000b0000001b0001000100000003\
  1207b000000001000c000000060001000000060001000f000000020010\
  """;

}

ORIGINAL文本块包含我们类的第一个版本。第二个是UPDATED文本块。

试验方法

使用Subject类的十六进制转储,我们编写以下测试方法:

@Test(description = """
It should be able to load and reload the subject class.
""")
public void testCase01() throws Exception {
  Path classOutput;
  classOutput = Files.createTempDirectory("reloader-");

  ClassReloader reloader;
  reloader = new ClassReloader(classOutput);

  try {
    Path packageDir;
    packageDir = classOutput.resolve("reloader");

    Files.createDirectories(packageDir);

    Path classFile;
    classFile = packageDir.resolve("Subject.class");

    HexFormat hexFormat;
    hexFormat = HexFormat.of();

    Files.write(classFile, hexFormat.parseHex(ORIGINAL));

    assertEquals(execute(reloader), "I'm the original!");

    Files.write(classFile, hexFormat.parseHex(UPDATED));

    assertEquals(execute(reloader), "Reloading successful!");
  } finally {
    deleteRecursively(classOutput);
  }
}

我们首先创建一个临时目录,表示Java项目的“类输出”。例如,在Maven项目中,它将表示target/classes目录。

我们使用此目录创建ClassReloader实例:

Path classOutput;
classOutput = Files.createTempDirectory("reloader-");

ClassReloader reloader;
reloader = new ClassReloader(classOutput);

我们的重载程序将尝试在此目录中查找.class文件。

接下来,使用ORIGINAL十六进制转储,我们创建Subject类的类文件:

Path packageDir;
packageDir = classOutput.resolve("reloader");

Files.createDirectories(packageDir);

Path classFile;
classFile = packageDir.resolve("Subject.class");

HexFormat hexFormat;
hexFormat = HexFormat.of();

Files.write(classFile, hexFormat.parseHex(ORIGINAL));

它首先创建表示类包的目录。

然后,它使用HexFormat类将十六进制转储转换为字节数组。

接下来,我们写出第一个断言:

assertEquals(execute(reloader), "I'm the original!");

其中execute方法由下式给出:

private String execute(ClassReloader reloader) throws Exception {
  Class<?> clazz;
  clazz = reloader.loadClass("reloader.Subject");

  Constructor<?> constructor;
  constructor = clazz.getConstructor();

  Object instance;
  instance = constructor.newInstance();

  return instance.toString();
}

使用我们的重载器,它试图通过给出Subject类的二进制名称来加载它。

假设加载成功,则:

  • 创建类的新实例;
  • 返回对象的toString值。

接下来,测试模拟我们类的重新编译:

Files.write(classFile, hexFormat.parseHex(UPDATED));

assertEquals(execute(reloader), "Reloading successful!");

它尝试重新加载类定义,并验证新实例是否返回预期的toString值。

实施

下面是一个使我们的测试通过的实现:

public class ClassReloader {

  private final Path classOutput;

  public ClassReloader(Path classOutput) {
    this.classOutput = classOutput;
  }

  public final Class<?> loadClass(String binaryName) throws ClassNotFoundException {
    ThisLoader loader;
    loader = new ThisLoader();

    return loader.loadClass(binaryName);
  }

  private class ThisLoader extends ClassLoader {

    public ThisLoader() {
      super(null); // no parent
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
      try {
        String fileName;
        fileName = name.replace('.', File.separatorChar);

        fileName += ".class";

        Path path;
        path = classOutput.resolve(fileName);

        byte[] bytes;
        bytes = Files.readAllBytes(path);

        return defineClass(name, bytes, 0, bytes.length);
      } catch (NoSuchFileException e) {
        ClassLoader systemLoader;
        systemLoader = ClassLoader.getSystemClassLoader();

        return systemLoader.loadClass(name);
      } catch (IOException e) {
        throw new ClassNotFoundException(name, e);
      }
    }

  }

}

在loadClass方法中,我们创建了内部ClassLoader子类的新实例。

此子类重写findClass方法:

  • 它试图从classOutput目录读取类定义;
  • 如果由于文件不存在而失败,则将其委托给系统类加载器
  • 如果它因另一个I/O错误而失败,它会将异常作为ClassNotFoundException重新抛出。

如前所述,此实现使我们的测试通过。

结论

我们的ClassLoader实现能够在运行时重新加载单个Java类定义。

它急切地在每次调用时加载类定义,这并不理想。因此,它可能不是Java web应用程序开发过程中使用的最佳候选者。

但我希望它表明,在运行时重新加载单个类定义可以用几行Java代码完成。

您可以在这个GitHub(https://github.com/objectos/blog-examples/tree/main/2024/02/18)找到示例的源代码。

原文地址:https://www.objectos.com.br/blog/reloading-a-java-class-definition-at-runtime.html

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/3001.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册