首页 > 解决方案 > 面向上下文的运行时 CDI 限定符(HK2,Guice)

问题描述

我有兴趣使用 HK2 或 Guice 作为依赖注入框架。我知道@Named,@Qualifier和自定义注释等。但这些都是编译时的。

我正在寻找一种工具来根据运行时上下文动态确定所需的具体类型并注入正确的实现。

HK2 或 Guice 中是否有类似的东西或实现此目标的推荐方式?

例如:

// I would want to turn this...
public final class Handler
{ 
  private final Session session;
   
  @Inject   
  public Handler(@Named("Database") final Session session)
  {
    this.session = session;
  }
  ...
}

// into something like this...
public final class Handler
{
  private final Session session;
    
  @Inject
  public Handler(final Session session)
  {
    this.session = session;
  }
}

// where "session" is injected based on some previous context value ("Database")
// or something to that effect.

标签: javadependency-injectioncdiguicehk2

解决方案


我最终在HK2中使用了一个名为Operations的功能(链接到文档)。它允许 HK2 的用户定义自定义范围并将它们作为“操作”进行管理。您可以在 HK2 的 github 项目中找到有关如何使用该功能的更详细示例:操作示例

这是一个简化的示例,说明我最终如何使用此功能基于上下文或在本例中的“范围”注入事物。

这是一些几乎可以工作的伪代码来演示我的用法:

// Create the custom scope annotation.
@Scope
@Proxiable(proxyForSameScope = false)
@Documented
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface BatchScope
{
  public static final BatchScope INSTANCE = new BatchScopeEnvoy();
}


final class BatchScopeEnvoy extends AnnotationLiteral<BatchScope> implements BatchScope
{
  private static final long serialVersionUID = 938233179310254573L;
}


// Create a context used by the HK2 operation feature.
@Singleton
public final class BatchScopeContext extends OperationContext<BatchScope>
{
  @Override
  public Class<? extends Annotation> getScope()
  {
    return BatchScope.class;
  }
}


// Create a class that holds your custom scope data/context.
public final class BatchScopeRuntime
{
   // ... Arbitrary runtime data here ...
     
     public SomeData getData()
     {
       return this.data;
     }
}


// Create a factory that serves up something you want to inject from a custom scope.
@Singleton
public final class DataFactory implements Factory<SomeData>
{
  private final OperationManager operations;

  @Inject
  public BatchInfoFactory(final OperationManager operations)
  {
    Sentinel.assertIsNotNull(operations);

    this.operations = operations;
  }


  // The @BatchScope on the provide() method indicates that objects returned 
  // from this factory are in the "BatchScope".
  @Override
  @BatchScope
  public IBatchInfo provide()
  {
    final OperationHandle handle = this.operations.getCurrentOperation(BatchScope.INSTANCE);
    final BatchScopeRuntime runtime = (BatchScopeRuntime)handle.getOperationData();

    return runtime.getData();
  }

  @Override
  public void dispose(final IBatchInfo instance)
  {
    // Do nothing.
  }
}


// Setup the injector.
public static ServiceLocator createInjector(final String name)
{
    final ServiceLocator injector = ServiceLocatorFactory.getInstance().create(name);

    ServiceLocatorUtilities.bind(
      injector,
      new AbstractBinder()
      {
        @Override
        protected void configure()
        {
           // This creates a "Singleton" factory that provides
           // "SomeData" instances at "BatchScope".
           bindFactory(DataFactory.class, Singleton.class)
            .to(SomeData.class)
            .in(BatchScope.class);
        }
      }
        
    return injector;
}


// Create a class that needs something in the custom scope.
public final class Foo
{
  @Inject
  public Foo(final SomeData data)
  {
    System.out.printf("I got: %s%n", data);
  }
}   


// Usage: how to manage the scopes using the operations feature.
final SomeData data = ... // get some data
final BatchScopeRuntime runtime = new BatchScopeRuntime(data); // Setup the runtime information.

// Create an operation handle for the custom scope and associate the custom data with it.
final ServiceLocator injector = createInjector("test");
ServiceLocatorUtilities.addClasses(injector, BatchScopeContext.class, Foo.class);
final OperationManager operations = injector.getService(OperationManager.class);
final OperationHandle<BatchScope> batchScope = operations.createAndStartOperation(BatchScope.INSTANCE);

// Operation/scope is now associated with the current thread.           
batchScope.setOperationData(runtime);

// Foo will now be injected with: "data" from above.
final Foo foo = injector.getService(Foo.class);

// Do some work...

// Close the operation (make it go out of scope) on the current thread.
batchScope.closeOperation();

推荐阅读