【问题标题】:Initialize (load) a java class with a java.lang.Class<T> instance使用 java.lang.Class<T> 实例初始化(加载)一个 java 类
【发布时间】:2019-12-07 21:26:57
【问题描述】:

我不得不承认这更像是一个表面问题,但我还没有找到更直接的解决方案这一事实让我觉得我可能遗漏了一些东西。

问题是,我的类(比如说 Foo)有一个非常重要的 static 块,它使用 Map 中的构建器方法注册自己 (Foo.class),如下所示:

// somewhere in the class
static {
    Bar.registerBuilder(Foo.class, Foo::build);
}

这使得从Bar 类中获取Foo 构建器成为可能,有点像这样:

// somewhere in a method
Foo foo = Bar.getBuilder(Foo.class).apply("Hello World");

(如果构建器采用String 参数)。但是,上面的代码示例仅在 Foo 类已经初始化的情况下才有效。如果没有,这意味着Foostatic 块没有被执行,并且构建器现在没有在Bar 中注册,这导致getBuilder() 返回nullapply() 抛出一个@ 987654338@.

感谢互联网(主要是 StackOverflow),我发现您可以强制使用 Class.forName(String)。但真正让我困惑的是,这个方法需要一个String(因此抛出检查的ClassNotFoundException),我还没有找到一种方法来直接通过java.lang.Class 实例加载和初始化一个类。我本来期望像

Class<Foo> clazz = Foo.class;
clazz.load(); // does not exist

相反,我必须这样做:

Class<Foo> clazz = Foo.class;
try {
    Class.forName(clazz.getName());
} catch (ClassNotFoundException) {
    // handle an exception that is actually unreachable
}

我想知道我是否完全遗漏了什么,或者如果没有,是否有更简洁的方法可以通过java.lang.Class 表示来加载和初始化一个类。

感谢您的帮助,谢谢!

编辑 1:正如@Boris the Spider 在 cmets 中指出的那样,Foo.class 可能已经加载并初始化了该类,但它没有(至少在我的情况下)这就是我什至遇到这个问题的原因.

编辑2:使用“复杂”的方式通过Class.forName()(如代码示例)加载类实际上解决了我想的问题。只是如果可能的话,我想使用一种更清洁的方式。

使用:

  • Java 11 (openjdk 11.0.2)
  • IntelliJ IDEA Ultimate (2019.3)
  • Maven (3.6.3)

【问题讨论】:

  • @Boris the Spider 实际上,这正是我第一次想到为什么没有java.lang.Class.load()。但问题是我最初遇到这个问题是因为我试图从Bar 获取构建器,但我得到了所说的NullPointerException。显然,Foo.class 不会在 java 11 (openjdk11.0.2) 中加载Foo(或不初始化它)。我也很困惑,但这基本上是我问题的根本原因。
  • ServiceLoader 负责处理这种情况。
  • @BoristheSpider 初始化与加载完全不同,因此只能在加载之后进行,因为它需要加载的类。虽然有意未指定类加载时间,但初始化时间为precisely specified。不,使用文字 Foo.class 不在此列表中。

标签: java class reflection initialization


【解决方案1】:

如果您已经在引用该类,最好将该静态代码移至普通静态工厂方法中。既然可以运行该方法,为什么还要使用反射或尝试引用某个类只是为了使某些代码运行?

public static BuilderFunction createBuilder() {
    return Foo::build;
}

只需在 Bar 的静态块中调用它:

registerBuilder(Foo.class, Foo.createBuilder());

如果您需要更动态的东西,您可以使用服务加载器,尤其是在 java 9+ 中,因为它们现在更好用:

provides my.BuilderProvder with something.FooProvider;

然后将它们全部加载到 Bar 中:

ServiceLoader<BuilderProvder> loader = ServiceLoader.load(BuilderProvder.class);
loader.stream()
      .forEach(provider -> registerBuilder(provider));

现在,即使不是由您开发的不同模块也可以提供自己的构建器,并且您不需要进行任何手动类加载(并且只有在实际使用类时才能保证进行类初始化,例如使用某些方法或字段 - 注意常量在编译时被内联,所以它们不计算在内)。

您还可以使用一些 hacky 反射库(如 ClassGraph 或 Reflections)来获取给定类型/具有给定注释的所有类,然后加载它们并在它们上调用一些 init 方法,就像我第一个使用 createBuilder 提出的解决方案一样。这是spring内部注册了多少组件,类似的事情可以用java注解预处理在编译时找到这个类并保存名称。但如果可能的话,我建议坚持使用现有的内置解决方案,如服务加载程序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多