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:
- 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.
- Use AnnotationLiteral, which provides
equals
and hashCode
implementations for arbitrary subclasses.
- 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();
} // ...
}
}