为了账号安全,请及时绑定邮箱和手机立即绑定

自动调用静态块而不显式调用 Class.forName

自动调用静态块而不显式调用 Class.forName

POPMUISE 2021-11-17 15:00:03
假设以下代码:public class Main {    public static final List<Object> configuration = new ArrayList<>();    public static void main(String[] args) {        System.out.println(configuration);    }}我现在希望能够提供“自配置”类。这意味着,他们应该能够简单地提供类似静态块的东西,它会像这样自动调用:public class Custom {    static {        Main.configuration.add(Custom.class);    }}如果执行此代码,则配置列表为空(因为静态块的执行方式)。该类是 "reachable" ,但不是 "loaded"。您可以在 System.out 之前将以下内容添加到 Main 类Class.forName("Custom");并且该列表现在将包含自定义类对象(由于该类尚未初始化,此调用将对其进行初始化)。但是因为控件应该是反向的(自定义应该知道 Main 而不是相反),这不是一种可用的方法。绝不能直接从 Main 或与 Main 关联的任何类调用 Custom。但可能的情况如下:您可以向类添加注释并使用所述注释收集所有类,使用类图框架之类的东西并调用Class.forName它们中的每一个。TL; 博士有没有办法自动调用静态块,而无需分析所有类,也不需要知道具体的“自配置”类?Perfect 将是一种方法,即在启动应用程序时,自动初始化一个类(如果它们用某个注解进行注解)。我考虑过自定义类加载器,但据我所知,它们很懒惰,因此不适用于这种方法。这样做的背景是,我想将它合并到一个注释处理器中,它创建“自配置代码”。示例(警告:设计谈话和深度)为了让这不那么抽象,请想象以下内容:你开发一个框架。我们称它为 Foo。Foo 有类 GlobalRepository 和 Repository。GlobalRepository 遵循单例设计模式(仅限静态方法)。Repository 和 GlobalRepository 都有一个方法“void add(Object)”和“T get(Class)”。如果您在 Repository 上调用 get 并且找不到 Class,它会调用 GlobalRepository.get(Class)。为方便起见,您希望提供一个名为@Add 的注解。这个注解可以放在类型声明(又名类)上。注释处理器创建一些配置,这些配置会自动将所有带注释的类添加到 GlobalRepository 并因此减少样板代码。它应该只(在所有情况下)发生一次。因此,生成的代码有一个静态初始化程序,其中填充了 GlobalRepository,就像您对本地存储库所做的一样。因为您的配置的名称设计得尽可能独特,并且出于某种原因甚至包含创建日期(这有点武断,但请记住),所以它们几乎不可能被猜到。因此,您还向这些配置添加了一个注释,称为@AutoLoad。您需要使用开发人员调用 GlobalRepository.load(),然后分析所有类并初始化所有带有此注解的类,从而调用它们各自的静态块。这是一种不太可扩展的方法。应用程序越大,搜索范围越大,时间越长等等。更好的方法是,在启动应用程序时,所有类都会自动初始化。就像通过 ClassLoader 一样。像这样的东西就是我正在寻找的。
查看完整描述

1 回答

?
弑天下

TA贡献1818条经验 获得超8个赞

首先,不要Class在您的注册表中持有对象。这些Class对象需要您使用反射来获取实际操作,例如实例化它们或调用某些方法,无论如何您都需要事先知道其签名。


标准方法是使用 aninterface来描述动态组件应该支持的操作。然后,有一个实现实例的注册表。如果将它们分为操作接口和工厂接口,这些仍然允许推迟昂贵的操作。


例如 aCharsetProvider不是实际的Charset实现,而是按需提供对它们的访问。因此,只要仅使用通用字符集,现有的提供者注册表就不会消耗太多内存。


一旦定义了这样的服务接口,就可以使用标准的服务发现机制。对于包含类文件的 jar 文件或目录,您创建一个META-INF/services/包含文件名的子目录,作为包含实现类限定名称的接口的限定名称。每个类路径条目都可能有这样的资源。


在 Java 模块的情况下,您可以声明这样的实现更加健壮,使用


provides service.interface.name with actual.implementation.class;

模块声明中的语句。


然后,主类可以查找实现,只知道接口,如


List<MyService> registered = new ArrayList<>();

for(Iterator<MyService> i = ServiceLoader.load(MyService.class); i.hasNext();) {

    registered.add(i.next());

}

或者,从 Java 9 开始


List<MyService> registered = ServiceLoader.load(MyService.class)

    .stream().collect(Collectors.toList());

的类文档ServiceLoader包含有关此架构的更多详细信息。当您浏览标准 API的包列表以查找名称以 结尾的包时.spi,您会了解到这种机制在 JDK 本身中使用的频率。接口不需要在具有这样名称的包中,例如,java.sql.Driver也可以通过这种机制搜索 的实现。


从 Java 9 开始,您甚至可以使用它来执行诸如“Class为所有具有特定注释的类查找对象”之类的操作,例如


List<Class<?>> configuration = ServiceLoader.load(MyService.class)

    .stream()

    .map(ServiceLoader.Provider::type)

    .filter(c -> c.isAnnotationPresent(MyAnnotation.class))

    .collect(Collectors.toList());

但是由于这仍然需要类实现服务接口并声明为接口的实现,因此最好使用接口声明的方法与模块进行交互。


查看完整回答
反对 回复 2021-11-17
  • 1 回答
  • 0 关注
  • 130 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信