[关闭]
@Awille 2019-01-12T13:10:36.000000Z 字数 7515 阅读 160

依赖注入

设计模式 Java 依赖注入


1、依赖注入的原理

1.1、控制反转(IOC)(Inversion of Control)

IOC提出借助于“第三方” 实现具有依赖关系的对象的解耦。这个第三方可以理解为就是一个IOC容器,关于这个IOC容器的作用,可以举个例子来说明。假如对象A在运行过程之中要用到对象B的方法,那么如果没有解耦,那么对象A就会自己创建一个对象B然后调用对象B的方法,而IOC容器就在A创建B这个环起了作用。A需要使用B的时候,B的创建不由A自己创建,而是交给IOC容器来创建,然后将创建的对象注入到A当中。这样A获取B对象由主动形式转为被动形式,即控制被反转。

1.2、依赖注入 (Dependence Rejection)

依赖注入跟控制反转是一个东西,讲的都是获取依赖对象的过程由自身管理变为由IOC容器主动注入。

Tips

依赖注入的好处就是实现了两个有依赖的对象之间的解耦。比如A依赖B,在依赖关系不由IOC容器来管理的情况下,假如B的实现方式由implementOne变为implementTwo, 那么在A中,所有创建B对象的代码(如 B = implementOne())都要做出改动(如 B = implementTwo()),当然啦,当项目代码量很小的时候,可能这样的改动只有几个地方,改这几个地方就够了,但是项目代码量上去了,可能有几十个甚至上百个地方需要修改,很有可能就会发生纰漏,某些地方没有做相应的修改导致程序运行崩溃。

2、依赖注入的方式

假设情景A依赖B, B可能在不同的时期换不同的实现方式。
针对B的多变性,我们在设计的时候设置一个基类,让其他所有的实现都继承这个基类做出实现。或者将B设计成一个接口,其他的实现类都去实现这个接口。反正最终目的就是让这些不同的实现都可以用用一个统一的对象来调用它们各自的方法

  1. public interface B {
  2. void test();
  3. }

2.1 构造方法注入

  1. public class A {
  2. private B b;
  3. A(B b) {
  4. this.b = b;
  5. }
  6. }

2.2 set方法注入

  1. public class A {
  2. private B b;
  3. public void setB(B b) {
  4. this.b = b;
  5. }
  6. }

2.3 接口注入

  1. public interface IA {
  2. void setB(B b);
  3. }

以上是接口定义需要注入的信息

  1. public class A implements IA{
  2. private B b;
  3. public void setB(B b) {
  4. this.b = b;
  5. }
  6. }

3、 Dagger2依赖注入框架

Dagger2是一个标准的依赖注入框架,在编译期间自动生成代码,负责依赖对象的创建。我们可以暂时将其理解为一个IOC容器的实现。

3.1 dagger的使用

3.1.1 添加依赖

  1. implementation "com.google.dagger:dagger:2.14.1"
  2. annotationProcessor "com.google.dagger:dagger-compiler:2.14.1"

3.1.2 被依赖对象的创建

假设当下我们的mainActivity中要依赖一个car对象

  1. public interface Car {
  2. String createCar();
  3. }
  4. public class BenzCar implements Car {
  5. @Override
  6. public String createCar() {
  7. return "Benz";
  8. }
  9. }
  10. public class BmwCar implements Car {
  11. @Override
  12. public String createCar() {
  13. return "BMW";
  14. }
  15. }

我们在定义了一个Car接口,并且完成了它的两种实现。
在不使用依赖注入这种设计模式的情况之下,我们加入要使用Car实例中的方法一般是在需要的使用的时候

  1. Car car = new BenzCar();
  2. System.out.println(car.createCar());

在使用依赖注入框架的情况下,这种依赖关系的注入可以由dagger来完成,dagger就是我们之前提到的IOC容器的一种实现。

3.1.2 创建module

  1. @Module
  2. public class MainModule {
  3. @Provides
  4. Car provideCar() {
  5. return new BmwCar();
  6. }
  7. }

3.1.3 创建Component接口

  1. @Component(modules = {MainModule.class})
  2. public interface MainComponent {
  3. void inject(MainActivity mainActivity);
  4. }

3.1.4 先编译一下project,之后再MainActivity中依赖注入

  1. public class MainActivity extends AppCompatActivity {
  2. private static final String TAG = "MainActivity";
  3. private TextView carName;
  4. @Inject
  5. Car myCar;
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_main);
  10. //依赖注入
  11. MainComponent component = DaggerMainComponent.builder().mainModule(new MainModule()).build();
  12. component.inject(this);
  13. carName = (TextView)findViewById(R.id.car_name);
  14. carName.setText(myCar.createCar());
  15. Log.e(TAG, "the create car's name : " + myCar.createCar());
  16. }
  17. }

3.1.5 结果输出

01-12 17:50:22.894 2212-2212/? E/MainActivity: the create car's name : BMW

3.2 原理分析

以上可以看到,依赖注入的语句是mainActivity中的以下语句:
//依赖注入

  1. MainComponent component = DaggerMainComponent.builder().mainModule(new MainModule()).build();
  2. component.inject(this);

还有就是依赖注入对象的声明:

  1. @Inject
  2. Car myCar;

在我们编写完component跟module以后,编译project是可以看到生成了三个文件的
DaggerMainComponent.java、MainActivity_MembersInjector.java、MainModule_ProvideCarFactory.java

3.2.1 DaggerMainComponent.builder().mainModule(new MainModule()).build()

首先看到DaggerMainComponent的builder方法
注意到DaggerMainComponent是实现了我们之前定义的MainComponent接口的。

  1. public static Builder builder() {
  2. return new Builder();
  3. }

这个Builder是DaggerMainComponent的内部类:

  1. public static final class Builder {
  2. private MainModule mainModule;
  3. private Builder() {}
  4. public MainComponent build() {
  5. if (mainModule == null) {
  6. this.mainModule = new MainModule();
  7. }
  8. return new DaggerMainComponent(this);
  9. }
  10. public Builder mainModule(MainModule mainModule) {
  11. this.mainModule = Preconditions.checkNotNull(mainModule);
  12. return this;
  13. }
  14. }

其builder.mainModule()只是将传入的MainModue对象赋值给了自己本身的mainModule成员变量,注意这里的mainmodule对象使我们之前自己写的module类。
将module对象传入之后,调用其自身的build方法将自己传入DaggerMainComponent的构造方法当中生成生成了一个DaggerMainComponent对象,DaggerMainComponent(Builder builder)这个构造方法只是把Buider中携带的MainModule对象赋值给DaggerMainComponent自身的MainModule成员变量。 经过这些过程,我们获得了一个DaggerMainComponent对象

3.2.2 component.inject(this);

调用inject方法,将mainActivity本身作为参数传入该方法中,就完成了依赖对象的注入。
DaggerMainComponent#inject:

  1. @Override
  2. public void inject(MainActivity mainActivity) {
  3. injectMainActivity(mainActivity);
  4. }

DaggerMainComponent#injectMainActivity:

  1. private MainActivity injectMainActivity(MainActivity instance) {
  2. MainActivity_MembersInjector.injectMyCar(
  3. instance, MainModule_ProvideCarFactory.proxyProvideCar(mainModule));
  4. return instance;
  5. }

MainActivity_MembersInjector#injectMyCar:

  1. public static void injectMyCar(MainActivity instance, Car myCar) {
  2. instance.myCar = myCar;
  3. }

这里就是依赖注入的完成,通过这个inject方法,就把我们传入的instance,在这里是MainActivity对象中的myCar成员变量赋值我们传入的Car对象。

在调用injectMyCar方法的时候,Car对象是怎么传入的,我们需要关注一下,关注之前的代码,我们看到传入的car对象是这么构建的:

  1. MainModule_ProvideCarFactory.proxyProvideCar(mainModule)

MainModule_ProvideCarFactory#proxyProvideCar
这里传入的mainModule对象依然是我们自己创建的那个module类的对象

  1. public static Car proxyProvideCar(MainModule instance) {
  2. return Preconditions.checkNotNull(
  3. instance.provideCar(), "Cannot return null from a non-@Nullable @Provides method");
  4. }

这里调用是返回instance.provideCar()返回的对象,checkNotNull方法只是对返回的对象判断是否为null,如果为null就抛出异常,而这个instance.provideCar(),instance是MainModule对象,MainModule的provideCar()是我们自己实现的:

  1. @Provides
  2. Car provideCar() {
  3. return new BmwCar();
  4. }

这里就构建具体构建了我们的依赖对象

回顾一下:
我们自己构建的类:
1、Module:MainModule
创建这个module类要用@Module标注,在这类中要创建返回我们构建的依赖对象的方法,并且要用注解@Provides标注该方法
2、Component接口:MainComponent
这个接口要用@Component(modules = {MainModule.class}) 标注,其中Component注解中的module值要指定我们创建的与其对应的module
在这个接口当中我们要声明一个inject方法,这个方法中要把我们依赖对象注入的目标传进来。
3、需要注入依赖对象的目标当中,依赖对象的声明要用@Inject标注,之后编译project生成相应文件后创建生成的Component对象,调用其inject方法完成依赖注入。

其中的几个注解可以这么理解:
@Module : 代表该类是依赖对象生成工厂
@Provides : 依赖对象工厂中具体生成对象的方法
@Component : 依赖注入完成组件,这个注解声明的类中的inject方法完成了目标中依赖对象的赋值
@inject : 依赖注入目标中,要被注入的对象的标记,有了这个注解dagger就可以判断去注入哪个对象。

3.3 Dagger其他用法

3.3.1 单例模式 @Singleton

在之前代码不变的情况下,

  1. @Inject
  2. Car myCar;
  3. @Inject
  4. Car mySecondCar;

依赖注入以后:

  1. Log.e(TAG, "the create car's name : " + myCar.createCar() + " " + myCar.hashCode());
  2. Log.e(TAG, "my second car's name : " + mySecondCar.createCar() + " " + mySecondCar.hashCode());

结果:

01-12 20:38:31.718 3016-3016/? E/MainActivity: the create car's name : BMW 71488593
01-12 20:38:31.718 3016-3016/? E/MainActivity: my second car's name : BMW 80430262

可以看到这是两个不同的对象
假如在创建module和componet的时候加上@Singleton

  1. @Module
  2. public class MainModule {
  3. @Singleton
  4. @Provides
  5. Car provideCar() {
  6. return new BmwCar();
  7. }
  8. }
  9. @Singleton
  10. @Component(modules = {MainModule.class})
  11. public interface MainComponent {
  12. void inject(MainActivity mainActivity);
  13. }

再看输出:

01-12 20:44:24.805 3100-3100/? E/MainActivity: the create car's name : BMW 71488593
01-12 20:44:24.805 3100-3100/? E/MainActivity: my second car's name : BMW 71488593

可以看到,这样就是一个对象了

3.3.2 注入带有参数的依赖对象

假若创建Car的时候需要传入一个Context对象

  1. public class BmwCar implements Car {
  2. private Context mContext;
  3. public BmwCar(Context context) {
  4. mContext = context;
  5. }
  6. @Override
  7. public String createCar() {
  8. return "BMW";
  9. }
  10. }
  1. @Module
  2. public class MainModule {
  3. private Context mContext;
  4. public MainModule(Context context) {
  5. this.mContext = context;
  6. }
  7. @Provides
  8. public Context provideContext() {
  9. return this.mContext;
  10. }
  11. @Singleton
  12. @Provides
  13. Car provideCar(Context context) {
  14. return new BmwCar(context);
  15. }
  16. }

结果:

01-12 21:01:37.056 3205-3205/? E/MainActivity: the create car's name : BMW 71488593
01-12 21:01:37.057 3205-3205/? E/MainActivity: my second car's name : BMW 71488593

Tips

@Target取值问题,其用来标明注解所修饰的范围对象:
其取值是一个enum类型的数组

  1. public enum ElementType {
  2. TYPE, //类、接口、枚举类型、以及注解类型
  3. FIELD, //修饰类成员变量
  4. METHOD, //修饰方法
  5. PARAMETER, //修饰方法中的参数
  6. CONSTRUCTOR, //修饰构造方法
  7. LOCAL_VARIABLE, //修饰局部变量
  8. ANNOTATION_TYPE, //修饰注解(与Type区别在哪里)
  9. PACKAGE, //修饰包(疑问,什么叫做修饰包,怎么修饰包)
  10. TYPE_PARAMETER, //类型参数说明
  11. TYPE_USE //使用类型
  12. }

@Target怎么修饰包的?
@Target({ElementType.PACKAGE})理解和使用

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注