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());
但是由于这仍然需要类实现服务接口并声明为接口的实现,因此最好使用接口声明的方法与模块进行交互。
添加回答
举报