Reputation: 247
My application uses Guice for Dependency Injection and consists of several modules, some depend on an instance of class X and some need to be able to run independently of the main application. So in the MainModule I have to provide an instance of class X, while some sub-modules also need to provide that instance, since their respective applications need to be able to run without the MainModule providing said instance of class X. Which leads to errors because "an instance of class X was already bound".
I have been looking around for a while now, but mostly I find references to PrivateModules which don't really do what I need, also I found a lot on OptionalBindings which, as far as I understand, mainly provide default values.
What I need is some sort of conditional binding as in "If another module provides an instance of class X do nothing, if no other module provides an instance of class X provide this one."
Upvotes: 8
Views: 817
Reputation: 6969
The problem is caused by insufficient isolation of various sub-systems.
From a sub-system S
point of view, type X
has changed its contract over time. In the past, the type gave S
ability to control all of its instances. But with integrations, control of instances was lost. Type X
changed its behavior in an incompatible way.
Sub-system S
should have been more isolated. It should not have used types that are potentially in conflict with other systems. It should have used its own private types instead, there'd be no issue then.
Now let's say in our hypothetical problem, there is a Database
sub-system. It uses timeout when issuing network calls. It wants an instance of Timeout
that has the value of 100 millis. EmailSender
sub-system expects to be slower. Its Timeout
has to equal 5 seconds. The systems conflict when integrated.
// EmailSender-private wrapper. Class is not public on purpose.
class EmailSenderTimeout {
final Timeout timeout;
EmailSenderTimeout(Timeout t) { this.timeout = t; }
}
// In the module
bind(EmailSenderTimeout.class)
.toInstance(new EmailSenderTimeout(Timeout.seconds(5));
// In the service
@Inject
EmailSendingService(EmailSenderTimeout timeout) {
long millis = timeout.timeout.millis();
}
Walla! Should anyone ever go and bind Timeout
to whatever their heart desires, we the EmailSender
still have our 5 seconds!
We achieved through isolation. We are still sharing the Timeout
type, but we are no longer sharing instances.
This mechanism is Guice's answer to our exact problem.
// Define this annotation once in the sub-system somewhere.
// Perhaps even directly in the Module class.
@Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation
@interface ForEmail { }
// EmailModule
protected void configure() {
bind(Timeout.class).annotatedWith(ForEmail.class)
.toInstance(Timeout.seconds(5);
}
// Service
class EmailSendingService {
@Inject
EmailServiceImpl(@ForEmail Timeout timeout) {
long millis = timeout.millis();
}
}
You can reuse the annotation for other shared types, too:
class EmailServiceImpl {
@Inject
EmailServiceImpl(@ForEmail Timeout timeout,
@ForEmail RemoteAddress remoteAddress,
@ForEmail Protocol protocol) {
}
}
Each sub-system would declare its own private binding annotation and use it throughout.
In absolute, no two sub-systems should bind the same types, whether or not they are integrated today.
There must never be duplicates in bindings
:
class Guice {
HashMap<Key, Provider> bindings;
}
// Combines 3 things: Class, Generic Types, and Annotation
class Key {
Class<?> actualClass;
@Nullable Class<?> annotationClass;
@Nullable Type genericTypes;
}
More details: Key.java, TypeLiteral.java
Upvotes: 1
Reputation: 1540
I think you can use Module-equality to address this need:
Guice adds each module that you install
into a Set
, which has the effect of deduplicating any Modules that are redundantly installed, as long as they considered equal
.
So, you can embed the binding of class X in its own Module
, and ensure that its module-class has an equals
method which will identify other instances of the same module as equal.
For example:
class XModule extends AbstractModule {
@Override
protected void configure() {
bind(X.class).to(RealX.class); // whatever you need to deduplicate
}
@Override
public boolean equals(Object other) {
return other == this || (other instanceof XModule);
}
@Override
public int hashCode() {
return XModule.class.hashCode();
}
}
class ModuleA extends AbstractModule {
@Override
protected void configure() {
install(new XModule());
}
}
class ModuleB extends AbstractModule {
@Override
protected void configure() {
install(new ModuleA());
install(new XModule()); // will be deduplicated
}
}
(this is also discussed here: Ensure Module Is Loaded Only Once In Guice)
Upvotes: 0
Reputation: 281
https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/util/Modules.html
The override methods are probably what you want
Upvotes: 1