我知道编辑这个网站吗?

2011-07-27 - 同一 JVM 中 ImageJ 的多个实例

对于ImageJ 1.x,有一个程序的单例实例,可通过IJ.getInstance()访问。通过ImageJ2,我们希望提供一种管理多个 ImageJ“应用程序上下文”的机制。目前,ImageJ2 仍然是单例,但我们最近做了一些工作,为同时运行多个 ImageJ 应用程序铺平了道路。

在 ImageJ2 中,功能被划分为一组服务,这些服务是实现 IService 接口的类。每个服务类都有一个与其ImageJ应用程序上下文关联的实例。因此,一旦您拥有了 ImageJ 对象,您就可以通过调用 getService(Class<? extends IService>) 方法来请求它提供特定类的服务。对于多个应用程序上下文,棘手的部分可能是首先访问正确的ImageJ对象。

一种选择是 API 要求在许多地方传递 ImageJ 对象。例如,编写插件当前需要实现单个方法,run()。大多数插件需要访问应用程序上下文的一项或多项服务(例如,插件可能希望向DisplayService询问当前活动的Display)。我们可以将此签名更改为 run(ImageJ),但这样它就会与Runnable 接口不兼容。或者,我们可以添加另一个方法 setContext(ImageJ) 来通知插件上下文,或者需要一个采用 ImageJ 参数的构造函数,但这都会使编写插件变得更加复杂。第三种选择是将所需的服务声明为用 @Parameter 注释的字段,并在预处理期间填充。

目前,我们选择通过静态方法ImageJ.getContext()提供对当前应用程序上下文的访问。此方法检查当前线程的名称并提取上下文 ID(如果可用)。例如,当 ID 为 1 的 ImageJ 执行插件时,会生成名为 "ImageJ-1-ModuleRunner" 的线程。然后,getContext()方法可以仅根据线程名称来确定应用程序上下文。但是,此方法无法从标准线程(例如 AWT 事件分派线程)收集应用程序上下文。因此,当创建一个新的应用程序上下文时,我们使用一个名为"ImageJ-0-Initialization"的特殊初始化线程(其中“0”是正在创建的上下文的ID),并阻塞调用线程直到它完成。

要创建新的应用程序上下文,请调用静态ImageJ.createContext()方法,该方法使用所有可用服务实例化ImageJ对象(即,那些实现IService并用@Service注释的类,由SezPoz在运行时在类路径中发现)。或者,调用 ImageJ.createContext(createContext(Class<? extends IService>...)ImageJ.createContext(Collection<Class<? extends IService>>) 将创建一个仅包含给定服务(以及任何依赖项)的应用程序。无论哪种方式,上下文都保证有一个唯一的 ID,它将是下一个可用的递增值,从 0 开始。

为了初始化服务,ImageJ 按顺序检查给定的服务类列表,递归扫描依赖关系,如每个服务的构造函数中声明的那样。例如,PluginService 需要 ModuleService 才能运行,因此它声明了一个构造函数 PluginService(ImageJ, ModuleService),ImageJ 负责用正确的值填充该构造函数。 (附带说明:每个服务还声明一个无参数构造函数,但这仅仅是因为 SezPoz 需要它;它从未被使用,并且如果调用它,将抛出 UnsupportedOperationException。)使用的构造函数始终是第一个以 ImageJ 作为第一个参数的构造函数。任何附加参数都被视为依赖项,并预计为 IService 实现。这种范例允许更容易的依赖注入,例如对于服务类的单元测试很有用。初始化时,ImageJ 对其服务使用双通道方法:在第一通道中,它递归地构造实例,在第二通道中,它以相同的顺序对每个新实例调用 `IService.initialize()`。这确保了所有服务类在初始化之前就存在,并允许对循环依赖关系的有限支持。最后,在初始化后,可以使用 loadService(Class<? extends IService>)loadServices(Collection<Class<? extends IService>>) 方法将附加服务加载到ImageJ应用程序上下文中。

在 ImageJ 完全支持多个并发应用程序上下文之前,还存在一些问题。具体来说,有一些静态方法必须修复或消除:

  • ImageJ.get(Class<? extends IService>):此方法是ImageJ.getContext().getService(Class<? extends IService>)的快捷方式,是插件和其他外部代码用于获取应用程序服务访问权限的主要机制。幸运的是,使getContext()可靠地工作也可以修复此方法。但理想情况下,我们应该减少甚至可能消除这种方法的使用,转而采用其他方法。
  • Events.publishEvents.subscribe:作为面向多个应用程序上下文的重构的一部分,我们放弃了单个静态事件总线,现在有了一个带有自己的事件总线的EventService。因此,事件现在仅在特定上下文中本地发布。我们还在 ImageJEvent 中添加了 getContext() 方法,以便所有事件处理程序都可以轻松访问事件的应用程序上下文。不幸的是,整个代码库中有一百多个对静态 Events 类的引用。因此,我们将 Events.publish(ImageJEvent) 更新为 ImageJ.get(EventService.class).publish(ImageJEvent) 的快捷方式。将来,我们可能会消除 Events 类,或者至少彻底审查它所使用的所有地方。

特别是,从 EDT 或其他通用线程调用上述静态方法中的任何一个都会失败,因此必须将架构构造为能够正常工作,而无需这样做。不幸的是,目前有很多地方都以这种方式调用这些方法(特别是Events方法)。因此,目前,我们对 ImageJ.getContext() 进行了硬编码,默认返回 ImageJ.getContext(0),这有效地将 ImageJ 限制为单例应用程序。我们将来会重新讨论这个问题,但目前还有更紧迫的问题(#632#694#660many more)。