akagixxer
akagixxer

Reputation: 1859

Context Oriented Runtime CDI Qualifiers (HK2, Guice)

I'm interested in using HK2 or Guice for a dependency injection framework. I know of @Named, @Qualifier, and custom annotations etc. But these are all compile-time.

I am looking for a facility to dynamically determine the desired concrete type based on runtime context and inject the correct implementation.

Is there something like that in HK2 or Guice or a recommended way of achieving this?

For example:

// 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.

Upvotes: 0

Views: 268

Answers (1)

akagixxer
akagixxer

Reputation: 1859

I ended up using a feature in HK2 called Operations (link to docs). It allows a user of HK2 to define custom scopes and manage them as "operations". You can find a more detailed example of how to use the feature on HK2's github project: operations example.

This is a simplified example of how I ended up using this feature to inject things based on context or in this case "scope".

Here is some almost-working pseudo-code to demonstrate my usage:

// 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();

Upvotes: 1

Related Questions