Dagger2 User's Guide(翻译)
概述依赖注入(dependency injection)是一个对象为另一个对象提供依赖关系的技术手段。简单点说,就是一个对象(client)要依赖其它对象(services)才能完成工作,那么这个对象(client)就对其它对象(services)产生了依赖,而依赖注入就是把依赖(services)在需要的时候自动传给client,而不是client自己创建或者寻找services。也就是说客户对象(client)把提供依赖的职责交给了外部代码(注入器),注入器(injector)的注入代码会构建services并调用client注入依赖,client不用知道注入代码是什么,不用去构建services依赖对象,也不用知道真正用到的是什么service对象,只需要知道要帮它完成工作的service接口即可。这样就把使用和构建的职责分离了,达到了松耦合的作用,遵循依赖倒置和单一职责原则。 使用定义依赖(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
class DripCoffeeModule {
@Provides static Heater provideHeater() {
return new ElectricHeater();
}
@Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
}
构建对象依赖图(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 // 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)如果你想要对象延迟实例化,对于绑定 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)如果你想要拿到 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方法可以依赖一个
绑定实例(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平台上使用。 专门为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
}
}
而这样的代码有几个问题:
dagger.android包下面的类提供了简化这一模式的手段。 注入Activity对象(Injecting Activity objects)
经过上面这5步,你就可以非常方便地注入任何Activity对象了。但是它是怎么实现的呢? 注入Fragment对象(Injecting Fragment objects)注入一个Fragment就像注入一个Activity一样简单。用同样的方式创建一个subcomponent,然后把Activity类型参数换成Fragment就行了( 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)由于 支持库(Support libraries)像使用Android support library一样,dagger.android.support包下面也会提供相应的类。但要注意,当support Fragment用户需要绑定 何时注入(When to inject)要尽可能的采用构造器注入,因为javac将确保被set之前没有字段被引用,这有利于避免空指针异常。但如果你一定要注入成员变量,最好尽早进行注入(越早越好)。也正是因为这样, references:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |