概述
依赖注入(dependency injection)是一个对象为另一个对象提供依赖关系的技术手段。简单点说,就是一个对象(client)要依赖其它对象(services)才能完成工作,那么这个对象(client)就对其它对象(services)产生了依赖,而依赖注入就是把依赖(services)在需要的时候自动传给client,而不是client自己创建或者寻找services。也就是说客户对象(client)把提供依赖的职责交给了外部代码(注入器),注入器(injector)的注入代码会构建services并调用client注入依赖,client不用知道注入代码是什么,不用去构建services依赖对象,也不用知道真正用到的是什么service对象,只需要知道要帮它完成工作的service接口即可。这样就把使用和构建的职责分离了,达到了松耦合的作用,遵循依赖倒置和单一职责原则。
Dagger是当前比较流行的依赖注入框架,最先由square/dagger开发,不过由于注入过程中使用了反射机制会影响性能等等问题被Google fork并进行改进优化,所以有了更加优秀的Dagger2。
Dagger2是Java/Android平台下的完全静态的、编译时的依赖注入框架。Dagger2的实现完全是自动生成代码(我们本应该手写的指定依赖关系的代码),以保证依赖注入尽可能的简单、可跟踪、高性能。依赖注入不单是为了利于测试,还是为了更容易地创建可重用的、可替换的Module。
使用
定义依赖(Declaring Dependencies)
Dagger,就像它的名字一样(Directed Acyclic Graph)将对象间的依赖关系表示成有向无环图。如果想要把类实例的构建交给Dagger处理,只需要在构造器上加上@Inject注解,Dagger会在需要的时候获取构造参数并调用构造器。
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
...
}
class CoffeeMaker {
@Inject Heater heater;
@Inject Pump pump;
...
}
Dagger可以直接对字段进行注入,将Heater实例和Pump实例注入给CoffeeMaker的字段。如果一个类有@Inject注解的字段但没有@Inject注解构造器,那么Dagger会在在需要的时候注入字段而不会创建新的实例,用@Inject注解无参构造器可以告诉Dagger可以构建该类的实例。缺少@Inject注解的类将不会被Dagger构建。
满足依赖(Satisfying Dependencies)
@Inject注解在某些情况下是不可行的:
这些情况下,就需要@Module注解和@Provides注解来描述依赖关系了。
@Module注解的类表明它可以提供依赖对象,Module类会包含一系列用@Provides注解的@Provides方法,而@Provides方法的返回值就是依赖对象(也就是依赖图中的节点)。
@Module
class DripCoffeeModule {
@Provides static Heater provideHeater() {
return new ElectricHeater();
}
@Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
}
按照惯例,@Provides方法以provide开头,Module类以Module结尾。
构建对象依赖图(Building the Graph)
@Inject和@Provides注解的类构成了对象依赖图,而应用可以通过一个接口访问这张依赖图,这个接口包含一系列返回值是依赖类型的无参方法。这个接口需要用@Component注解并给modules参数赋值:
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
Dagger2会自动生成该接口的实现类(以Dagger开头,如果@Component注解类不是顶级类,自动生成的实现类的名字会闭包命名并用下划线分割如DaggerFoo_Bar_BazComponent),可以通过该实现类的builder()方法获得builder对象,builder对象可以设置好依赖,最后通过build()方法构建实例:
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
如果Modules都是缺省构造器,@Provides方法都是静态的,用户不需要构建依赖实例,那么自动生成的实现类就会有create()方法获取新实例,就不需要处理builder了。
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker().brew();
}
}
单例及作用域绑定(Singletons and Scoped Bindings)
@Singleton注解的provider方法或可注入类,对象依赖图将为其所有client提供一个唯一实例(单例)。@Singleton也表明该类可以被多个线程共享:
@Provides @Singleton static Heater provideHeater() {
return new ElectricHeater();
}
@Singleton
class CoffeeMaker {
...
}
Dagger2要为component实现类实例 关联 对象依赖图的作用域实例,以便可以更好地控制依赖类实例和client实例的生命周期,因此component自己就需要声明他想关联哪个作用域。同一个component同时拥有@Singleton绑定和@RequestScoped绑定是没意义的,因为这些作用域生命周期不一样。要想给component关联某个作用域,只需要为component添加scope注解即可:
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
CoffeeMaker maker();
}
可重用的作用域绑定(Reusable scope)
如果你想控制@Inject构造器类被实例化或provider方法被调用的的次数,且不用保证某个component或subcomponent在生命周期中使用同一个实例,就可以使用@Reusable作用域了。@Reusable作用域绑定,不像其他作用域绑定一样需要关联一个component,它不会关联任何component,相反真正要用这个绑定的component都会缓存这个返回值或对象实例。
这就意味着,如果你用@Reusable为component指定module,且只有一个subcomponent会用到这个绑定,那么只有这个subcomponent会缓存这个绑定对象。如果两个subcomponent祖先没有共享一个绑定,那么每个subcomponent会各自缓存自己的对象。如果一个component的祖先已经缓存了绑定对象,那么subcomponent会重用这个对象。
由于无法保证component只会调用一次绑定,@Reusable绑定可能会返回不确定的对象,这个时候想引用同一个对象是很危险的。所以当你不关心对象会被实例化多少次使用@Reusable是安全的。
@Reusable // It doesn't matter how many scoopers we use,but don't waste them.
class CoffeeScooper {
@Inject CoffeeScooper() {}
}
@Module
class CashRegisterModule {
@Provides
@Reusable // DON'T DO THIS! You do care which register you put your cash in.
// Use a specific scope instead.
static CashRegister badIdeaCashRegister() {
return new CashRegister();
}
}
@Reusable // DON'T DO THIS! You really do want a new filter each time,so this
// should be unscoped.
class CoffeeFilter {
@Inject CoffeeFilter() {}
}
可释放的引用(Releasable references)
如果一个绑定使用scope注解,这就意味着component对象会持有这个作用域对象的引用直到component自己被GC回收。在像Android这样的内存敏感环境中,如果你想在内存紧张时让GC回收当前未使用的作用域对象,只需要使用@CanReleaseReferences定义一个scope即可:
@Documented
@Retention(RUNTIME)
@CanReleaseReferences
@Scope
public @interface MyScope {}
可以为scope注入一个ReleasableReferenceManager对象,在内存紧张时调用它的releaseStrongReferences()方法以便让component持有该对象的弱引用而不是强引用。
@Inject @ForReleasableReferences(MyScope.class)
ReleasableReferences myScopeReferences;
void lowMemory() {
myScopeReferences.releaseStrongReferences();
}
当内存压力减少时可以调用restoreStrongReferences()方法恢复缓存对象的强引用。
void highMemory() {
myScopeReferences.restoreStrongReferences();
}
延迟注入(Lazy injections)
如果你想要对象延迟实例化,对于绑定T
,你可以创建一个Lazy<T>
,它会延迟到Lazy<T>
第一次调用get()
方法时才会实例化。如果T
是单例,那么对象依赖图内的所有注入将会拥有唯一一个Lazy<T>
实例,否则每个注入会有自己的Lazy实例。不管这样。之后调用Lazy的get()
将只会获得唯一的T实例。
class GridingCoffeeMaker {
@Inject Lazy<Grinder> lazyGrinder;
public void brew() {
while (needsGrinding()) {
// Grinder created once on first call to .get() and cached.
lazyGrinder.get().grind();
}
}
}
Provider注入(Provider injections)
如果你想要拿到T
的多个注入实例,可以使用注入Provider<T>
而不是注入T
(虽然使用Factories,Builders等也可以实现),每次调用Provider<T>
的get()
方法都会调用一次绑定逻辑,如果绑定逻辑是@Inject构造器,那么每次都会创建新的实例,如果是@Provides方法就没法保证了。不过Provider<T>
也可能会使代码变得混乱,最好使用factory或Lazy<T>
或重新组织生命周期和代码结构来注入T
,当然Provider<T>
也可以作为生命周期保存着使用。
class BigCoffeeMaker {
@Inject Provider<Filter> filterProvider;
public void brew(int numberOfPots) {
...
for (int p = 0; p < numberOfPots; p++) {
maker.addFilter(filterProvider.get()); //new filter every time.
maker.addCoffee(...);
maker.percolate();
...
}
}
}
限定符(Qualifiers)
如果单一类型不足以描述一个依赖,可以使用限定符来描述。例如我们新增一个限定符注解@Named:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}
然后,就可以用这个限定符注解去注解字段或感兴趣的参数了:
class ExpensiveCoffeeMaker {
@Inject @Named("water") Heater waterHeater;
@Inject @Named("hot plate") Heater hotPlateHeater;
...
}
@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
return new ElectricHeater(70);
}
@Provides @Named("water") static Heater provideWaterHeater() {
return new ElectricHeater(93);
}
可选的绑定(Optional bindings)
如果你想某个绑定在component的某些依赖不满足的情况下也能工作,可以给Module添加一个@BindsOptionalOf方法:
@BindsOptionalOf abstract CoffeeCozy optionalCozy();
这就意味着@Inject构造器和成员和@Provides方法可以依赖一个Optional<CoffeeCozy>
对象,如果component绑定了CoffeeCozy
那么Optional就是当前的,否则Optional就是缺省的。你可以注入一下几种类型:
Optional<CoffeeCozy>
Optional<Provider<CoffeeCozy>>
Optional<Lazy<CoffeeCozy>>
Optional<Provider<Lazy<CoffeeCozy>>>
绑定实例(Binding Instances)
如果你想在绑定component时注入参数,如app需要一个用户名参数,就可以给component的builder方法添加一个[@BindsInstance][BindsInstance]方法注解以使用户名字符串实例可以被注入到component中:
@Component(modules = AppModule.class)
interface AppComponent {
App app();
@Component.Builder
interface Builder {
@BindsInstance Builder userName(@UserName String userName);
AppComponent build();
}
}
public static void main(String[] args) {
if (args.length > 1) { exit(1); }
App app = DaggerAppComponent
.builder()
.userName(args[0])
.build()
.app();
app.run();
}
编译时验证(Compile-time Validation)
Dagger2注解处理器是严格模式的,如果所有的绑定无效或者不完整就会导致编译时错误。例如这个module被安装到component,但是忘了绑定Executor:
@Module
class DripCoffeeModule {
@Provides static Heater provideHeater(Executor executor) {
return new cpuHeater(executor);
}
}
那么当编译时,javac就会拒绝这个错误的绑定:
[ERROR] COMPILATION ERROR :
[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.
只需要在当前component的任一Module中新增一个Executor的@Provides方法即可修复这个错误。
编译时生成代码(Compile-time Code Generation)
Dagger的注解处理器可能会自动生成像CoffeeMaker_Factory.java或CoffeeMaker_MembersInjector.java等源文件。这些文件就是Dagger的实现细节,你不需要直接去使用它们(虽然它们有利于注入时的单步调试),而你代码中只需要使用以Dagger开头的component就可以了。
在Android平台上的使用
哲理(Philosophy)
虽然Android应用使用Java语言,但代码的书写原则和书写风格还是和普通Java应用不同,最典型就是要考虑移动端设备的性能,一些特性不建议在Android平台上使用。
为了能自动生成既地道又轻量的代码,Dagger依靠混淆器(ProGuard)去处理编译后的字节码。这就保证了当使用不同的工具链生成可高效运行的字节码时,Dagger在server和Android都可生成自然流畅的源码。而且,Dagger也会确保自动生成的源码可以完全兼容混淆优化(ProGuard optimizations)。
专门为Android准备的相关类(dagger.android)
Android应用使用Dagger最主要的困难就是一些Framework类(如Activity、Fragment)是由操作系统实例化的,而Dagger更好工作的前提是它可以构建所有的注入对象。所以,你只能(不得不)在生命周期方法中进行成员变量注入,这就意味着一些类可能会写成这个样子:
public class FrombulationActivity extends Activity {
@Inject Frombulator frombulator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// DO THIS FIRST. Otherwise frombulator might be null!
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
// ... now you can write the exciting code
}
}
而这样的代码有几个问题:
- 很多类都复制粘贴这样的代码会让之后的重构变得很困难,如果越来越多开发者复制粘贴这样的代码块,就很少有人知道这段代码究竟干了什么了。
- 更重要的是,请求注入的类型(如这里的FrombulationActivity)需要知道它的注入器是什么。即使这些是通过接口而不是具体类型完成的,但它打破了依赖注入的核心原则:一个类不应该知道任何和注入实现细节相关的东西。
dagger.android包下面的类提供了简化这一模式的手段。
注入Activity对象(Injecting Activity objects)
- 将
AndroidInjectionModule
安装到你的application component,以保证这些基本类型所需的所有绑定是可用的。 写一个实现了
[AndroidInjector<YourActivity>][AndroidInjector]
的Subcomponent,Subcomponent的Builder
继承[AndroidInjector.Builder<YourActivity>][AndroidInjector.Builder]
即可。@Subcomponent(modules = ...) public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Builder public abstract class Builder extends AndroidInjector.Builder<YourActivity> {} }
定义好subcomponent之后,把它添加到你的component层次结构中(定义一个绑定了subcomponent builder的Module并把它添加到负责Application注入的那个component中):
@Module(subcomponents = YourActivitySubcomponent.class) abstract class YourActivityModule { @Binds @IntoMap @ActivityKey(YourActivity.class) abstract AndroidInjector.Factory<? extends Activity> bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder); } @Component(modules = {...,YourActivityModule.class}) interface YourApplicationComponent {}
接下来,让你的Application实现
HasDispatchingActivityInjector
,注入一个DispatchingAndroidInjector<Activity>
让activityInjector()
返回:public class YourApplication extends Application implements HasDispatchingActivityInjector { @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector; @Override public void onCreate() { super.onCreate(); DaggerYourApplicationComponent.create() .inject(this); } @Override public DispatchingAndroidInjector<Activity> activityInjector() { return dispatchingActivityInjector; } }
最后,在你Activity的
onCreate()
方法调用super.onCreate(savedInstanceState);
之前调用AndroidInjection.inject(this);
就可以了:public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); } }
经过上面这5步,你就可以非常方便地注入任何Activity对象了。但是它是怎么实现的呢?
AndroidInjection.inject()
会从你的Application中拿到一个DispatchingAndroidInjector<Activity>
并通过inject(Activity)
把Activity传了过去。这个DispatchingAndroidInjector
会查找一个你的Activity类的AndroidInjector.Factory
(YourActivitySubcomponent.Builder),构建AndroidInjector (YourActivitySubcomponent),然后把你的Activity传给inject(YourActivity)
。
注入Fragment对象(Injecting Fragment objects)
注入一个Fragment就像注入一个Activity一样简单。用同样的方式创建一个subcomponent,然后把Activity类型参数换成Fragment就行了(@ActivityKey
换成@FragmentKey
,HasDispatchingActivityInjector
换成HasDispatchingFragmentInjector
)。
不像Activity需要在onCreate()
方法中注入,Fragment要在onAttach()
方法中注入。
不像Activity那样定义Module,你可以选择在何处为Fragment安装Module。你可以让你的Fragment component成为其它Fragment component或Activity component,甚至Application component的subcomponent,这完全取决于你的Fragment需要哪些绑定。决定好component的位置后,完成相应类型的HasDispatchingFragmentInjector
实现。例如,如果你的Fragment需要来着YourActivitySubcomponent
的绑定,你的代码就要像下面这样:
public class YourActivity extends Activity implements HasDispatchingFragmentInjector {
@Inject DispatchingFragmentInjector<Fragment> fragmentInjector;
@Override
public void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
// ...
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return fragmentInjector;
}
}
public class YourFragment extends Fragment {
@Inject SomeDependency someDep;
@Override
public void onAttach(Activity activity) {
AndroidInjection.inject(this);
super.onAttach(activity);
// ...
}
}
@Subcomponent(modules = ...)
public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> {
@Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder<YourFragment> {}
}
@Module(subcomponents = YourFragmentSubcomponent.class)
abstract class YourFragmentModule {
@Binds
@IntoMap
@FragmentKey(YourFragment.class)
abstract AndroidInjector.Factory<? extends Fragment>
bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
}
@Subcomponent(modules = { YourFragmentModule.class,... }
public interface YourActivityOrYourApplicationComponent { ... }
基本的Framework类型(Base Framework Types)
由于DispatchingAndroidInjector
会在运行时查找合适的AndroidInjector
,基类需要像调用AndroidInjection.inject()
一样实现HasDispatchingActivityInjector
/HasDispatchingFragmentInjector
,子类需要做的就是绑定一个对应的@Subcomponent
。如果你的类层次不是很复杂的话Dagger会提供一些基类(如[DaggerActivity]和[DaggerFragment])。同时Dagger也会提供[DaggerApplication],你需要做的就是继承它并覆写applicationInjector()
方法以返回应该注入Application的component。
注意:[DaggerBroadcastReceiver]只适用于在AndroidManifest.xml文件中静态注册的BroadcastReceiver
,如果你在代码中手动创建注册BroadcastReceiver
,最好采用构造器注入。
支持库(Support libraries)
像使用Android support library一样,dagger.android.support包下面也会提供相应的类。但要注意,当support Fragment用户需要绑定AndroidInjector.Factory<? extends android.support.v4.app.Fragment>
时,AppCompat用户应该继续实现AndroidInjector.Factory<? extends Activity>
而不是<? extends AppCompatActivity>
或<? extends FragmentActivity>
。
何时注入(When to inject)
要尽可能的采用构造器注入,因为javac将确保被set之前没有字段被引用,这有利于避免空指针异常。但如果你一定要注入成员变量,最好尽早进行注入(越早越好)。也正是因为这样,DaggerActivity
才要在onCreate()
方法中立刻调用AndroidInjection.inject()
再调用super.onCreate()
。DaggerFragment
的onAttach()
也是一样,也是为了防止Fragment重新attach产生的矛盾。
references: