I am having trouble with the Dagger 2 dependency injection framework. I would like to create an EagerSingleton. I assume that dagger 2 creates lazy loaded singletons when I use the @Singleton
annotation. How do I create EagerSingleton using the Dagger 2 framework ?
问题:
回答1:
I solved this by creating an EagerModule
which had a single provide method which returned Void
. Everything I wanted created eagerly I specified as parameters to that method. Then I added a Void init()
method to the Component which I call immediately after creating the Component.
@Module
public class EagerModule {
@Provides
@Nullable
Void provideEager(...) {
// this eagerly builds any parameters specified and returns nothing
return null;
}
}
@Component(modules = {EagerModule.class})
public interface TestComponent {
@Nullable
Void init();
}
This obviously doesn't give you access to the eagerly created singletons, but for my usage I didn't need access, I just wanted them created.
回答2:
I figured out how to do this:
as per the docs dagger does not support eagerSingletons directly so:
Work around this by creating an EagerSingletons class that declares static fields for each eager singleton. Method injection.
So i created a EagerSingletons class in any module i want to use a eager singleton. In that eagerSingletons class i would declare any singleton i want to use. Thats it, dagger then makes the singletons eager.
UPDATE: The reason i used dagger 1 as an example, is this is how its done in dagger 2. there really is no mechanism for eagerSingletons. you have to make it using a static field. Let me give you an example of how you might create a eagerSingleton:
In every module you need a eager singleton you could have this:
//assume this file is called myModule.java
static EagerObjects eagerObjects;
public static void initEagerObjects() {
if (eagerObjects == null) {
eagerObjects = new EagerObjects();
}
}
//so far so good, only one object will be created, lets inject what we need immediately
public static class EagerObjects {
public EagerObjects() {
//inject right away,you'll have to figure out a way to pass the graph in. by constructor param should be fine
getGraph().inject(this);
}
//make this injection static
@Inject
static CoffeePot coffeePot;
}
}
Now to find a way to call this right away on application launch....
Back in your dagger component or in the application class you extend you can have a static method to call each one of these per module:
static void injectAllEagerObjects() {
myModule.initEagerObjects();
someOtherModule.initEagerObjects();
//...all of them can be here since there static
}
now we are almost done, just have to call it at application launch. So right after you create your graph from application you have to call injectAllEagerObjects() (and probably pass in your graph instance there if you want). This will initialize the eager singletons right way and only once.
all this being said, i wish dagger just had a annotation you could use like this: @singleton(eager=true) but static fields, this is what they recommend for now.
回答3:
This is a copy-pastable example that I came up with for Dagger2, I don't think there is a way to avoid (redundantly) declaring in the component which modules have eager objects.
It would be nice if someone could provide a less boilerplate-y example.
Main.java
import dagger.Component;
import dagger.Module;
import dagger.Provides;
import javax.inject.Inject;
import javax.inject.Singleton;
public class Main {
public static void main(String[] args) {
TestComponent component = DaggerTestComponent.create();
System.out.println("Created component.");
component.createEagerSingletons();
}
}
@Component(modules = {TestModule1.class, TestModule2.class})
@Singleton
abstract class TestComponent {
abstract EagerSingletons createEagerSingletons();
static class EagerSingletons {
@Inject EagerSingletons() { System.out.println("Building all eager objects.."); }
@Inject TestModule1.EagerSingletons m1;
@Inject TestModule2.EagerSingletons m2;
}
}
@Module
class TestModule1 {
static class Thing1 { @Inject Thing1(){}}
static class Thing2 { @Inject Thing2(){}}
@Provides
@Singleton
Thing1 first() { return new Thing1(); }
@Provides
@Singleton
Thing2 secon() { return new Thing2(); }
static class EagerSingletons {
@Inject Thing1 a;
@Inject Thing2 b;
@Inject EagerSingletons() { System.out.println("[1] Eagerly built objects!"); }
}
}
@Module
class TestModule2 {
static class EagerSingletons {
@Inject EagerSingletons() { System.out.println("[2] Eagerly built objects!"); }
}
}
The main method will print:
Created component.
Building all eager objects..
[1] Eagerly built objects!
[2] Eagerly built objects!
回答4:
It can be solved by using dagger multibindings. First, you need to create the interface:
public interface EagerInit {
void eagerInit();
}
In the EagerModule
you bind EagerInit
implementations to set, so you can access it in the EagerComponent
:
@Module
public abstract class EagerModule {
@Binds
@IntoSet
abstract EagerInit eagerInitImpl1(EagerInitImpl1 eagerInitImpl1);
@Binds
@IntoSet
abstract EagerInit eagerInitImpl2(EagerInitImpl1 eagerInitImpl2);
}
@Component(modules = {EagerModule.class})
public interface EagerComponent {
Set<EagerInit> getEagerInits();
}
After creating the EagerComponent
you simply call:
component.getEagerInits().forEach(EagerInit::eagerInit);