Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

A Dagger2

1.1 introduction:

  1. Learning Dagger2 requires knowledge of annotations and reflection.

  2. Dagger2 originates from Dagger, an open source library that implements dependency injection completely at compile time based on Java annotations. It is mainly used to decouple modules and improve the robustness and maintainability of code.

  3. Dagger2 automatically generates Java code with Java annotations through APT at compile time, and then combines handwritten code to automatically help us with dependency injection.

  4. Dagger2 needs to compile the code once, otherwise an error will be reported. Code is compiled to generate intermediate code, which is then developed according to the normal flow.

  5. The Dagger2 principle makes use of reflection, and reflection processing is slow compared to normal development. Dagger2 moves reflection processing to the stage when the compiler compiles the code, and reflection is not involved at program runtime (supplement 1) : it generates some new code at compile time, which is then used as normal during program execution. So that’s how Dagger2, or other tripartite libraries like GreenDao and Butterknife, use reflection and still be effective.

  6. Dagger2 is developed based on annotations, and the annotations involved in Dagger2 are actually developed based on javax.Inject, which comes from JSR330. JSR330 is the specification and suggests what to do, while Dagger2 implements the specification, and for the average developer, learning Dagger2 is learning the meaning and use of annotations.

1.2 the origin of

Square initially developed Dagger inspired by Guice, but the Dagger semi-static semi-runtime framework has some performance issues (although dependency injection is completely static, its Directed Acyclic Graph is generated based on reflection, This is not optimal for either large server applications or Android applications). So Google engineers Fork the Dagger project and modify it. So that gives us Dagger2, which we’re going to talk about today, so Dagger2 is really just a high version of the Dagger.

First acquaintance

Dagger2 is based on Java annotations to implement dependency injection, so we need to understand annotations in Dagger2 before using them. Dagger2 annotations include @inject, @Module, @provides, @Component, @qulifier, @Scope, and @Singleten.

@ Inject 2.1:

Inject has two functions:

One is used to mark dependent variables to tell Dagger2 to provide it with dependencies.

Dagger2 uses the @Inject annotation to find the constructor when the class instance is needed and construct related instances to provide dependencies for the variables marked by @Inject.

See the case A below for further study.

2.2 @ the Module:

@Module is used to annotate classes that provide dependencies.

You might be a little confused. Why do we need @Module when we use @inject to tag constructors to provide dependencies?

For example, many times we need to provide dependent constructors that are third-party libraries and we can’t annotate them with @inject;

For example, a dependency class that needs to be injected provides a constructor that takes arguments. What about its arguments?

@Module is what helps us solve these problems.

See case B below for further study.

@ 2.3 Provides:

@provides is used to annotate methods in the classes annotated by Module. This method is called when it needs to provide dependencies, thus assigning values to the variables annotated by @Inject as if the provided object is a dependency.

See case B below for further study.

2.4 @ Component:

@Component is used to annotate the interface and acts as a bridge between the dependent demander and the dependent provider. The implementation class of the interface annotated by Component is generated at compile time (DaggerCarComponent is generated at compile time if the interface annotated by @Component is CarComponent). We use the method of this implementation class to complete the injection.

@ Qulifier 2.5:

@qulifier is used for custom annotations, that is, @Qulifier is used to mark up annotation classes just like the basic meta-annotations provided by Java. When we use @Module to annotate methods that provide dependencies, we can define method names arbitrarily (although it is not mandatory to define method names beginning with provide, just for readability).

So how does Dagger2 know for whom this method is providing dependencies? The answer is the type of the return value. Dagger2 uses the type of the return value to determine which variable is annotated by @Inject. The problem with Dagger2 is that it gets confusing when you have multiple returns of the same type. @Qulifier to solve this problem, we use @Qulifier to define our own annotations, and then use the custom annotations to annotate the methods providing dependencies and the dependent demanders (i.e. variables annotated by @Inject), so that Dagger2 knows who to provide dependencies for. For details, please refer to case C below.

A more concise definition: we can use this annotation when the type is not sufficient to identify a dependency;

2.6 @ the Scope:

@scope is also used for custom annotations. I can use @scope custom annotations to limit the Scope of annotations and achieve singletons (branch and global).

@Scope requires a Component and a dependency provider to work. For dependencies with @Scope annotations, Component holds the dependency that was created the first time and will reuse the instance when it is injected. Essentially the purpose of @Scope is to bind the life cycle of the generated dependent instance to the Component

If the Component is rebuilt, the @Scope dependencies are also rebuilt, so you need to maintain the Component lifecycle yourself to maintain local singletons.

@ Singleton 2.7:

@singleton is essentially an annotation defined by @scope that we use to implement global singletons. But it does not anticipate a global singleton, and it depends on whether the corresponding Component is a global object.

Three Dagger2 use

3.1 introduced Dagger2

IO /dagger/ Dagger2 /dagger/… Dependency injection frameworks for Java and Android development, not just Android.

dependencies {
  api 'com.google.dagger:dagger:2.x'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}
Copy the code

3.2 Case A: Getting started Demo

The Car class is a requirement dependent that relies on Engine. So we need to add @inject to the class variable Engine to tell Dagger2 to provide a dependency for ourselves. The Engine class is a dependency provider, so we need to add @Inject to its constructor

public class Engine {
    
    /** * Dagger2 passes@InjectThe annotation can find the constructor when the class instance is needed and construct the related instance, * to be used by@InjectFlagged variables provide dependencies on * */
         
    @Inject
    public Engine(a) {}@NonNull
    @Override
    public String toString(a) {
        return "Engine{}";
    }
 
    public void run(a) {
        Log.i("tag"."The engine is running...");
    }
 
Copy the code

Next we need to create an interface CarComponent annotated with @Component, which is essentially an injector to inject Engine into the Car.

@Component
public interface CarComponent {
    void inject(Car car);
}
Copy the code

Once this is done we need to Build the project and have Dagger2 generate the relevant Java classes for us. We can then inject the Car by calling the Dagger2-generated DaggerCarComponent in the Car constructor (this is already in the Car code).

public class Car {
    / * * *@Inject:@InjectIt is used to mark dependent variables to tell Dagger2 to provide it with dependencies */
    @Inject
    Engine engine;
 
    public Car(a) {
        DaggerCarComponent.builder().build().inject(this);
    }
 
    public Engine getEngine(a) {
        return this.engine;
    }
 
    public static void main(String ... args){
        Car car = newCar(); System.out.println(car.getEngine()); }}Copy the code

3.3 case B

3.3.1 Use of @Module and @provide

What if the constructor that creates Engine takes arguments? Or is the Eggine class something we can’t change? This is where @Module and @provide come in.

You can see that the code for the Engine class below is almost identical to the code for Engine in the introductory demo above, with the addition of an argument constructor.

public class Engine {
 
    private String name;
 
    @Inject
    Engine(){}
 
    Engine(String name) {
        this.name = name;
    }
 
    @Override
    public String toString(a) {
        return "Engine{" +
                "name='" + name + '\' ' +
                '} ';
    }
 
    public void run(a) {
        System.out.println("The engine is running..."); }}Copy the code

Next we need a Module class to generate dependent objects. The @Module mentioned earlier is used to standardize this class, while @provide is used to annotate methods that Provide dependent objects. (It is an unwritten rule that methods annotated by @provide are named with Provide, which is not mandatory but is useful for code readability.)

@Module
public class MarkCarModule {
 
    String engineType;
 
    public MarkCarModule(String engineType) {
        this.engineType = engineType;
    }
 
    @Provides
    Engine provideEngine(a) {
        return newEngine(engineType); }}Copy the code

Now we need to add modules = {markCarModule.class} to the @Component annotation that takes no arguments. MarkCarModule is used to tell Dagger2 that it is the MarkCarModule class that provides the dependency.

@Component(modules = MarkCarModule.class)
public interface CarComponent {
    void inject(Car car);
}
Copy the code

We also need to change the Car constructor to include a markCarModule(new markCarModule()) method. This tells the DaggerCarComponent injector to inject the dependencies provided by markCarModule into the Car class.

public class Car {
    
    @Inject
    Engine engine;
 
    public Car(a) {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule("Domestic engine"))
                .build().inject(this);
    }
 
    public Engine getEngine(a) {
        return this.engine;
    }
 
    public static void main(String ... args){
        Car car = newCar(); System.out.println(car.getEngine()); }}Copy the code

Now that a very basic dependency injection is done, let’s test our code. Output:

Engine{name=' Engine '}Copy the code

3.3.2 Dependency search order of Dagger2

We mentioned that @inject and @Module both provide dependencies. What if we use the constructor tag @Inject and @Module to provide dependencies? The specific rules are as follows:

  • Step 1: First look for methods that provide dependencies in the @Module annotation class.

  • Step 2: If there is a method that provides dependencies, check whether the method has parameters. If there are parameters, initialize them from Step 1. If it does not exist, the class instance is directly initialized to complete a dependency injection.

  • Step 3: If no method exists to provide dependencies, look for the constructor annotated by @Inject to see if the constructor has arguments. If there are parameters, initialize them from Step 1. If it does not exist, the class instance is directly initialized to complete a dependency injection.

Conclusion: The dependency search sequence of Dagger2 is to search for all @Provides dependencies in the Module first. If the dependency search cannot be found, then search for @Inject dependencies.

3.4 Case C: Use of @qualifiers and @name

In the same Module, @Provides Provides dependencies that are determined by the return value. And then the question is, how do you differentiate between different instances of the same type? For example, if a Car has two engines (i.e., two Engine variables in the Car class), you might write MarkCarModule in 3.3.1 case B:

@Module
public class MarkCarModule {
 
    public MarkCarModule(a){}@Provides
    Engine provideEngineA(a){
        return new Engine("Domestic engine");
    }
 
    @Provides
    Engine provideEngineB(a){
        return new Engine("German engine"); }}Copy the code

You should be able to code it like this, but it doesn’t compile at all. Because Dagger2 is dependent on the type returned. If there are two methods that return the same type, Dagger2 normally has no way of knowing which one to choose (such as provideEngineA() or ProvideB () in the code above).

Dagger2 provides a solution: @name annotation or @qulifier annotation:

3.4.1 track @ Qulifier annotation

We customize two annotations using the @Qulifier annotation to distinguish between two concrete classes that return the same type but are different objects, in this case a domestic engine and a German engine.

public class Engine {
    / * * *@QulifierDefine two annotations */
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface QualifierA { }
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface QualifierB { }
 
    private String name;
 
    Engine(String name) {
        this.name = name;
    }
 
    @Override
    public String toString(a) {
        return "Engine{" +
                "name='" + name + '\' ' +
                '} ';
    }
 
    public void run(a) {
        System.out.println("The engine is running..."); }}Copy the code

We also need to modify the dependency providers to label different provideEngine methods with different annotations defined so that Dagger2 knows exactly which object to look for when looking for objects of the same type.

@Module
public class MarkCarModule {
 
    public MarkCarModule(a){}@Engine.QualifierA
    @Provides
    Engine provideEngineA(a){
        return new Engine("Domestic engine");
    }
 
    @Engine.QualifierB
    @Provides
    Engine provideEngineB(a){
        return new Engine("German engine"); }}Copy the code

Next, the dependency Car class also needs to be modified by adding a custom annotation note in front of the injected object so that Dagger2 knows which provide method to select:

public class Car {
    
    @Engine.QualifierA
    @Inject
    Engine engineA;
 
    @Engine.QualifierB
    @Inject
    Engine engineB;
 
    public Car(a) {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }
 
    public Engine getEngineA(a) {
        return this.engineA;
    }
 
    public Engine getEngineB(a) {
        return this.engineB;
    }
 
    public static void main(String... args) {
        Car car = newCar(); System.out.println(car.getEngineA()); System.out.println(car.getEngineB()); }}Copy the code

3.4.2 @ Name annotation

Take a look at the source code for the Named tag:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
 
    /** The name. */
    String value(a) default "";
}
Copy the code

You can see that @name is actually an annotation of the @qualifier annotation. The principle is still @qualifier, but there is an extra layer that makes it easier to use.

@Module
public class MarkCarModule {
 
    public MarkCarModule(a){}@Provides
    @Named("QualifierA")
    Engine provideEngineA(a){
        return new Engine("Domestic engine");
    }
 
    @Provides
    @Named("QualifierB")
    Engine provideEngineB(a){
        return new Engine("German engine"); }}Copy the code
public class Car {
    
    @Named("QualifierA")
    @Inject
    Engine engineA;
 
    @Named("QualifierB")
    @Inject
    Engine engineB;
 
    public Car(a) {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }
 
    public Engine getEngineA(a) {
        return this.engineA;
    }
 
    public Engine getEngineB(a) {
        return this.engineB;
    }
 
    public static void main(String... args) {
        Car car = newCar(); System.out.println(car.getEngineA()); System.out.println(car.getEngineB()); }}Copy the code

A singleton of four advanced uses

4.1 Local Singleton implementations for @singleton

With Dagger2 we can get rid of the usual tedious singleton creation methods such as double-checked locks. For singleton learning, please refer to this article: Singleton pattern learning and Progression.

Dagger2 provides the @Singleton annotation to implement a Singleton, which requires two steps:

1: Mark @singleton in the Module Provides method to indicate that a Singleton object is provided.

2: Identify @Singleton in the Component class to indicate that a Module in the Component uses @Singleton.

Let’s write a simple local singleton example:

4.1.1 Creating a UserBean object

public class UserBean {
    private String name;

    public UserBean(String name) {
        this.name = name; }}Copy the code

4.1.2 to create the Module

Use @singleton to provide a Singleton object:

@Module
public class UserModule {
    @Singleton
    @Provides
    UserBean providesUserA(a) {
        return new UserBean("Lucas"); }}Copy the code

4.1.3 create Component

Note: If the Comonent on which moudule depends has an object that is a @Singleton, then Conponnent must also be a @Singleton, indicating that a Module in the Component uses @Singleton

@Singleton
@Component(modules = UserModule.class)
public interface UserComponent {
    void join(UserActivity userActivity);
}
Copy the code

So you might be wondering, why is @Singleton annotated with @Provides and @Component?

Component is the link between requirements and dependencies. For dependencies with @Scope (/ @singleton) annotations, Component holds the dependency created the first time and will reuse the instance for injection. If Component rebuilds, Dependencies held by @Scope (/@Singleton) will also be rebuilt, so @scope (/@Singleton) is needed to annotate the Component. The @provides annotation is intended to provide a unique userBean object. You can even think of it as a singleton of singletons, which is not exact, but helps to understand: I want the singleton’s userBean, so Component has to be a singleton and the same concrete implementation class as the UserComponent that the activity gets. Also for the provide method, the userModule is a dependency in the UserComponent, and the userModule in the same UserComponent must be a singleton.

4.1.4 Test in UserActivity

public class UserActivity extends AppCompatActivity {
    private TextView textView;
    @Inject
    UserBean userBeanA;
    @Inject
    UserBean userBeanB;
    @Inject
    UserBean userBeanC;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_monkey);
        textView = findViewById(R.id.textView);
        DaggerUserComponent.create().join(this);
    }
     
    public void checkSingleton(View view) {
        textView.setText("userBeanA:" + userBeanA.hashCode() + "\n"
                + "userBeanB:" + userBeanB.hashCode() + "\n"
                + "userBeanC:" + userBeanC.hashCode() + "\n"); }}Copy the code

The hashCode values of userBeanA, userBeanB, and userBeanC are all the same, proving that the singleton we just wrote in this activity succeeded!

It is worth noting that the simple object here is valid only within the same Activity.

Explain: Such as the example above, the key is UserActivity DaggerUserComponent. In the create (). Join the (this), it creates a bridge UserComponent, so in this activity, All userBean objects are injected through the UserComponent, and the UserComponent already knows that the UserModule is a singleton decorated with the @Singeton annotation. If new another UserActivity2 again, and again in the new activity2 DaggerUserComponent. The create (). The join (this), then can produce new bridge: UserComponent2 (UserComponent is an interface that passes in different activity instances to different activities, thus generating different UserComponent implementations), The userBean object is no longer the userBean in the UserActivity!

Different activities hold different objects. Using the @singeton annotation or a custom @scope can only hold a singleton for one lifetime of the same Activity (or fragment).

4.2 Custom @scope annotation implementation of a local singleton

Let’s look at the implementation of the @singeton annotation:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
Copy the code

As you can see, @Singleton is just a default implementation of @Scope, but because it’s more readable, it lets developers know at a glance that it’s used for Singleton purposes. So we now implement a singleton using @scope. First we need to define a CarScope annotation via @Scope:

public class Engine {
    / * * * 1.@ScopeDefine a CarScope annotation */
          @Scope
          @Retention(RetentionPolicy.RUNTIME)
          public @interface CarScope {
          }

    private String name;
     
    Engine(String name) {
        this.name = name;
        System.out.println("Engine create: " + name);
    }
     
    @Override
    public String toString(a) {
        return "Engine{" +
                "name='" + name + '\' ' +
                '} ';
    }
     
    public void run(a) {
        System.out.println("The engine is running..."); }}Copy the code

We then need to mark the dependent provider MarkCarModule with the @CarScope to indicate that the object provided by the provide method is a singleton.

@Module
public class MarkCarModule {

    public MarkCarModule(a){}/ * * * 2.@CarScopeUntag the dependent provider MarkCarModule *@return* /
    @Engine.CarScope
    @Provides
    Engine provideEngine(a){
        return new Engine("Domestic engine"); }}Copy the code

Compoent also needs to be tagged with @scope to indicate that a Module in the Component uses the custom @Scope:

/** * 3@ScopeUntag the injector Compoent */
@Engine.CarScope
@Component(modules = MarkCarModule.class)
public interface CarComponent {
    void inject(Car car);
}
Copy the code
public class Car {
 
    @Inject
    Engine engineA;
 
    @Inject
    Engine engineB;
 
    public Car(a) {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }
 
    public Engine getEngineA(a) {
        return this.engineA;
    }
 
    public Engine getEngineB(a) {
        return this.engineB;
    }
 
    public static void main(String... args) {
        Car car = newCar(); System.out.println(car.getEngineA()); System.out.println(car.getEngineB()); }}Copy the code

If we didn’t use @Scope, the above code would instantiate the Engine class twice, so there would be two “Create Engine” outputs. Now we are testing the product of labor with @Scope: output

Engine{name=' Engine '} Engine{name=' Engine '}Copy the code

4.3 Global Singleton

In the local singleton example above, the DaggerUserComponent is instantiated once in each activity, thus creating two different objects. We need to enable the Component to implement the singleton. In Android, we know that there is only one Appclication instance for the entire App lifecycle, so we get a unique component instance in the Application and use it to provide the singleton we need:

4.3.1 Creating the UserBean class

public class UserBean {}Copy the code

4.3.2 create BaseModule

It’s the same thing as a local singleton.

@Module
public class BaseModule {
    @Singleton
    @Provides
    UserBean providesUser(a) {
        return newUserBean(); }}Copy the code

4.3.3 create BaseComponent

This Component is intended for other Components to rely on. It simply tells the other Component what types of dependencies it can provide.

@Singleton
@Component(modules = BaseModule.class)
public interface BaseComponent {
    UserBean getSingletonUser(a);
}
Copy the code

4.3.4 Provide a unique baseComponent class in Application

public class MyApplication extends Application {

    private BaseComponent baseComponent;
     
    @Override
    public void onCreate(a) {
        super.onCreate();
        baseComponent = DaggerBaseComponent.create();
    }
     
    public BaseComponent getBaseComponent(a) {
        returnbaseComponent; }}Copy the code

4.3.5 Customizing a Scope

The UserComponet to be created in 4.3.7 needs to rely on BaseComponent, which is marked by @Singleton, so UserComponet also needs to be marked by @Singleton. However, dependencies already annotated by @singleton cannot be used by dependent parties (UserComponet below). Only one dependency can be defined.

/** * Scope@Retention(RUNTIME) RUNTIME level */
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface BaseScope {
}
Copy the code

4.3.6 create UserModule

This is a custom business.

@Module
public class UserModule {}Copy the code

4.3.7 create UserComponet

In our own Component, we rely on baseComponent with dependencies. (In the @Component annotation parameter, we can rely on multiple Modules and Components, depending on our business needs.)

A Component can depend on one or more Components and get instances exposed by the dependent Components.

Tips, if there are multiple modules, can use big parentheses: @ Component modules (= {AModule. Class, BModule class})

@BaseScope
@Component(modules = UserModule.class, dependencies = BaseComponent.class)
public interface UserComponet {
    void join(UserActivity userActivity);
    void join(User2Activity user2Activity);
}
Copy the code

4.3.8 Testing in activity

public class UserActivity extends AppCompatActivity {
    private TextView textView;

    @Inject
    UserBean userBeanA;
    @Inject
    UserBean userBeanB;
    @Inject
    UserBean userBeanC;
     
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_student);
        ((TextView) findViewById(R.id.text)).setText("First interface");
        textView = findViewById(R.id.textView);
        DaggerUserComponet.builder().baseComponent(((MyApplication) getApplication()).getBaseComponent()).build().join(this);
    }
     
    public voidClick global singleton (View View) {textView.settext ("userA:" + userBeanA.hashCode() + "\n"
                + "userB:" + userBeanB.hashCode() + "\n"
                + "userC:" + userBeanC.hashCode() + "\n");
    }
     
    public voidNext global singleton (View View) {startActivity(new Intent(this, User2Activity.class)); }}Copy the code

It may seem a bit convoluted at first, but it can actually be interpreted as using two local singletons: No matter what activity is injected with DaggerUserComponet, because UserComponet relies on baseComponent, which is created by the Application, there is only one. As for baseComponent, its UserBean is annotated @Singleton and unique. In this way, the whole app life cycle of the global singleton is realized.

Add 1 Dagger2 injection is not implemented by reflection, but by generating code to achieve the injection effect. Many Android open source frameworks use code generation tools (APT), such as ButterKnife.

Reference article:

Dagger2 source analysis (3) Analysis of annotations in Dagger2 from the source point of view

Easy to learn. I hear you haven’t figured out Dagger2 yet

Docs.oracle.com/javaee/7/ap…

Jcp.org/en/jsr/deta…

dagger.dev/dev-guide/

Dagger 2 basic use, local singleton, global singleton

@scope Just read this one – Dagger2 (ii)

Dagger2 from start to drop -Component inheritance, local singleton