Guy Grin Guy Grin - 3 months ago 33
Java Question

Guice multiple annotations

I have an interface called

. I have 2 implementations of this store. An in-memory and an SQL implementation called
. In order to inject them I've create 2 annotations
. the injections are:



Now I want to add a new layer of annotation to separate between
but I can't add more than one annotation to the binding lines e.g. the following does not compile:

.annotatedWith(NumberStoreAnnotation.class) // using named doesn't work as well

How can I add more than one annotation without using a single named one which would be quite complicated the more layers I add to it?

The other solution I had in mind is Injecting twice:



Thanks all.


As Amit said, you can't have more than one @BindingAnnotation apply to any given injection. Internally, Guice works like a Map<Key, Provider> where a Key is a possibly-parameterized class with an optional single annotation instance. However, because these are instances, you're welcome to create your own instantiable annotation that works the way Named works.

@Inject @InMemoryStore(NUMBER) StatsStore inMemoryNumberStore;
@Inject @SqlStore(STRING) StatsStore sqlStringStore;
// or
@Inject @Store(dataType=NUMBER, backend=SQL) sqlNumberStore;

The annotation must have the fields defined like so. (If you have one element named value, you can omit the property name per JLS 9.7.3.) Equal annotations are defined as in the Annotation.equals docs.

public enum DataType { NUMBER, STRING; }
public enum Backend { SQL, IN_MEMORY; }

@BindingAnnotation @Retention(SOURCE) @Target({ FIELD, PARAMETER, METHOD })
public @interface Store {
  DataType dataType();
  Backend backend();

That works nicely for @Provides, when you can invoke the annotation the same way you inject it, but how can you create a factory method for instances like Names.named? For that, you'll need to do one of the following:

  1. Create an anonymous implementation, with accessors for each attribute as well as correct implementations of equals and hashCode. Note that the hashCode implementation is much stricter than for Object, but you can get compatible implementations from Apache annotation utils or similar libraries.
  2. Use AnnotationLiteral, which provides equals and hashCode implementations for arbitrary subclasses.
  3. Use Google Auto or a similar code generator to generate code for a compatible implementation for you. Familiarity with this type of solution is particularly useful for Android and other memory-constrained environments for which reflection is slow, though such environments usually preclude you from using Guice. (@Qualifier annotations work the same way in other JSR-330 compatible dependency injection frameworks, though, including Dagger.)

If the above seems a little complicated, or if you want more complex logic than Guice's map-based implementation can accomplishone alternative is to add a layer of indirection that you control:

public class StoreStore {
  @Inject Provider<InMemoryNumberStore> inMemoryNumberStoreProvider;
  // ...
  // You can also inject the Injector to call getInstance with a class literal.

  public StatsStore getStore(DataType dataType, Backend backend) {
    // This can also be a switch or any other sort of lookup, of course.
    if (dataType == NUMBER && backend == IN_MEMORY) {
      return inMemoryNumberStoreProvider.get();
    } // ...