如何强制 Java 在实例化时重新加载类?
Posted
技术标签:
【中文标题】如何强制 Java 在实例化时重新加载类?【英文标题】:How to force Java to reload class upon instantiation? 【发布时间】:2011-04-27 15:47:11 【问题描述】:背景: 我有一个 MainTest 类,它有很多按钮,每个按钮都实例化一个我正在编码/测试的类。我希望这些类的代码/测试周期更快,并快速查看我的更改的效果,每分钟几次。稳定的 MainTest 加载大约需要 20 秒,如果我不需要为它实例化的类中的每次更改重新加载它,这将不是问题。我想加载一次 MainTest,当它实例化另一个类时,我们称它为 ChildTest,多次(在按钮事件时),它应该重新加载最新版本的 ChildTest。简而言之问题: 如何告诉 java 'new' 命令从磁盘而不是从 jvm 缓存重新加载类? 我尝试了 Class.ForName 但没有任何区别。 我也尝试过使用自定义类加载器(从开源复制),但无济于事。
【问题讨论】:
您是在使用 JUnit 之类的测试框架还是自己开发的测试平台? 【参考方案1】:我不确定如何在代码中执行此操作,但 NetBeans 确实有一个“应用代码更改”按钮可以执行此操作。
但是,它只能改变类的实现,不能改变它的签名。这意味着您不能添加、删除或更改实例变量或方法。这是因为 JVM 的设计不允许在加载一次类后更改这些内容。
既然NetBeans可以做到,那肯定有办法,但是不知道怎么手动做到。
如果您在支持此功能的 IDE 中运行,则每次修改类时只需单击按钮即可。然后它将重新编译该类并重新加载它。
【讨论】:
我正在使用 Eclipse,它会在保存时自动编译。问题是如何将新编译的类加载到已经加载了相同类的先前版本的 jvm 中。【参考方案2】:听起来你想在可以与调试器一起使用时使用热部署。当您调试问题并重新编译它的某些类时,您可以选择重新加载已更改的类。
编辑:除了使用调试 API,您还可以使用 Instrumentation。 http://download-llnw.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html
但是,由于使用调试器是迄今为止最简单的方法,如果这不起作用,您很可能会遇到同样的问题。
这听起来像是您需要测试较小的工作,因此运行您的应用程序的某个子集只需不到一秒钟的时间。
或者您可以通过为内存提供转储和重新加载工具来更快地加载您的应用程序。这样您就可以从快照启动您的应用程序(可能立即)
【讨论】:
我不能真正使用调试器来达到这个目的,太慢了。 (广泛的 gui。)但我的问题是,如果调试器可以做到,为什么我不能呢? 如果您使用调试 API 来执行此操作,则可以。然而,这并不容易使用。更改类加载器不会更改已加载的类更改其代码。见编辑。【参考方案3】:听起来有点吓人,但这应该会有所帮助。
http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ClassLoader.html
ClassLoader 可以在运行时动态加载类,我会阅读 api 以确定再次加载它是否会覆盖以前的版本。
【讨论】:
我需要编写自己的 ClassLoader 子类来完成此操作。 这不会很难完成,只需扩展 ClassLoader 并选择始终从源文件创建一个新实例。 API 简要概述了如何执行此操作以从网络动态获取文件。您的案例将来自常量文件源。【参考方案4】:没有希望“重载”new
运算符,但您当然可以编写一个自定义类加载器,每次您要求它加载一个类时它都会简单地重新加载字节码。没有开箱即用的类加载器可以满足您的需求,因为它们都假设类定义在 JVM 的整个生命周期中都不会改变。
但这就是你实现它的方法。创建一个名为Reloader
的类加载器,它会覆盖loadClass
和findClass
方法,以便它们每次调用时只需从磁盘重新加载类文件(而不是“缓存”它们以供以后使用)。然后,只要您怀疑类定义已更改(例如,作为测试框架生命周期方法的一部分),您只需致电 new Reloader().loadClass("foo.bar.MyClassName")
。
This article 填写了一些细节,但遗漏了一些重要的点,尤其是关于使用类加载器的新实例进行后续重新加载以及在适当时委托给默认类加载器。这是一个简单的工作示例,它重复加载类MyClass
并假设其类文件存在于相对的“./bin”目录中:
public class Reloader extends ClassLoader
public static void main(String[] args) throws Exception
do
Object foo = new Reloader().loadClass("MyFoo").newInstance();
System.out.println("LOADED: " + foo); // Overload MyFoo#toString() for effect
System.out.println("Press <ENTER> when MyFoo.class has changed");
System.in.read();
while (true);
@Override
public Class<?> loadClass(String s)
return findClass(s);
@Override
public Class<?> findClass(String s)
try
byte[] bytes = loadClassData(s);
return defineClass(s, bytes, 0, bytes.length);
catch (IOException ioe)
try
return super.loadClass(s);
catch (ClassNotFoundException ignore)
ioe.printStackTrace(System.out);
return null;
private byte[] loadClassData(String className) throws IOException
File f = new File("bin/" + className.replaceAll("\\.", "/") + ".class");
int size = (int) f.length();
byte buff[] = new byte[size];
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
dis.readFully(buff);
dis.close();
return buff;
在 main 方法中每次调用“do/while”块时,都会实例化一个新的 Reloader,它会从磁盘加载类并将其返回给调用者。因此,如果您覆盖 bin/MyClass.class
文件以包含具有不同的重载 toString
方法的新实现,那么您应该每次都能看到新实现。
【讨论】:
我实际上正在使用那篇文章的 CustomClassLoader,但无法让它做我想做的事。这听起来像是正确的方法,但我想我将不得不深入研究这篇文章。 您需要注意不要对更改的类使用“new”运算符,否则将使用默认的系统类加载器而不是您自己的。尝试使用类似的东西MyClass foo = myReloader.loadClass("MyClass").newInstance();
是的!它完成了这项工作。即使是一个gui。谢谢。
运行上述示例会导致“***”错误,除非存在“MyFoo”类。
这非常重要,“在 main 方法中每次调用“do/while”块时,都会实例化一个新的 Reloader”【参考方案5】:
我找到了一篇关于这个问题的文章。http://www.zeroturnaround.com/blog/reloading-objects-classes-classloaders/
但是,maerics 的答案看起来也不错。稍后会尝试。
【讨论】:
【参考方案6】:你可以试试Java Rebel。它是一个“仅用于开发”的类加载器,旨在完全满足您的需求,但对于您的需求而言,它可能会很昂贵。在您的情况下,Peter Lawrey 的解决方案可能就足够了。
【讨论】:
以上是关于如何强制 Java 在实例化时重新加载类?的主要内容,如果未能解决你的问题,请参考以下文章