本文介绍了Dagger 2 中@Qualifier和@Named注解以及Lazy和Provider的使用。
本文首发:http://yuweiguocn.github.io/
《卜算子·咏梅》
风雨送春归,飞雪迎春到。已是悬崖百丈冰,犹有花枝俏。
俏也不争春,只把春来报。待到山花烂漫时,她在丛中笑。
-近代,毛泽东
注解@Qualifier和注解@Named
书接上文,在上文中我们使用注解@Binds
完成了对People抽象类的其中一个子类的绑定,如果我们想同时注入两个子类该怎样处理?
public abstract class People {
abstract String doWhat();
}
public class Student extends People {
@Inject
public Student() {
}
@Override
String doWhat() {
return "study";
}
}
public class Worker extends People {
@Inject
public Worker() {
}
@Override
String doWhat() {
return "work";
}
}
在Module类中添加对另一个子类Worker的绑定方法,这里我们用到了限定符注解@Named
,由于我们提供了多个依赖导致Dagger2无法识别要注入哪一个依赖,使用限定符注解@Named
很好地解决了这种问题。
@Module
public abstract class PeopleModule {
@Binds
@Named("study")
abstract People bindStudent(Student student);
@Binds
@Named("work")
abstract People bindWorker(Worker worker);
}
@Component(modules = PeopleModule.class)
public interface PeopleComponent {
void inject(PeopleActivity activity);
}
在提供的实例添加注解@Named
,在使用依赖注入的地方添加同样的注解:
public class PeopleActivity extends Activity {
@Inject
@Named("study")
People student;
@Inject
@Named("work")
People worker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerPeopleComponent.create().inject(this);
Log.d("debug", student.doWhat());
Log.d("debug", worker.doWhat());
}
}
同样我们也可以使用限定符注解@Qualifier
通过自定义注解完成相同的功能。
使用注解@Qualifier
自定义注解:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Study {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Work {
}
然后将自定义的注解替换上面注解@Named
:
@Module
public abstract class PeopleModule {
@Binds
@Study
abstract People bindStudent(Student student);
@Binds
@Work
abstract People bindWorker(Worker worker);
}
public class PeopleActivity extends Activity {
@Inject
@Study
People student;
@Inject
@Work
People worker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerPeopleComponent.create().inject(this);
setContentView(R.layout.activity_main);
Log.d("debug", student.doWhat());
Log.d("debug", worker.doWhat());
}
}
由于使用注解@Named
需要指定字符串,可能会由于人为疏忽造成错误,所以这里推荐使用限定符注解@Qualifier
自定义注解的方法。
Lazy和Provider
我们可以使用Lazy
在注入时不进行初始化,在调用get方法时再初始化实例:
public class PeopleActivity extends Activity {
@Inject
@Work
Lazy<People> worker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerPeopleComponent.create().inject(this);
setContentView(R.layout.activity_main);
Log.d("debug", worker.get().doWhat());
}
}
然后来看一下生成的代码:
public final class DaggerPeopleComponent implements PeopleComponent {
...
private PeopleActivity injectPeopleActivity(PeopleActivity instance) {
PeopleActivity_MembersInjector.injectWorker(
instance, DoubleCheck.lazy((Provider) Worker_Factory.create()));
return instance;
}
...
}
这里终于用到了生成的工厂类:
public final class Worker_Factory implements Factory<Worker> {
private static final Worker_Factory INSTANCE = new Worker_Factory();
@Override
public Worker get() {
return provideInstance();
}
public static Worker provideInstance() {
return new Worker();
}
public static Worker_Factory create() {
return INSTANCE;
}
public static Worker newWorker() {
return new Worker();
}
}
这里使用双重检查锁定为我们提供了唯一的实例,分析具体可参见:Java实现单例模式的正确姿势
/**
* A {@link Lazy} and {@link Provider} implementation that memoizes the value returned from a
* delegate using the double-check idiom described in Item 71 of <i>Effective Java 2</i>.
*/
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance = UNINITIALIZED;
private DoubleCheck(Provider<T> provider) {
assert provider != null;
this.provider = provider;
}
@SuppressWarnings("unchecked") // cast only happens when result comes from the provider
@Override
public T get() {
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
result = provider.get();
instance = reentrantCheck(instance, result);
/* Null out the reference to the provider. We are never going to need it again, so we
* can make it eligible for GC. */
provider = null;
}
}
}
return (T) result;
}
/**
* Checks to see if creating the new instance has resulted in a recursive call. If it has, and the
* new instance is the same as the current instance, return the instance. However, if the new
* instance differs from the current instance, an {@link IllegalStateException} is thrown.
*/
public static Object reentrantCheck(Object currentInstance, Object newInstance) {
boolean isReentrant = !(currentInstance == UNINITIALIZED
// This check is needed for fastInit's implementation, which uses MemoizedSentinel types.
|| currentInstance instanceof MemoizedSentinel);
if (isReentrant && currentInstance != newInstance) {
throw new IllegalStateException("Scoped provider was invoked recursively returning "
+ "different results: " + currentInstance + " & " + newInstance + ". This is likely "
+ "due to a circular dependency.");
}
return newInstance;
}
...
/** Returns a {@link Lazy} that caches the value from the given provider. */
// This method is declared this way instead of "<T> Lazy<T> lazy(Provider<T> delegate)"
// to work around an Eclipse type inference bug: https://github.com/google/dagger/issues/949.
public static <P extends Provider<T>, T> Lazy<T> lazy(P provider) {
if (provider instanceof Lazy) {
@SuppressWarnings("unchecked")
final Lazy<T> lazy = (Lazy<T>) provider;
// Avoids memoizing a value that is already memoized.
// NOTE: There is a pathological case where Provider<P> may implement Lazy<L>, but P and L
// are different types using covariant return on get(). Right now this is used with
// DoubleCheck<T> exclusively, which is implemented such that P and L are always
// the same, so it will be fine for that case.
return lazy;
}
return new DoubleCheck<T>(checkNotNull(provider));
}
}
相对于Lazy
注入,Provider
注入每次调用get时都会提供一个新的实例,Provider
是由Java提供的接口:
public class PeopleActivity extends Activity {
@Inject
@Study
Provider<People> student;
@Inject
@Work
Lazy<People> worker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerPeopleComponent.create().inject(this);
setContentView(R.layout.activity_main);
Log.d("debug", "1"+student.get().toString());
Log.d("debug", "2"+student.get().toString());
Log.d("debug", "3"+worker.get().toString());
Log.d("debug", "4"+worker.get().toString());
}
}
打印输出如下:
2018-11-12 21:26:26.365 30805-30805/? D/debug: 1io.github.yuweiguocn.test.Student@205713d
2018-11-12 21:26:26.365 30805-30805/? D/debug: 2io.github.yuweiguocn.test.Student@6fffd32
2018-11-12 21:26:26.365 30805-30805/? D/debug: 3io.github.yuweiguocn.test.Worker@d03a83
2018-11-12 21:26:26.365 30805-30805/? D/debug: 4io.github.yuweiguocn.test.Worker@d03a83
对比下生成的代码,唯一的区别就是Lazy使用DoubleCheck保证了实例的唯一性:
public final class DaggerPeopleComponent implements PeopleComponent {
...
private PeopleActivity injectPeopleActivity(PeopleActivity instance) {
PeopleActivity_MembersInjector.injectStudent(instance, (Provider) Student_Factory.create());
PeopleActivity_MembersInjector.injectWorker(
instance, DoubleCheck.lazy((Provider) Worker_Factory.create()));
return instance;
}
...
}
总结
- 当提供多个相同类型的注入依赖时,可以使用限定符注解
@Qualifier
和注解@Named
,为了避免人为疏忽导致错误,推荐使用注解@Qualifier
自定义注解完成相应功能 - 使用Lazy可以在调用get方法时再初始化,其中使用了双重检查锁定保证了实例的唯一性
- 使用Provider在调用get方法时会提供新的实例
网友评论