About Dagger2, which is a DI library for Java / Android, the official sample Coffee app I would like to explain the mechanism based on (/ master / examples / simple / src / main / java / coffee).
However, the Coffee app was a little hard to understand (@Binds
, Lazy
, etc.), so the code described below has been slightly modified.
Dagger2 User Guide can be understood with knowledge from the top to the middle (Singletons and Scoped Bindings).
What this sample is trying to do is create an instance of the CoffeeMaker class with Dagger2.
The CoffeeMaker class has the following dependencies.
https://docs.google.com/presentation/d/1fby5VeGU9CN8zjw4lAb2QPPsKRxx6mSwCe9q7ECNSJQ/pub?start=false&loop=false&delayms=3000&slide=id.p
Resolving this dependency and getting a CoffeeMaker instance can be a bit tricky.
Heater heater = new ElectricHeater();
Pump pump = new Thermosiphon(heater);
CoffeeMaker coffeeMaker = new CoffeeMaker(heater, pump).maker();
coffeeMaker.brew();
Using Dagger2, the above code looks like this:
CoffeeMaker coffeeMaker = DaggerCoffeeShop.create().maker();
coffeeMaker.brew();
In this way, CoffeeMaker client code is freed from the complexity of building dependencies. The following describes how Dagger2 builds these dependencies.
Dagger instantiates using a constructor annotated with @Inject
.
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
@Override
public void pump() {
if (heater.isHot()) {
System.out.println("=> => pumping => =>");
}
}
}
If a Thermosiphon instance is requested, a Thermosiphon instance will be created using the above constructor.
Here, the Thermosiphon class depends on the Heater class. Dagger identifies the dependencies defined in the constructor and resolves them.
Dagger tries to satisfy the dependency by instantiating the requested type, but the declaration of dependency by @Inject
does not always work.
In the following cases, @Inject
will not work.
--Type is an interface --3rd party classes cannot be annotated --Configurable objects (it seems to be objects that are set by setter etc. after instantiation) need to be set.
In the Coffee app, the types that CoffeeMaker depends on (Heater and Pump) are declared as interfaces, which is the case above.
In these cases, the method annotated with @Provides
creates an instance.
For example, Heater declares a dependency as follows:
Define a method annotated with @ Provides
and specify Heater as the return type.
@Provides
static Heater provideHeater() {
return new ElectricHeater();
}
Similarly, Thermosiphon declares:
The @ Provides
method can have its own dependencies.
@Provides
static Pump providePump(Thermosiphon pump) {
return pump;
}
The @ Provides
method must belong to a module.
A module is a class that is annotated with @Module
.
@Module
class DripCoffeeModule {
@Provides
static Heater provideHeater() {
return new ElectricHeater();
}
@Provides
static Pump providePump(Thermosiphon pump) {
return pump;
}
}
By convention, it seems that the @ Provides
method is prefixed with provide and the module is suffixed with Module.
Classes and methods defined by @ Inject
and @ Provides
form a dependency graph of objects.
To create this graph, define a component.
A component is an interface annotated with @Component
and has methods with no arguments.
The return type of these methods will be the type you want to resolve.
The following defines a CoffeeShop component that has a method that returns a CoffeeMaker instance. Pass modules to the components to build their subordinate dependencies. Here we are passing the DripCoffeeModule.
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
Dagger will automatically generate an implementation of CoffeeShop based on this definition. Implementation classes generated by Dagger are prefixed with Dagger. Here, an implementation class called DaggerCoffeeShop is generated.
The component implementation class has a builder method, which is used to build dependencies.
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
In the above example, the DripCoffeeModule instance is manually created and passed to the builder, but if the following conditions are met, this process is unnecessary.
--If you have access to the module's default constructor
--If all @ Provides
methods in the module are static
For components, if all of the following modules apply to the above, the implementation class has a create method that allows you to instantiate directly without going through the builder.
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
Finally, the CoffeeMaker client code looks like this:
CoffeeMaker coffeeMaker = DaggerCoffeeShop.create().maker();
coffeeMaker.brew();
Here is the code with a slight modification of the Official Coffee App. It is implemented only with the functions in the range explained above.
** * Only the @Singleton
annotation will be described later. ** **
Heater.java(Remain official)
interface Heater {
void on();
void off();
boolean isHot();
}
Pump.java(Remain official)
interface Pump {
void pump();
}
ElectricHeater(Remain official)
class ElectricHeater implements Heater {
boolean heating;
@Override
public void on() {
System.out.println("~ ~ ~ heating ~ ~ ~");
this.heating = true;
}
@Override
public void off() {
this.heating = false;
}
@Override
public boolean isHot() {
return heating;
}
}
Thermosiphon(Remain official)
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
@Override
public void pump() {
if (heater.isHot()) {
System.out.println("=> => pumping => =>");
}
}
}
DripCoffeeModule.java
@Module
class DripCoffeeModule {
@Singleton
@Provides
static Heater provideHeater() {
return new ElectricHeater();
}
@Provides
static Pump providePump(Thermosiphon pump) {
return pump;
}
}
CoffeeMaker(Remain official)
class CoffeeMaker {
private final Heater heater;
private final Pump pump;
@Inject
CoffeeMaker(Heater heater, Pump pump) {
this.heater = heater;
this.pump = pump;
}
public void brew() {
heater.on();
pump.pump();
System.out.println(" [_]P coffee![_]P ");
heater.off();
}
}
CoffeeShop.java
@Singleton
@Component(modules = DripCoffeeModule.class)
public interface CoffeeShop {
CoffeeMaker maker();
}
CoffeeApp.java
public class CoffeeApp {
public static void main(String[] args) {
CoffeeMaker coffeeMaker = DaggerCoffeeShop.create().maker();
coffeeMaker.brew();
}
}
@Singleton
annotationWhen resolving dependencies, Dagger will try to instantiate a type every time it is requested by default. However, in the case of the Coffee app, the default behavior does not work as intended.
CoffeeMaker's brew method is to turn the heater on (heater.on ()), pour water (pump.pump ()), brew coffee (println ()), and turn the heater off again. Pour water only when the heater is on. Confirmation of whether the heater is turned on is delegated to the Heat instance held by the Pump instance, but at this time, the Heat instance held by the CoffeeMaker instance and the Heat instance held by the Pump instance are You must be referring to the same thing.
As mentioned earlier, Dagger is instantiated every time a type is requested, so it will not behave as intended.
You can then use the @ Singleton
annotation to specify that the same instance should always be returned when the type is requested.
In the above code, the provideHeater method of DripCoffeeModule is annotated with @Singleton
to specify that the Heater instance is a singleton.
The CoffeeShop interface is also annotated with @ Singleton
so that the component and its subordinate instances have the same life cycle.
I've explained the basics that are the first step in understanding Dagger. Since this is only the first part of the user guide, I think there is still a lot of knowledge that is not enough to practice Dagger, but those who want to use Dagger from now on, and my own a few days later. I thought it would be helpful for you.
Recommended Posts