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:
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. equals
and hashCode
implementations for arbitrary subclasses. @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.