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."
We recommend that you use On-Demand Instances for applications with short-term, irregular workloads that cannot be interrupted. For significant savings over On-Demand Instances, use AWS Savings Plans, Spot Instances, or Reserved Instances .
When we provide the service in the @ngModule of the root module or any eagerly loaded module, the will be available everywhere in the application. The Services provided in the @ngModule of the lazy loaded module are available in that module only. Each Injector creates a singleton object of the dependency registered by the provider.
Angular will create new instances for any of InjectionToken or Injectable in cases of using: This is happening because Angular creates a new module Injector for any lazy loaded module, this behavior is perfectly described in docs and this article.
It can use a factory function, which returns the instance of service class or value ( useFactory ). It can return the instance from an already existing token ( useExisting ).
https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/util/Modules.html
The override methods are probably what you want
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With