HK2:从子定位器访问 RunLevel 范围内的服务

HK2: accessing RunLevel scoped services from child locator

我在 Java SE 应用程序中使用 Jersey。 HK2 为整个应用程序提供依赖注入。 HK2 RunLevel 服务在应用程序服务定位器中注册,它是 Jerseys 服务定位器的父级。

+ application locator
|\- RunLevel capabilities
| - MyCustomService, @RunLevel(value=1)
 \
  + jersey locator
   \- jersey resource class
     \ @Inject MyCustomService

我的问题是我无法从 Jersey 访问运行级别范围内的服务。当 - 在上面的示例中 - 打开球衣资源时,MyCustomService 的注入失败:

java.lang.IllegalStateException: Could not find an active context for org.glassfish.hk2.runlevel.RunLevel

这似乎是因为 HK2 RunLevel 功能背后的服务具有可见性 LOCAL: The jersey locator cannot access them via its parent locator. See here

问题:

更新

为了给问题提供上下文,我使用 "System-V" 风格的运行级别。

这个概念允许对可用性和长 运行 请求进行精细控制。关闭时,应用程序将处于运行级别 2,直到先前接受的 HTTP 请求得到满足并且排队的后台任务完成。但是,不接受新的 tasks/requests。然后,runlevel 1, 0, -1, exit.

children 和 RunLevelService 的想法是,实际的 RunLevelServices 将是 child 定位器中的精简服务,在 parent 中编排实际服务。一个进程中的多个 "subsystems" 可能有不同的 RunLevelService "compartments," 每个在它自己的 child 中 parent.

在该模型中,您 start/add children 中的 RunLevelService,而不是 parent 中的 RunLevelService。通过这种方式,您可以在同一 parent.

的不同 children 的不同级别拥有多个 RunLevelServices

听起来你有不同的用例,但可能无法完全奏效。值得考虑您的用例。

更新:这不起作用!

出于教育目的,我将把它留在这里,但是不要这样做!一旦从 jersey 的服务定位器中访问 RunLevelController,它就会丢失在另一个 服务定位器上跟踪它管理的所有服务

更新结束

我已经用这个 hack 解决了它:我故意覆盖 LOCAL 可见性并将所需的描述符插入到缺少它们的服务定位器中。

public class RunLevelBridge implements Feature {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private final ServiceLocator sourceLocator;

    public RunLevelBridge(ServiceLocator sourceLocator) {
        this.sourceLocator = sourceLocator;
    }

    @Override
    public boolean configure(FeatureContext context) {
        if (sourceLocator == null) {
            log.error("Unable to bridge descriptors, the source service locator cannot be null");
            return false;
        }

        InjectionManager im = InjectionManagerProvider.getInjectionManager(context);
        ServiceLocator jerseyLocator = im.getInstance(ServiceLocator.class);
        if (jerseyLocator == null) {
            log.error("Unable to bridge descriptors, the target service locator cannot be null");
            return false;
        }

        if (!sourceLocator.equals(jerseyLocator.getParent())) {
            ExtrasUtilities.bridgeServiceLocator(jerseyLocator, sourceLocator);
            log.info("Bridge from {} into {} established", sourceLocator.getName(), jerseyLocator.getName());
        }

        Filter filter;
        filter = BuilderHelper.createContractFilter(RunLevelContext.class.getName());
        sourceLocator.getDescriptors(filter).forEach(
                (descriptor) -> ServiceLocatorUtilities.addOneDescriptor(
                        jerseyLocator, 
                        descriptor, 
                        false
                )
        );
        filter = BuilderHelper.createContractFilter(RunLevelController.class.getName());
        sourceLocator.getDescriptors(filter).forEach(
                (descriptor) -> ServiceLocatorUtilities.addOneDescriptor(
                        jerseyLocator, 
                        descriptor, 
                        false
                )
        );

        log.info("Added the RunLevel feature to jersey's service locator");
        return true;
    }

}

网桥注册在ResourceConfig:

public class ApplicationConfig extends ResourceConfig {

    public ApplicationConfig(ServiceLocator parentLocator) {

        // bridge runlevel into jersey
        register(new RunLevelBridge(parentLocator));

        // ... other stuff ...

    }
}

我鼓励对这种非正统的方法提出反馈意见。

解决方案是尊重 DescriptorVisibility#LOCAL 并注入依赖于 RunLevelContext 的服务,仅来自管理它们的服务定位器

有点麻烦:

  • 从 Jersey 资源中,通过名称
  • 获取具有运行级别功能的服务定位器
  • 明确注入运行级别范围的服务

    ServiceLocator applicationLocator = ServiceLocatorFactory.getInstance().find("applicationLocator");
    MyCustomService mcs = applicationLocator.getService(MyCustomService.class);
    mcs.doSomething();
    

为了减少忘记这样做的危险,只是将 MyCustomService 注入球衣资源,我现在将我的运行级别范围的服务标记为 also[= DescriptorVisibility#LOCAL 的 30=]。这样他们就不会被球衣定位器注入。