默认情况下,Sun的JVM懒惰地加载类并且懒惰地初始化(即调用它们的<clinit>
方法)它们。考虑以下类ClinitBomb
,它会在Exception
块中抛出static{}
。
public class ClinitBomb {
static {
explode();
}
private static void explode() {
throw new RuntimeException("boom!");
}
}
现在,考虑如何触发炸弹:
public class Main {
public static void main(String[] args) {
System.out.println("A");
try {
Class.forName("ClinitBomb");
} catch (Exception e) {
e.printStackTrace(System.out);
}
System.out.println("B");
ClinitBomb o2 = new ClinitBomb();
System.out.println("C");
}
}
我们保证爆炸发生在B点之前,因为forName
的文件说明了这一点;问题是它是否发生在A点之前(加载Main
时。)在Sun的JVM中,即使main()
包含对ClinitBomb
的静态引用,它也会发生在A之后。
我想要一种方法告诉JVM在初始化ClinitBomb
时加载并初始化Main
(因此炸弹在点A之前爆炸)。一般来说,我想要一种方式来说,“每当加载/初始化类X时,也会对它引用的任何类Y这样做。”
有办法吗?
答案 0 :(得分:9)
没有办法做到这一点。 JLS在§12.4.1 When Initialization Occurs(强调我的)中说:
类的初始化包括执行其静态初始化程序和类中声明的静态字段的初始化程序。 [...]
类或接口类型T将在第一次出现以下任何一个之前立即初始化:
- T是一个类,创建了一个T实例。
- T是一个类,调用T声明的静态方法。
- 分配由T声明的静态字段。
- 使用由T声明的静态字段,该字段不是常量变量(§4.12.4)。
- T是顶级类,并且执行词法嵌套在T中的断言语句(第14.10节)。
在类Class和包java.lang.reflect中调用某些反射方法也会导致类或接口初始化。 在任何其他情况下都不会初始化类或接口。
一旦加载了类就初始化类的Java实现会违反JLS。
虽然您可以做的是使用JVM instrumentation API来编写ClassFileTransformer,它为每个显式初始化其引用类的类添加了一个静态块(可能通过Class.forName)。只要一个类被初始化,就可以初始化从它可以访问的所有类。这可能会给你你想要的结果。不过,这是相当多的工作!
答案 1 :(得分:1)
Class.forName("...", true /*initialize*/, getClassLoader());
你在那里一半。