依赖注入框架解惑

作者: 王岩_shang | 来源:发表于2017-07-14 11:51 被阅读363次

    何为依赖注入?

    就是非自己主动初始化依赖,而是通过外部传入依赖的方式,我们就称之为依赖注入。
    而好处是什么呢?

    • 解耦,将依赖之间解耦。
    • 方便单元测试。

    何为依赖注入框架?

    就是一个框架 来做依赖注入这件事,这样我们就可以把自己的重心放到核心的逻辑上去。
    而一个依赖注入框架需要解决的事情:

    1. 生成 被注入对象
    2. 被注入对象 与 注入的类中的 引用 绑定。
    3. 额外的 被注入对象 生命周期管理。

    一个框架完成前两部分就ok了,而后一部分更像是基于此的扩充的额外功能。
    典型的例子如ButterKnife 和 AndroidAnnotations,对于Android 控件View控件的初始化,只需要一个注解,便省去了findViewbyId的繁琐,自动完成了对象和引用的绑定。然后View 的 生命周期便被Activity或者Fragment托管。
    但是从另一个方面去考虑的话,其实对象的生成是已经在inflate view 时产生出来了,和我们理解的从外部传入又有些不一致,并不是所谓的“依赖注入”。
    这里发现其实View也是可以直接注入的,哈哈 ,逃)

    不同的依赖注入框架实现

    • Spring IoC
      IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。


      Spring 容器

      Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
      最后使用xml配置,使用java的类反射机制生成实例,直接使用。


    • Roboguice
      这个是google提供的依赖注入框架,基于注解 ,基于反射,�效率上在Android 设备肯定不是最优的,现在google 也不再维护,而是推荐了其他框架如dagger和dagger2 这些,这里大家感兴趣的话,可以参考下 依赖注入框架性能对比
      有空的话,我再详细的分析下源码实现。
      //TODO 的分割线

    • dagger
      square 出品,必属精品。�注入框架是以JSR-330为标准的,后面的dagger2也参照了这个标准。
      下面是一个demo的例子:
    public class MainActivity extends AppCompatActivity {
    
        @Inject Test1 mTest1;
        @Inject TestManager15 mTestManager15;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ObjectGraph objectGraph = ObjectGraph.create(new Dagger1Module(), new ModelModule());
            objectGraph.inject(this);
            objectGraph.inject(mTest1);
            Log.d(MainActivity.class.getSimpleName(), "onCreate(): " + mTest1.toString());
            mTestManager15.start();
        }
    
    }
    
    

    dagger 用到了apt生成部分代码,在编译完成后我们可以看到下图目录是有代码的,apt生成的代码。


    apt生成的代码

    我们看下这个类内部实现,如下图:


    具体实现
    可以看出这个类在实际的代码中是没有被引用到了,但是这个类却完成在attch方法中完成了被注入的类的实例化工作,在injectMembers中完成了绑定的工作。
    显而易见的,这个类是通过反射实例化出来的。

    于是,我们知道了dagger的实现思路:

    * 通过apt生成一个单一容器类(injectadapter),这个类完成下面两个逻辑任务:
    1. 生成 被注入对象 的逻辑。
    2. 将  被注入对象  与 注入的类中的  引用  绑定 的逻辑。
    
    * 通过反射实例化这个�容器(injectadapter),�注入对象。
    

    具体的实现我们可以参考Linker和�下面的源码:

    
    /**
     * Handles loading/finding of modules, injection bindings, and static injections by use of a
     * strategy of "load the appropriate generated code" or, if no such code is found, create a
     * reflective equivalent.
     */
    public final class FailoverLoader extends Loader {
      /*
       * Note that String.concat is used throughout this code because it is the most efficient way to
       * concatenate _two_ strings.  javac uses StringBuilder for the + operator and it has proven to
       * be wasteful in terms of both CPU and memory allocated.
       */
    
      private final Memoizer<Class<?>, ModuleAdapter<?>> loadedAdapters =
          new Memoizer<Class<?>, ModuleAdapter<?>>() {
            @Override protected ModuleAdapter<?> create(Class<?> type) {
              ModuleAdapter<?> result =
                  instantiate(type.getName().concat(MODULE_ADAPTER_SUFFIX), type.getClassLoader());
              if (result == null) {
                throw new IllegalStateException("Module adapter for " + type + " could not be loaded. "
                    + "Please ensure that code generation was run for this module.");
              }
              return result;
            }
          };
    
      /**
       * Obtains a module adapter for {@code module} from the first responding resolver.
       */
      @SuppressWarnings("unchecked") // cache ensures types match
      @Override public <T> ModuleAdapter<T> getModuleAdapter(Class<T> type) {
        return (ModuleAdapter<T>) loadedAdapters.get(type);
      }
    
      @Override public Binding<?> getAtInjectBinding(
          String key, String className, ClassLoader classLoader, boolean mustHaveInjections) {
        Binding<?> result = instantiate(className.concat(INJECT_ADAPTER_SUFFIX), classLoader);
        if (result != null) {
          return result; // Found loadable adapter, returning it.
        }
        Class<?> type = loadClass(classLoader, className);
        if (type.equals(Void.class)) {
          throw new IllegalStateException(
              String.format("Could not load class %s needed for binding %s", className, key));
        }
        if (type.isInterface()) {
          return null; // Short-circuit since we can't build reflective bindings for interfaces.
        }
        return ReflectiveAtInjectBinding.create(type, mustHaveInjections);
      }
    
      @Override public StaticInjection getStaticInjection(Class<?> injectedClass) {
        StaticInjection result = instantiate(
              injectedClass.getName().concat(STATIC_INJECTION_SUFFIX), injectedClass.getClassLoader());
        if (result != null) {
          return result;
        }
        return ReflectiveStaticInjection.create(injectedClass);
      }
    }
    

    Dagger 相关概念

    Module:也叫 ModuleClass,指被 @Module 注解修饰的类,为 Dagger 提供需要依赖注入的 Host 信息及一些 Dependency 的生成方式。

    ModuleAdapter:指由 APT 根据 @Module 注解自动生成的类,父类是 Dagger 的 ModuleAdapter.java,与 ModuleClass 对应,以 ModuleClass 的 ClassName 加上 $$ModuleAdapter 命名,在 ModuleClass 的同一个 package 下。

    InjectAdapter:每个属性或构造函数被 @Inject 修饰的类都会生成一个 继承自 Binding.java 的子类,生成类以修饰类的 ClassName 加上 $$InjectAdapter 命名,在该类的同一个 package 下。

    ProvidesAdapter:每个被 @Provides 修饰的生成函数都会生成一个继承自 ProvidesBinding.java 的子类,ProvidesBinding.java 继承自 Binding.java,生成类以 Provide 函数名首字母大写加上 ProvidesAdapter 命名,是 Provide 函数所在 Module 对应生成的ModuleAdapter中的静态内部类。

    Binding:指由 APT 根据 @Inject 注解和 @Provides 注解自动生成,最终继承自 Binding.java 的类。为下面介绍的 DAG 图中的一个节点,每个 Host 及依赖都是一个 Binding。

    Binding 安装:指将 Binding 添加到 Binding 库中。对 Dagger Linker.java 代码来说是将 Binding 添加到 Linker.bindings 属性中,Linker.bindings 属性表示某个 ObjectGraph 已安装的所有 Binding。对于下面的 DAG 图来说是将节点放到图中,但尚未跟其他任何节点连接起来。


    DAG

    Binding 连接:把当前 Binding 和它内部依赖的 Binding 进行连接,即初始化这个 Binding 内部的所有 Binding,使它们可用。对 DAG 的角度说,就是把某个节点与其所依赖的各个节点连接起来。

    UML类图

    上图是 Dagger 整体框架最简类关系图。大致原理可以描述为:Linker通过Loader加载需要的Binding并把它们拼装成合理的依赖关系图 ObjectGraph,由ObjectGraph(其子类DaggerObjectGraph)最终实现依赖注入的管理。
    ObjectGraph 是个抽象类,DaggerObjectGraph 是它目前唯一的子类,对 Dagger 的调用实际都是对 DaggerObjectGraph 的调用。

    初始化

      private static ObjectGraph makeGraph(DaggerObjectGraph base, Loader plugin, Object... modules) {
         //储存静态的字符串key,value 是module的类名字
          Map<String, Class<?>> injectableTypes = new LinkedHashMap<String, Class<?>>();
        //静态属性
          Map<Class<?>, StaticInjection> staticInjections
              = new LinkedHashMap<Class<?>, StaticInjection>();
         //Object ,被注入的对象,以类名称为key,value为生成的$$ProvideAdapter
          StandardBindings baseBindings =
              (base == null) ? new StandardBindings() : new StandardBindings(base.setBindings);
          BindingsGroup overrideBindings = new OverridesBindings();
    
          Map<ModuleAdapter<?>, Object> loadedModules = Modules.loadModules(plugin, modules);
          for (Entry<ModuleAdapter<?>, Object> loadedModule : loadedModules.entrySet()) {
            ModuleAdapter<Object> moduleAdapter = (ModuleAdapter<Object>) loadedModule.getKey();
            for (int i = 0; i < moduleAdapter.injectableTypes.length; i++) {
              injectableTypes.put(moduleAdapter.injectableTypes[i], moduleAdapter.moduleClass);
            }
            for (int i = 0; i < moduleAdapter.staticInjections.length; i++) {
              staticInjections.put(moduleAdapter.staticInjections[i], null);
            }
            try {
              BindingsGroup addTo = moduleAdapter.overrides ? overrideBindings : baseBindings;
              //从moduleadapter中获取到将要绑定的provideAdapter对象,以@Provide的类名为key,ProvidesAdapter为value,
              moduleAdapter.getBindings(addTo, loadedModule.getValue());
            } catch (IllegalArgumentException e) {
              throw new IllegalArgumentException(
                  moduleAdapter.moduleClass.getSimpleName() + ": " + e.getMessage(), e);
            }
          }
    
          // Create a linker and install all of the user's bindings
          Linker linker =
              new Linker((base != null) ? base.linker : null, plugin, new ThrowingErrorHandler());
          //将map值放进到Linker中的bindings,在injectAdapter中调用requestBinding时使用
          linker.installBindings(baseBindings);
          linker.installBindings(overrideBindings);
    
          return new DaggerObjectGraph(
              base, linker, plugin, staticInjections, injectableTypes, baseBindings.setBindings);
        }
    

    在MainActivity attch时,调用:

     @Override
      @SuppressWarnings("unchecked")
      public void attach(Linker linker) {
        mTest1 = (Binding<com.nimbledroid.demo.dagger1.test.Test1>) linker.requestBinding("com.nimbledroid.demo.dagger1.test.Test1", MainActivity.class, getClass().getClassLoader());
        mTestManager15 = (Binding<com.nimbledroid.demo.dagger1.manager.TestManager15>) linker.requestBinding("com.nimbledroid.demo.dagger1.manager.TestManager15", MainActivity.class, getClass().getClassLoader());
      }
    

    此时调用,默认后两个参数为true:

      public Binding<?> requestBinding(String key, Object requiredBy, ClassLoader classLoader,
          boolean mustHaveInjections, boolean library) {
        assertLockHeld();
    
        Binding<?> binding = null;
        for (Linker linker = this; linker != null; linker = linker.base) {
          binding = linker.bindings.get(key);
          if (binding != null) {
            if (linker != this && !binding.isLinked()) throw new AssertionError();
            break;
          }
        }
    
        if (binding == null) {
          // We can't satisfy this binding. Make sure it'll work next time!
          Binding<?> deferredBinding =
              new DeferredBinding(key, classLoader, requiredBy, mustHaveInjections);
          deferredBinding.setLibrary(library);
          deferredBinding.setDependedOn(true);
          toLink.add(deferredBinding);
          attachSuccess = false;
          return null;
        }
    
        if (!binding.isLinked()) {
          toLink.add(binding); // This binding was never linked; link it now!
        }
    
        binding.setLibrary(library);
        binding.setDependedOn(true);
        return binding;
      }
    

    在inject 传入需要赋给引用的对象后,会调用到:

      /**
       * Links all requested bindings plus their transitive dependencies. This
       * creates JIT bindings as necessary to fill in the gaps.
       *
       * @throws AssertionError if this method is not called within a synchronized block which
       *     holds this {@link Linker} as the lock object.
       */
      public void linkRequested() {
        assertLockHeld();
        Binding<?> binding;
        while ((binding = toLink.poll()) != null) {
          if (binding instanceof DeferredBinding) {
            DeferredBinding deferred = (DeferredBinding) binding;
            String key = deferred.deferredKey;
            boolean mustHaveInjections = deferred.mustHaveInjections;
            if (bindings.containsKey(key)) {
              continue; // A binding for this key has since been linked.
            }
            try {
            //////实例化出需要注入的对象
              Binding<?> resolvedBinding =
                  createBinding(key, binding.requiredBy, deferred.classLoader, mustHaveInjections);
              resolvedBinding.setLibrary(binding.library());
              resolvedBinding.setDependedOn(binding.dependedOn());
              // Fail if the type of binding we got wasn't capable of what was requested.
              if (!key.equals(resolvedBinding.provideKey) && !key.equals(resolvedBinding.membersKey)) {
                throw new IllegalStateException("Unable to create binding for " + key);
              }
              // Enqueue the JIT binding so its own dependencies can be linked.
              Binding<?> scopedBinding = scope(resolvedBinding);
              toLink.add(scopedBinding);
              putBinding(scopedBinding);
            } catch (InvalidBindingException e) {
              addError(e.type + " " + e.getMessage() + " required by " + binding.requiredBy);
              bindings.put(key, Binding.UNRESOLVED);
            } catch (UnsupportedOperationException e) {
              addError("Unsupported: " + e.getMessage() + " required by " + binding.requiredBy);
              bindings.put(key, Binding.UNRESOLVED);
            } catch (IllegalArgumentException e) {
              addError(e.getMessage() + " required by " + binding.requiredBy);
              bindings.put(key, Binding.UNRESOLVED);
            } catch (RuntimeException e) {
              throw e;
            } catch (Exception e) {
              throw new RuntimeException(e);
            }
          } else {
            // Attempt to attach the binding to its dependencies. If any dependency
            // is not available, the attach will fail. We'll enqueue creation of
            // that dependency and retry the attachment later.
            attachSuccess = true;
            binding.attach(this);
            if (attachSuccess) {
              binding.setLinked();
            } else {
              toLink.add(binding);
            }
          }
        }
    
        try {
          errorHandler.handleErrors(errors);
        } finally {
          errors.clear();
        }
      }
    

    然后是检测是否有循环依赖:

     private static void detectCircularDependencies(Collection<Binding<?>> bindings,
          List<Binding<?>> path) {
        for (Binding<?> binding : bindings) {
          if (binding.isCycleFree()) {
            continue;
          }
    
          if (binding.isVisiting()) {
            int index = path.indexOf(binding);
            StringBuilder message = new StringBuilder()
                .append("Dependency cycle:");
            for (int i = index; i < path.size(); i++) {
              message.append("\n    ").append(i - index).append(". ")
                  .append(path.get(i).provideKey).append(" bound by ").append(path.get(i));
            }
            message.append("\n    ").append(0).append(". ").append(binding.provideKey);
            throw new IllegalStateException(message.toString());
          }
    
          binding.setVisiting(true);
          path.add(binding);
          try {
            ArraySet<Binding<?>> dependencies = new ArraySet<Binding<?>>();
            binding.getDependencies(dependencies, dependencies);
            //获取当前binding的依赖,进行递归检测
            detectCircularDependencies(dependencies, path);
            binding.setCycleFree(true);
          } finally {
            path.remove(path.size() - 1);
            binding.setVisiting(false);
          }
        }
      }
    

    • dagger2
      dagger2则更进一步的进行了优化,利于接口的特性和apt的�强大功能,完全摆脱的反射,所有的类,绑定实现都是依靠自动生成的代码进行。
      对外部提供的入口类在编译时�生成,逻辑的交互也就依靠接口来实现。
      在使用dagger2时,需要理解几个概念 :
    @Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。
    
    @Module: Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的modules)。
    
    @Provide: 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。
    
    @Component: Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的@Modules组成该组件,如果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的modules知道依赖的范围。
    
    @Scope: Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。后面会演示一个例子,这是一个非常强大的特点,因为就如前面说的一样,没必要让每个对象都去了解如何管理他们的实例。在scope的例子中,我们用自定义的@PerActivity注解一个类,所以这个对象存活时间就和activity的一样。简单来说就是我们可以定义所有范围的粒度(@PerFragment, @PerUser, 等等)。
    
    Qualifier: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在Android中,我们会需要不同类型的context,所以我们就可以定义qualifier注解“@ForApplication”和“@ForActivity”,这样当注入一个context的时候,我们就可以告诉Dagger我们想要哪种类型的context。
    

    而后,我们来分析一个简单的dagger2的例子:
    定义的Subcomponent:

    /**
     * Scope: activity, it will be instantiated when the MainActivity starts.
     */
    @ActivityScope
    @Subcomponent(modules = {MainModule.class})
    public interface MainComponent {
    
        void inject(MainActivity activity);
    
    }
    

    而后我们在MainActivity中写下如下逻辑:

    public class MainActivity extends AppCompatActivity {
    
        @Inject
        @Named("AppName")
        String mAppName;
        @Inject
        @Named("UserName")
        String mUserName;
        @Inject
        @Named("title")
        String mTitle;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            MainComponent mainComponent = App.userComponent().plusMain();
            mainComponent.inject(this);
    
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ActionBar actionBar = getSupportActionBar();
            actionBar.setTitle(mTitle);
    
            TextView textView = (TextView) findViewById(R.id.text_view);
            textView.setText(mAppName);
            TextView userView = (TextView) findViewById(R.id.user_view);
            userView.setText(getString(R.string.current_user, mUserName));
        }
        
    }
    

    我们注入了3个�String字符串,并将MainActivity的引用传递给了MainComponent 接口。显而易见的,我们知道inject后,mainComponent 获取到MainActivity,下一步的动作便是为@Inject的成员变量赋值。
    我们看下具体�apt生成的类:

    @Generated(
      value = "dagger.internal.codegen.ComponentProcessor",
      comments = "https://google.github.io/dagger"
    )
    public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
      private final Provider<String> mAppNameProvider;
    
      private final Provider<String> mUserNameProvider;
    
      private final Provider<String> mTitleProvider;
    
      public MainActivity_MembersInjector(
          Provider<String> mAppNameProvider,
          Provider<String> mUserNameProvider,
          Provider<String> mTitleProvider) {
        assert mAppNameProvider != null;
        this.mAppNameProvider = mAppNameProvider;
        assert mUserNameProvider != null;
        this.mUserNameProvider = mUserNameProvider;
        assert mTitleProvider != null;
        this.mTitleProvider = mTitleProvider;
      }
    
      public static MembersInjector<MainActivity> create(
          Provider<String> mAppNameProvider,
          Provider<String> mUserNameProvider,
          Provider<String> mTitleProvider) {
        return new MainActivity_MembersInjector(mAppNameProvider, mUserNameProvider, mTitleProvider);
      }
    
      @Override
      public void injectMembers(MainActivity instance) {
        if (instance == null) {
          throw new NullPointerException("Cannot inject members into a null reference");
        }
        instance.mAppName = mAppNameProvider.get();
        instance.mUserName = mUserNameProvider.get();
        instance.mTitle = mTitleProvider.get();
      }
    
      public static void injectMAppName(MainActivity instance, Provider<String> mAppNameProvider) {
        instance.mAppName = mAppNameProvider.get();
      }
    
      public static void injectMUserName(MainActivity instance, Provider<String> mUserNameProvider) {
        instance.mUserName = mUserNameProvider.get();
      }
    
      public static void injectMTitle(MainActivity instance, Provider<String> mTitleProvider) {
        instance.mTitle = mTitleProvider.get();
      }
    }
    
    

    dagger2通过接口的定义规避了�dagger通过 反射 获取 apt生成实现类 的问题,进一步的提高了注入的效率,确实值得学习。

    延伸思考

    但是dagger2也存在一些问题:

    1. 我们在使用一个框架或者第三方类库,这个东西首先是易于学习和上手,但是dagger2的一些概念确实让新手“望而生畏”,学习上确实需要一定成本,不能拿来就用。
    2. 对象生命周期的管理�,如何确保不发生内存泄漏的问题,很大程度上依赖�使用者。

    基于以上两点,我们可以做一些优化拓展:
    首先是接口的定义,我们是希望给使用者提供一些简单,可用,灵活性高的api,代码的生成上,我们不仅可以利用到apt,甚至可以在编译阶段使用javasist等工具改变�造类。
    其次我们可以构建一个“对象容器”,就像Spring一样,管理对象的生命周期。

    TODO

    上面的分析�只是粗浅的概要,一些细节的实现如单例如何注入以及Scope均未分析到,可以参考文末的链接,另外我也会一直完善这个文档的。

    参考

    相关文章

      网友评论

        本文标题:依赖注入框架解惑

        本文链接:https://www.haomeiwen.com/subject/szenhxtx.html