Dagger is a fully static, compile-time dependency injection framework for both Java and Android. It is an adaptation of an earlier versioncreated by Square and now maintained by Google. Dagger aims to address many of the development and performance issues that have plagued reflection-based solutions.

Dagger is a completely static compile-time dependency injection framework for Java and Android, previously maintained by Square and now maintained by Google. Dagger addresses development and performance issues introduced by reflection.

Dagger is based on APT.

APT(Annotation Processing Tool) An Annotation Processing Tool that detects annotations in source files and performs additional Processing on annotations.

Dagger2 Github

Dependent on the provider, dependent on the injection container, dependent on the demand side

For example, in TestActivity we need to get a reference to the Teacher object, typically a new object.

In Dagger2, TestActivity is the dependency demander, Component is the dependency injection container, and @Inject or @Module & @provides is the dependency provider.

Ioc: Ioc inversion of Control is a design idea, such as ClassA, in which ClassB is referenced. Ioc is about handing your designed object classB to the container for control, rather than the traditional direct control of classB within your object classA.

If it is actively created in classA via new ClassB(), it gets a reference to the dependent object ClassB, which is positive, and highly coupled; The Ioc container creates, finds, and injects the dependency classB, which passively accepts the dependency classB, inverts, and looses the coupling;

Depedency Injection (DI) : DI means that a container dynamically injects a dependency into a component for reuse. ClassA relies on Ioc to provide the external resources needed by the object classB.

Java reflection mechanism: in the running state, for any class, can know all the properties and methods of that class; For any object, you can call any of its methods and properties; This ability to dynamically retrieve information and invoke methods on objects is called the Reflection mechanism of the Java language.

Next, basic usage. Add Dagger2 under Dependencies of Module

implementation Com. Google. "dagger: a dagger: 2.15"
annotationProcessor Com. Google. "dagger: a dagger - compiler: 2.15"
Copy the code

1, @inject decorates constructors

  • The constructor is decorated with @inject and depends on the provider
public class Teacher {
    @Inject
    public Teacher() {}}Copy the code
  • Component connects the provider-dependent and demand-dependent sides
@Component()
public interface TestComponent {
    void inject(TestActivity testActivity); // This name can be changed to XXX}Copy the code
  • Dependent demander, get an instance of Teacher through @inject, and DaggerTestComponent is needed to connect dependent provider and dependent demander.
  • DaggerTestComponent Generates a Dagger + newly declared component after compiling the component.
public class TestActivity extends AppCompatActivity {
    @Inject
    Teacher teacher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dagger_test); //DaggerTestComponent.create().inject(this); Create () is equivalent to builder (). The build () DaggerTestComponent. Builder (). The build () inject (this); // Same method name injected in Component XXX log.e ("zhen", teacher.toString()); // Teacher@63e4d73
    }
}
Copy the code

We created a Teacher class, a TestComponent interface, and an activity to implement the function

In the generated/source/apt/debug/package name, the system will generate four files, then the analysis on these files. Student_Factory is what I use for testing, just copy a Teacher, change the class name to Student, don’t affect the function.

DaggerTestComponent.java

DaggerTestComponent inherits from TestComponent,

DaggerTestComponent. Builder (). The build (), is through a DaggerTestComponent instance initializer mode.

Inject (TestActivity) is the dependency that the demander wants to inject.

public final class DaggerTestComponent implements TestComponent { private DaggerTestComponent(Builder builder) {} public  static Builderbuilder() {
    return new Builder();
  }

  public static TestComponent create() {
    return new Builder().build();
  }

  @Override
  public void inject(TestActivity testInjectTestActivity) {// The same injectTestActivity as the name of the component injectTestActivity(testActivity); Private TestActivity injectTestActivity(TestActivity instance) {DaggerTestComponent inherits from TestComponent} private TestActivity injectTestActivity(TestActivity instance) { TestActivity_MembersInjector.injectTeacher(instance, new Teacher()); // If we declare a student and annotate the constructor with @inject, Then TestActivity by introducing the student / / @ Inject TestActivity_MembersInjector. InjectStudent (instance, new student ()); // It will be like this.returninstance; } public static final Class Builder {// Static inner class gets an instance of the outer class privateBuilder() {}

    public TestComponent build() {
      returnnew DaggerTestComponent(this); }}}Copy the code

TestActivity_MembersInjector.java

Here is @Inject Teacher and Student. Imagine there is no Student.

  • injectTeacher(TestActivity instance, Teacher teacher)

    Instance = new teacher ();Copy the code
public final class TestActivity_MembersInjector implements MembersInjector<TestActivity> {
  private final Provider<Teacher> teacherProvider;
  //private final Provider<Student> studentProvider; 假如声明了student的话

  public TestActivity_MembersInjector(Provider<Teacher> teacherProvider) {
    this.teacherProvider = teacherProvider;
  }

  public static MembersInjector<TestActivity> create(Provider<Teacher> teacherProvider) {
    returnnew TestActivity_MembersInjector(teacherProvider); } @Override public void injectMembers(TestActivity instance) { injectTeacher(instance, teacherProvider.get()); } public static void injectTeacher(TestActivity instance, Teacher teacher) { instance.teacher = teacher; }}Copy the code

Teacherfactory.java.

2. Module provides dependencies

  • Teacher’s constructor removes @Inject, the ordinary bean class
public class Teacher {
    public Teacher() {}}Copy the code
  • The Module class is annotated by @Module
  • The @provides tag Provides methods for dependencies. It’s usually provideXXX, so I’m going to write provideTeacherHAHA, experiment.
@Module
public class TestModule {

    @Provides
    Teacher provideTeacherHAHA() {
        returnnew Teacher(); }}Copy the code
  • The Component interface is annotated with @Component
  • Module = testModule. class Inject module method name
@Component(modules = TestModule.class)
public interface TestComponent {

    void inject(TestActivity testActivity);
}
Copy the code
  • Looks like it’s the @Inject constructor, right
public class TestActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dagger_test); DaggerTestComponent. Builder () / /. TestModule (new testModule ()) / / this can take out, because the build (), if testModule is empty, will take the initiative to the new one, Build () // Similarly, builder().build() can use create() instead of.inject(this); Log.e("zhen", teacher.toString()); }}Copy the code

However, when we modify these and compile again, there’s a lot going on in DaggerTestComponent

TestActivity_MembersInjector did not change, but Teacher_Factory gone, replaced by TestModule_ProvideTeacherHAHAFactory, DaggerTestComponent has little change.

DaggerTestComponent.java

The biggest change is the addition of the TestModule member variable, so the constructor has changed; There are also changes in the constructor.

Inject () the same to the user to hide the implementation details, just by the above new the Teacher () replaced by TestModule_ProvideTeacherHAHAFactory. ProxyProvideTeacherHAHA (testModule).

public final class DaggerTestComponent implements TestComponent {
  private TestModule testModule; TestModule private DaggerTestComponent(Builder Builder) {initialize(Builder); //2.1. The constructor has changed and needs to be giventestModule assignment} @suppressWarnings ("unchecked") private void initialize(final Builder builder) { this.testModule = builder.testModule; //2.2. The constructor has changed and needs to be giventest} public static Builderbuilder() {
    return new Builder(); 
  }

  public static TestComponent create() {
    returnnew Builder().build(); } @override public void Inject (TestActivitytestActivity) {
    injectTestActivity(testActivity); } private TestActivity injectTestActivity(TestActivity instance) { TestActivity_MembersInjector.injectTeacher( instance,  TestModule_ProvideTeacherHAHAFactory.proxyProvideTeacherHAHA(testModule));
    return instance;
  }

  public static final class Builder {
    private TestModule testModule; // There is also a TestModule private in the constructorBuilder() {}

    public TestComponent build() {
      if (testModule == null) {// iftestIf Module is empty, new this. TestModule = new testModule (); }return new DaggerTestComponent(this);
    }

    public Builder testModule(TestModule testModule) {/ / 3.1, specify TestModule enclosing TestModule = Preconditions. CheckNotNull (testModule); 
      returnthis; }}}Copy the code

TestModule_ProvideTeacherHAHAFactory.java

The file is named A_BFacory, A is the name of the module class defined, and B is the name of the method with the @Provides flag inside.

ProxyProvideTeacherHAHA (testModule) = > testModule. ProvideTeacherHAHA (), if there is a truth, this is the Factory provide the provides method of adjusting the module back and forth, Finally, you get an instance of the dependency.

public final class TestModule_ProvideTeacherHAHAFactory implements Factory<Teacher> {
  private final TestModule module;

  public TestModule_ProvideTeacherHAHAFactory(TestModule module) {
    this.module = module;
  }

  @Override
  public Teacher get() {
    return Preconditions.checkNotNull(
        module.provideTeacherHAHA(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static TestModule_ProvideTeacherHAHAFactory create(TestModule module) {
    return new TestModule_ProvideTeacherHAHAFactory(module);
  }

  public static Teacher proxyProvideTeacherHAHA(TestModule instance) {
    return Preconditions.checkNotNull(
        instance.provideTeacherHAHA(), "Cannot return null from a non-@Nullable @Provides method"); }}Copy the code

The @inject constructor and Module provide dependencies in such a way that dependency injection can be easily implemented. It doesn’t make any difference to us, okay? It’s not, and there are major limitations to implementing injection through constructors.

  • Only one constructor can be marked. If we need to mark more than one constructor, an error will be reported at compile time.
  • 2. We cannot mark other classes that we cannot modify ourselves, such as third-party libraries, because we cannot mark their constructors with @inject.

For limitation 1, let’s do this.

3, @named inject multiple

  • First we provide Teacher with two constructors, one with no arguments and one with arguments
public class Teacher {
    String name;

    public TeacherPublic Teacher(String name) {this.name = name; }}Copy the code
  • Similarly, provide two different instances,
  • You can’t provide two methods in a module that return the same object type.
  • Dependency objects with parameters that are also provided by @inject or @module & @provides. This is simplified
@Module
public class TestModule {

    @Provides
    Teacher provideTeacherHAHA() {
        return new Teacher();
    }

    @Provides
    @Named("xxx"(// Pay attention to TeacherprovideTeacherWithName() {
        return new Teacher("King Nima."); }}Copy the code
  • Testactivity.java requires a teacher with a parameter, also marked by @named (),
    @Inject
    @Named("xxx")
    Teacher teacher1;
Copy the code

It’s that simple, and again we’re going to explore what’s going on inside.

@Named

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}
Copy the code

DaggerTestComponent.java

  • Here we still use HAHAFactory to get a Teacher with no arguments
  • We use the WithNameFactory notation to get a Teacher with a reference, which means that any method that passes the @Provides notation will have an A_BFactory
  • TestActivity_MembersInjector contains injectTeacher() and injectTeacher1(). TestActivity_MembersInjector adds the corresponding method.
private TestActivity injectTestActivity(TestActivity instance) { TestActivity_MembersInjector.injectTeacher( / / here is to get a no arguments the Teacher by HAHAFactory instance, TestModule_ProvideTeacherHAHAFactory. ProxyProvideTeacherHAHA (testModule)); Tong WithNameFactory TestActivity_MembersInjector. InjectTeacher1 (/ / here to get a reference of the Teacher the instance, TestModule_ProvideTeacherWithNameFactory.proxyProvideTeacherWithName(testModule));
    return instance;
  }
Copy the code

TestModule_ProvideTeacherWithNameFactory.java

And TestModule_ProvideTeacherHAHAFactory is almost a routine ah,,,,

public final class TestModule_ProvideTeacherWithNameFactory implements Factory<Teacher> {
  private final TestModule module;

  public TestModule_ProvideTeacherWithNameFactory(TestModule module) {
    this.module = module;
  }

  @Override
  public Teacher get() {
    return Preconditions.checkNotNull(
        module.provideTeacherWithName(),
        "Cannot return null from a non-@Nullable @Provides method");
  }

  public static TestModule_ProvideTeacherWithNameFactory create(TestModule module) {
    return new TestModule_ProvideTeacherWithNameFactory(module);
  }

  public static Teacher proxyProvideTeacherWithName(TestModule instance) {
    return Preconditions.checkNotNull(
        instance.provideTeacherWithName(),
        "Cannot return null from a non-@Nullable @Provides method"); }}Copy the code

TestActivity_MembersInjector.java

Equivalent to a copy of teacher, logical consistency.

public final class TestActivity_MembersInjector implements MembersInjector<TestActivity> { private final Provider<Teacher> teacherProvider; private final Provider<Teacher> teacher1Provider; @Override public void injectMembers(TestActivity instance) { injectTeacher(instance, teacherProvider.get()); injectTeacher1(instance, teacher1Provider.get()); } public static void injectTeacher(TestActivity instance, Teacher teacher) { instance.teacher = teacher; } public static void injectTeacher1(TestActivity instance, Teacher teacher1) { instance.teacher1 = teacher1; }}Copy the code

4, @ the Qualifier

@named; @named; @named; @named; @named; @named;

Customize a name as a tag. Then use this tag instead of @named ()

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface TeacherWithName {
}
Copy the code

Replace @named in module

    @Provides
    @TeacherWithName
    Teacher provideTeacherWithName() {
        return new Teacher("King Nima.");
    }
Copy the code

Replace @named in TestActivity

    @Inject
    @TeacherWithName
    Teacher teacher1;
Copy the code

5, @ Singleton

@Singleton can’t really create a Singleton, but we can still guarantee only one object per class for the lifetime of the App. The more important role of @Singleton is to better manage instances by reminding ourselves of tags.

  • 1. On provideXX in Module, mark the object with @singleton
@module public class TestModule {@singleton // Provides Teacher through @singletonprovideTeacherHAHA() {
        returnnew Teacher(); }}Copy the code
  • 2. Also pass the @singleton flag in the Component interface
@Singleton
@Component(modules = TestModule.class)
public interface TestComponent {

    void inject(TestActivity testActivity);

    void inject(Test2Activity test2Activity);
}
Copy the code
  • Inject dependent objects into TestActivity and Test2Activity respectively
TestComponent component = DaggerTestComponent.builder()
               .testModule(new TestModule())
               .build();
component.inject(this);
Log.e("zhen"."teacher: " + teacher.toString() + " component: " + component.toString());
Copy the code

TestActivity and teacher in Test2Activity are not the same object, and Component is not the same object. Just because Component is not an object, Teacher is not an object either. Let’s try providing a global Component in the BaseApplication.

Static provides a unique instance of component in the BaseApplication, and then we use that component to inject dependencies in TestActivity and Test2Activity. Component is the same object.

public class BaseApplication extends Application {
    static TestComponent component;

    @Override
    public void onCreate() {
        super.onCreate();
        component = DaggerTestComponent.builder()
                .testModule(new TestModule())
                .build();
    }

    public static TestComponent getComponent() {
        returncomponent; }}Copy the code

6, @ the Scope

We can see that @Singleton is actually a default implementation of @Scope, except for @Singleton, which focuses on the same component. In addition, any of our custom @scopes can replace @Singleton to achieve the same effect. But @singleton is more of a tag. People don’t do that.

All the @Scope annotation changes is a tag, and the Component does a “DoubleCheck” singleton on the tagged object factory class!

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

@singleton is a Singleton based on global, Application. In general, we use custom @scope to implement local singletons.

For example, I need to instantiate two Teacher objects in TestActivity, and I need to instantiate two Teacher objects in Test2Activity, but I want both teachers in TestActivity to be called Xiao Ming. But both teachers in Test2Activity are called Xiao Hong.

1. Let’s define an ActivityScope

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
Copy the code

2. Tag @ActivityScope with @provides and @Component in the Module

Inject dependent objects into TestActivity and Test2Activity respectively

TestComponent component = DaggerTestComponent.builder()
               .testModule(new TestModule())
               .build();
component.inject(this);
Log.e("zhen"."teacher: " + teacher.toString() + " component: " + component.toString());
Log.e("zhen"."teacher1: " + teacher1.toString() + " component: " + component.toString());
Copy the code

We can see that the same object A is injected into TestActivity and the same object B is injected into Test2Activity, but A is different from B, that is to say, there are no instances shared between different Activiyty, but one instance is shared within the same Activity. The point of this effect is that different Components annotate the same custom @scope.

In addition, I did various experiments to explore the relationship between them.

  • 1. Objects in TestActivity and Test2Activity are different, regardless of whether the same Component is used, as long as no Scope is marked

  • Annotated with Scope and using the same Component, objects in TestActivity and Test2Activity represent the same object

  • Use different components in TestActivity and Test2Activity. Inject the same object A in TestActivity and the same object B in Test2Activity, but A is different from B

  • 4, have dependencies, do not need to consider dependencies, the conclusion is the same as above.

    Component and its Module use the same Scope. The same Scope needs to be used in the same Module. Dependent or contains the relationship between Component cannot use the same Scope, the same complains Component is instantiated is DaggerxxComponent builder (). The build ()Copy the code

To sum up, specifying Scope yields the number of dependent Teacher objects based on the number of Component objects

7, dependencies

Suppose we need a Person object, provided by the ActivityComponent; The Context argument in person is provided by the ActivityComponent. That is, the ActivityComponent depends on the AppComponent.

ActivityModule depends on the provider

Dependency objects with parameters are provided by @inject or @module & @provides, as provided in AppComponent.

@Module
public class ActivityModule {

    @Provides
    public Person providePerson(Context context){
        returnnew Person(context); }}Copy the code

ActivityComponent dependency injection container

@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {

    void inject(TestActivity testActivity);
}
Copy the code

Context in AppModule is passed through AppModule instantiation

@Module
public class AppModule {
    private Context context;

    public AppModule(Context context) {
        this.context = context;
    }

    @Provides
    public Context providesContext() {
        returncontext; }}Copy the code

AppComponent

There is no Inject method in the AppComponent interface, because the Component that depends on it decides which class to inject.

@Component(modules = AppModule.class)
public interface AppComponent {
    Context getContextHAHA();
}
Copy the code

In our Activity

AppComponent can be added to BaseApplication, where this refers to appliAction and this refers to TestActivity’s context. This is just for demonstration purposes.

AppComponent AppComponent = DaggerAppComponent. Builder (). AppModule (new appModule (this)) / / must, otherwise an error. The build (); ActivityComponent ActivityComponent = DaggerActivityComponent. Builder (). AppComponent (appComponent) / / must be, And it USES appComponent. GetContextHAHA () gets the context object. ActivityModule (new activityModule ()). The build (); activityComponent.inject(this);Copy the code

You can look at the details. AppComponent will report an error if it is empty. AppModule will also report an error if it is empty.

Lazy loading and forced reloading of the Provider

Use Lazy and Provider, respectively, to decorate the object to be injected:

LazyPerson gets the same object multiple times, providerPerson gets it multiple times, and each get attempts to create a new object.

     @Inject
    Lazy<Person> person0;

    @Inject
    Provider<Person> person1;
    
    Log.e("zhen", person0.get().toString()); // Person@9c4dfc4
    Log.e("zhen", person0.get().toString()); // Person@9c4dfc4
    Log.e("zhen", person1.get().toString()); // Person@a992ead Log.e("zhen", person1.get().toString()); // Person@63fc5e2
Copy the code

How does the Dagger find the required dependency instance and inject it?

(The premise is that the interface of the @Conponent tag contains the Module class of the @Module tag. If there is no constructor corresponding to @Inject, an error will be reported.)

  • Step 1: Find if the Module has a method to create the class

  • Step 2: If there is a method to create a class, check whether the method has parameters

    Step 2.1: If there are parameters, initialize each parameter from Step 1. Step 2.2: If there are no parameters, directly initialize the class instance and the dependency injection endsCopy the code
  • Step 3: If there is no create class method, look for the @Inject annotated constructor to see if the constructor has arguments

    Step 3.1: If there are parameters, initialize each parameter from Step 1. Step 3.2: If there are no parameters, directly initialize the class instance and the dependency injection endsCopy the code

10 and summarize

  • @Inject

Inject has two main functions. One is used in constructors. Dagger2 uses Inject to provide dependencies by marking the constructor so that Dagger2 can find the constructor and new the related instance when needed. Another function is to flag dependent variables and make Dagger2 provide them with dependencies.

  • @Provide

Annotate a method with Provide. This method can be called when a dependency needs to be provided, thus assigning a value to the @injection annotated variable as if the provided object were a dependency. Provide is primarily used to annotate methods in the Module

  • @Module

Classes annotated with Module are specifically designed to provide dependencies. Inject a dependency. What if the class constructor we need to provide cannot be modified, such as some jar classes, we cannot modify the source code? This is where the Module comes in. Module can provide dependencies for classes that cannot modify the source code, and of course, Inject dependencies through Module

  • @Component

Component is usually used to annotate interfaces. The annotated interface generates instances of the corresponding classes at compile time to bridge the dependency providers and dependencies that need to be injected into them.