简体   繁体   中英

Guice multiple annotations

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

bind(StatsStore.class)
    .annotatedWith(InMemoryStore.class)
    .to(InMemoryStatsStore.class);

bind(StatsStore.class)
    .annotatedWith(SqlStore.class)
    .to(SqlStatsStore.class);   

Now I want to add a new layer of annotation to separate between InMemoryStringStore and InMemoryNumberStore but I can't add more than one annotation to the binding lines eg the following does not compile:

bind(StatsStore.class)
    .annotatedWith(InMemoryStore.class)
    .annotatedWith(NumberStoreAnnotation.class) // using named doesn't work as well
    .to(InMemoryNumberStore.class);  

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:

bind(StatsStore.class)
    .annotatedWith(InMemoryStore.class)
    .to(InMemoryStatsStore.class);

 bind(InMemoryStatsStore.class)
    .annotatedWith(NumberStoreAnnotation.class)
    .to(InMemoryNumberStore.class);

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 contract 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 accomplish, one 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();
    } // ...
  }
}

You can't do that :

@BindingAnnotation tells Guice that this is a binding annotation. Guice will produce an error if ever multiple binding annotations apply to the same member .

You could use named bindings instead, or you should consider redesigning your solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM