自动调用静态块而不显式调用 Class.forName
Automatically call static block without explicitly calling Class.forName
假设以下代码:
public class Main {
public static final List<Object> configuration = new ArrayList<>();
public static void main(String[] args) {
System.out.println(configuration);
}
}
我现在希望能够,提供"self-configuring"classes。这意味着,他们应该能够简单地提供类似于静态块的东西,它将像这样自动调用:
public class Custom {
static {
Main.configuration.add(Custom.class);
}
}
如果执行此代码,配置列表为空(因为way static blocks are executed). The class is "reachable", but not "loaded"。您可以在 System.out[=18= 之前的 Main class 添加以下内容]
Class.forName("Custom");
列表现在将包含自定义 class 对象(因为 class 尚未初始化,此调用会初始化它)。但是因为控件应该是反向的(Custom 应该知道 Main 而不是相反),这不是一个可用的方法。永远不应直接从 Main 或与 Main 关联的任何 class 调用自定义。
虽然以下是可能的:您可以向 class 添加注释并收集所有带有所述注释的 classes,使用类似 ClassGraph framework 的东西并调用Class.forName
他们每个人。
TL;DR
有没有一种方法可以自动调用静态块而不需要分析所有 classes 和了解具体的需要,"self configuring" class?完美的方法是,在启动应用程序时,自动初始化一个 classes(如果它们用某个注释进行注释)。我考虑过自定义 ClassLoaders,但据我了解,they are lazy 因此不适用于这种方法。
这样做的背景是,我想将它合并到注释处理器中,创建 "self configuring code"。
示例(警告:设计讨论和深入)
为了让这个不那么抽象,想象一下:
你开发了一个框架。我们称它为 Foo。 Foo 有 classes GlobalRepository 和 Repository。 GlobalRepository 遵循单例设计模式(仅限静态方法)。 Repository 和 GlobalRepository 都有一个方法 "void add(Object)" 和“T get(Class)”。如果您在存储库上调用 get 并且找不到 Class,它会调用 GlobalRepository.get(Class).
为了方便起见,您想提供一个名为@Add 的注解。这个注释可以放在类型声明上(又名 Classes)。注释处理器创建一些配置,自动将所有带注释的 classes 添加到 GlobalRepository 中,从而减少样板代码。它应该(在所有情况下)只发生一次。因此生成的代码有一个静态初始化器,其中填充了 GlobalRepository,就像您对本地存储库所做的那样。因为您的配置的名称被设计为尽可能唯一,并且出于某种原因甚至包含创建日期(这有点武断,但请注意),它们几乎不可能被猜到。
因此,您还为这些配置添加了一个注释,称为@AutoLoad。您需要使用开发人员调用 GlobalRepository.load(),然后分析所有 classes 并初始化所有带有此注释的 classes,并因此调用它们各自的静态块。
这是一种可扩展性不强的方法。应用程序越大,搜索的范围越大,时间越长等等。更好的方法是,在启动应用程序时,所有 classes 都会自动初始化。就像通过 ClassLoader。我正在寻找这样的东西。
首先,不要在注册表中保留 Class
对象。这些 Class
对象需要您使用反射来获得实际操作,例如实例化它们或调用某些方法,无论如何您都需要事先知道其签名。
标准方法是使用 interface
来描述动态组件应该支持的操作。然后,有一个实现实例的注册表。如果您将它们分为操作界面和工厂界面,这些仍然允许推迟昂贵的操作。
例如CharsetProvider
is not the actual Charset
实现,但按需提供对它们的访问。所以现有的provider registry只要使用common charsets就不会消耗太多内存。
一旦定义了这样的服务接口,就可以使用标准的服务发现机制。对于包含 class 文件的 jar 文件或目录,您可以创建一个子目录 META-INF/services/
,其中包含一个文件名作为接口的限定名称,该接口包含实现的限定名称 classes。每个 class 路径条目可能有这样一个资源。
在 Java 模块的情况下,您可以使用
声明这样的实现更加健壮
provides service.interface.name with actual.implementation.class;
模块声明中的语句。
然后,主要class可能会查找实现,只知道接口,如
List<MyService> registered = new ArrayList<>();
for(Iterator<MyService> i = ServiceLoader.load(MyService.class); i.hasNext();) {
registered.add(i.next());
}
或者,从 Java9
开始
List<MyService> registered = ServiceLoader.load(MyService.class)
.stream().collect(Collectors.toList());
class documentation of ServiceLoader
contains a lot more details about this architecture. When you go through the package list of the standard API 寻找名称以 .spi
结尾的包,您会知道,这种机制在 JDK 本身中使用的频率。但是,接口不需要位于具有此类名称的包中,例如java.sql.Driver
的实现也通过此机制进行搜索。
从 Java 9 开始,您甚至可以使用它来执行诸如“为所有具有特定注释的 class 查找 Class
对象”之类的操作,例如
List<Class<?>> configuration = ServiceLoader.load(MyService.class)
.stream()
.map(ServiceLoader.Provider::type)
.filter(c -> c.isAnnotationPresent(MyAnnotation.class))
.collect(Collectors.toList());
但由于这仍然需要 classes 实现服务接口并被声明为接口的实现,因此最好使用接口声明的方法与模块交互。
假设以下代码:
public class Main {
public static final List<Object> configuration = new ArrayList<>();
public static void main(String[] args) {
System.out.println(configuration);
}
}
我现在希望能够,提供"self-configuring"classes。这意味着,他们应该能够简单地提供类似于静态块的东西,它将像这样自动调用:
public class Custom {
static {
Main.configuration.add(Custom.class);
}
}
如果执行此代码,配置列表为空(因为way static blocks are executed). The class is "reachable", but not "loaded"。您可以在 System.out[=18= 之前的 Main class 添加以下内容]
Class.forName("Custom");
列表现在将包含自定义 class 对象(因为 class 尚未初始化,此调用会初始化它)。但是因为控件应该是反向的(Custom 应该知道 Main 而不是相反),这不是一个可用的方法。永远不应直接从 Main 或与 Main 关联的任何 class 调用自定义。
虽然以下是可能的:您可以向 class 添加注释并收集所有带有所述注释的 classes,使用类似 ClassGraph framework 的东西并调用Class.forName
他们每个人。
TL;DR
有没有一种方法可以自动调用静态块而不需要分析所有 classes 和了解具体的需要,"self configuring" class?完美的方法是,在启动应用程序时,自动初始化一个 classes(如果它们用某个注释进行注释)。我考虑过自定义 ClassLoaders,但据我了解,they are lazy 因此不适用于这种方法。
这样做的背景是,我想将它合并到注释处理器中,创建 "self configuring code"。
示例(警告:设计讨论和深入)
为了让这个不那么抽象,想象一下:
你开发了一个框架。我们称它为 Foo。 Foo 有 classes GlobalRepository 和 Repository。 GlobalRepository 遵循单例设计模式(仅限静态方法)。 Repository 和 GlobalRepository 都有一个方法 "void add(Object)" 和“T get(Class)”。如果您在存储库上调用 get 并且找不到 Class,它会调用 GlobalRepository.get(Class).
为了方便起见,您想提供一个名为@Add 的注解。这个注释可以放在类型声明上(又名 Classes)。注释处理器创建一些配置,自动将所有带注释的 classes 添加到 GlobalRepository 中,从而减少样板代码。它应该(在所有情况下)只发生一次。因此生成的代码有一个静态初始化器,其中填充了 GlobalRepository,就像您对本地存储库所做的那样。因为您的配置的名称被设计为尽可能唯一,并且出于某种原因甚至包含创建日期(这有点武断,但请注意),它们几乎不可能被猜到。
因此,您还为这些配置添加了一个注释,称为@AutoLoad。您需要使用开发人员调用 GlobalRepository.load(),然后分析所有 classes 并初始化所有带有此注释的 classes,并因此调用它们各自的静态块。
这是一种可扩展性不强的方法。应用程序越大,搜索的范围越大,时间越长等等。更好的方法是,在启动应用程序时,所有 classes 都会自动初始化。就像通过 ClassLoader。我正在寻找这样的东西。
首先,不要在注册表中保留 Class
对象。这些 Class
对象需要您使用反射来获得实际操作,例如实例化它们或调用某些方法,无论如何您都需要事先知道其签名。
标准方法是使用 interface
来描述动态组件应该支持的操作。然后,有一个实现实例的注册表。如果您将它们分为操作界面和工厂界面,这些仍然允许推迟昂贵的操作。
例如CharsetProvider
is not the actual Charset
实现,但按需提供对它们的访问。所以现有的provider registry只要使用common charsets就不会消耗太多内存。
一旦定义了这样的服务接口,就可以使用标准的服务发现机制。对于包含 class 文件的 jar 文件或目录,您可以创建一个子目录 META-INF/services/
,其中包含一个文件名作为接口的限定名称,该接口包含实现的限定名称 classes。每个 class 路径条目可能有这样一个资源。
在 Java 模块的情况下,您可以使用
声明这样的实现更加健壮provides service.interface.name with actual.implementation.class;
模块声明中的语句。
然后,主要class可能会查找实现,只知道接口,如
List<MyService> registered = new ArrayList<>();
for(Iterator<MyService> i = ServiceLoader.load(MyService.class); i.hasNext();) {
registered.add(i.next());
}
或者,从 Java9
开始List<MyService> registered = ServiceLoader.load(MyService.class)
.stream().collect(Collectors.toList());
class documentation of ServiceLoader
contains a lot more details about this architecture. When you go through the package list of the standard API 寻找名称以 .spi
结尾的包,您会知道,这种机制在 JDK 本身中使用的频率。但是,接口不需要位于具有此类名称的包中,例如java.sql.Driver
的实现也通过此机制进行搜索。
从 Java 9 开始,您甚至可以使用它来执行诸如“为所有具有特定注释的 class 查找 Class
对象”之类的操作,例如
List<Class<?>> configuration = ServiceLoader.load(MyService.class)
.stream()
.map(ServiceLoader.Provider::type)
.filter(c -> c.isAnnotationPresent(MyAnnotation.class))
.collect(Collectors.toList());
但由于这仍然需要 classes 实现服务接口并被声明为接口的实现,因此最好使用接口声明的方法与模块交互。