注:原文链接
依赖注入(Dependency Injection简称DI)是在控制反转(Inversion of Control)的概念之上构建的。这表示说一个类应该从它的外部得到它的依赖。简单来说,一个类不应该实例化另外一个类(即所依赖的类),而是应该从一个配置类中得到它的实例。
如果一个Java类通过new操作符创建了另外一个类的实例,那么该实例就不能被单独的使用和测试,这种依赖的方法称之为"强依赖"(Hard Dependency)。
那么,从外部获取类的实例会给我们带来什么样的好处呢?最重要的优势就是增强了类的重用性和能够独立与其他类进行测试
怎样才能做到DI呢?这可能需要我们回顾一下过去。
一个我们称之为依赖容器的框架类通常被用于分析类的依赖关系。有了这样的分析,便能够通过反射(Reflections)来构建类的实例和将这个对象注入到定义的依赖关系里面去。这边消除了强依赖。这样也就可以单独地测试类,例如使用模拟对象。这是dagger1。
这个过程最主要的劣势有两点:
- 反射效率低下
- 运行时执行依赖解析,导致意外崩溃
因此,Daggar2应运而生。
Dagger2 最大的改变就是使用注解处理器(annotation processor)来生成依赖图。这些类提供了使用javax
注解包在编译时生成的依赖关系,这有助于在应用程序运行之前进行可能的错误检查。生成的代码可读性很强。
为了描述依赖关系的类的标准注解被定义在Java Specification Request 330 (JSR 330)
注入模式:
- 构造器注入---注入方法参数
- 字段注入 ---注入成员变量
- 方法注入 ---注入方法参数
根据JSR 330的依赖注入顺序:
1、Constructor
2、Field
3、Method
注:
- 使用
@Inject
注解标注的方法或字段不存在顺序问题 - 当字段或方法参数在构造方法调用之后后被注入,你不能在构造方法中使用注入的成员变量
我对于Daggar2的理解如下:
A dependency consumer asks for the dependency(Object) from a dependency provider through a connector.
一个依赖消费者(目标类中用@Inject注解标注的类对象)通过连接器从依赖提供者(Dependency provider)来获取依赖对象[给出原文,不太会翻译]
-
依赖提供者:被
@Module
注解标注的类,负责提供被注入的类对象(即目标类所需的对象)。一些定义了被@Provides
注解标注的方法的类。从这些方法返回的对象就是用来依赖注入的。 - 依赖消费者:目标类中用@Inject注解标注的类对象
-
连接消费者和生产者:使用
@Component
注解标注的接口或抽象类。它定义了一个对象生产者(Module)和依赖对象的连接。
Daggar2的限制
- 不能自动注入字段(Field)
- 不能注入使用private修饰的Field
- 若想要使用Field注解,不得不在
@Component
注解标注的接口或抽象类中定义一个方法。
项目实战
项目地址
核心类
-
DataManager
--App数据的入口类 -
DbHelper
-- DataManager使用它去访问数据库 -
SharedPrefsHelper
--DataManager使用它去访问SharedPreferences
步骤一
在build.gradle文件中添加依赖:
dependencies {
...
compile "com.google.dagger:dagger:2.8"
annotationProcessor "com.google.dagger:dagger-compiler:2.8"
provided 'javax.annotation:jsr250-api:1.0'
compile 'javax.inject:javax.inject:1'
}
步骤二
创建实体类:
public class User {
private Long id;
private String name;
private String address;
private String createdAt;
private String updatedAt;
public User() {
}
public User(String name, String address) {
this.name = name;
this.address = address;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public String getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(String updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", address='" + address + '\'' +
", createdAt='" + createdAt + '\'' +
", updatedAt='" + updatedAt + '\'' +
'}';
}
}
步骤三
创建自定义注解ActivityContext
,ApplicationContext
,DatabaseInfo
,PerActivity
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityContext {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationContext {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface DatabaseInfo {
}
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {
}
注
为什么我们要自定义这些注解?@Qualifier
和@Scope
又代表什么意思呢?
@Qualifier
注解是javax inject package
提供的,是用来区分不同依赖的。例如:一个类需要查询两个Context,一个Application的Context,一个Activity的Context,但是两者的类型都是Context,就可以用@Qualifier来区分。所以,对于dagger2来说,找出哪些变量是要提供什么,我们必须明确指定的标识符。
因此,@Qualifier
被是用来区分同一对象的不同实例,在上面的代码中,我们有ApplicationContext
和ActivityContext
以至于需要被注入的Context
能够指出各自的Context
真正类型。使用DatabaseInfo
来给依赖提供数据库名字。由于String
类是被当作依赖项来提供的,对其进行限定,以至于Daggar能够清楚地找到并知道这个字符串是数据库名字。
另外一个注解@Named
是Daggar2提供的。@Named
自身就是被@Qualifier
注解标注的。使用@Named
,我们必须为相似的类对象提供标识符,这个标识符用于映射类的依赖关系。下文还将继续对@Named
进行探索。
@Scope
用于指定依赖对象持久化的范围。如果一个类获得依赖项,则向成员注入带有范围注释的类,则该类的每个实例通过请求依赖项都将得到自己的成员变量集。
步骤四
创建DbHelper
类来继承 SQLiteOpenHelper
,负责所有的Db操作。
@Singleton
public class DbHelper extends SQLiteOpenHelper {
//USER TABLE
public static final String USER_TABLE_NAME = "users";
public static final String USER_COLUMN_USER_ID = "id";
public static final String USER_COLUMN_USER_NAME = "usr_name";
public static final String USER_COLUMN_USER_ADDRESS = "usr_add";
public static final String USER_COLUMN_USER_CREATED_AT = "created_at";
public static final String USER_COLUMN_USER_UPDATED_AT = "updated_at";
@Inject
public DbHelper(@ApplicationContext Context context,
@DatabaseInfo String dbName,
@DatabaseInfo Integer version) {
super(context, dbName, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
tableCreateStatements(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + USER_TABLE_NAME);
onCreate(db);
}
private void tableCreateStatements(SQLiteDatabase db) {
try {
db.execSQL(
"CREATE TABLE IF NOT EXISTS "
+ USER_TABLE_NAME + "("
+ USER_COLUMN_USER_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ USER_COLUMN_USER_NAME + " VARCHAR(20), "
+ USER_COLUMN_USER_ADDRESS + " VARCHAR(50), "
+ USER_COLUMN_USER_CREATED_AT + " VARCHAR(10) DEFAULT " + getCurrentTimeStamp() + ", "
+ USER_COLUMN_USER_UPDATED_AT + " VARCHAR(10) DEFAULT " + getCurrentTimeStamp() + ")"
);
} catch (SQLException e) {
e.printStackTrace();
}
}
protected User getUser(Long userId) throws Resources.NotFoundException, NullPointerException {
Cursor cursor = null;
try {
SQLiteDatabase db = this.getReadableDatabase();
cursor = db.rawQuery(
"SELECT * FROM "
+ USER_TABLE_NAME
+ " WHERE "
+ USER_COLUMN_USER_ID
+ " = ? ",
new String[]{userId + ""});
if (cursor.getCount() > 0) {
cursor.moveToFirst();
User user = new User();
user.setId(cursor.getLong(cursor.getColumnIndex(USER_COLUMN_USER_ID)));
user.setName(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_NAME)));
user.setAddress(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_ADDRESS)));
user.setCreatedAt(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_CREATED_AT)));
user.setUpdatedAt(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_UPDATED_AT)));
return user;
} else {
throw new Resources.NotFoundException("User with id " + userId + " does not exists");
}
} catch (NullPointerException e) {
e.printStackTrace();
throw e;
} finally {
if (cursor != null)
cursor.close();
}
}
protected Long insertUser(User user) throws Exception {
try {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(USER_COLUMN_USER_NAME, user.getName());
contentValues.put(USER_COLUMN_USER_ADDRESS, user.getAddress());
return db.insert(USER_TABLE_NAME, null, contentValues);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private String getCurrentTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}
}
注:以下是这个类用到的一些注解
-
@Singleton
- 保证一个类全局的单例 -
@Inject
- 在构造函数构建时,构造函数指示Daggar所需的所有参数依赖项 -
@ApplicationContext
、@ApplicationContext Qualifier
- 帮助DbHelper
在Daggar的以来图中获得Application -
@DatabaseInfo
帮助Daggar从存在相同类型的依赖图中去区分出String
和Integer
的限定符
步骤五
创建一个SharedPrefsHelper
处理SharedPreferences
@Singleton
public class SharedPrefsHelper {
public static String PREF_KEY_ACCESS_TOKEN = "access-token";
private SharedPreferences mSharedPreferences;
@Inject
public SharedPrefsHelper(SharedPreferences sharedPreferences) {
mSharedPreferences = sharedPreferences;
}
public void put(String key, String value) {
mSharedPreferences.edit().putString(key, value).apply();
}
public void put(String key, int value) {
mSharedPreferences.edit().putInt(key, value).apply();
}
public void put(String key, float value) {
mSharedPreferences.edit().putFloat(key, value).apply();
}
public void put(String key, boolean value) {
mSharedPreferences.edit().putBoolean(key, value).apply();
}
public String get(String key, String defaultValue) {
return mSharedPreferences.getString(key, defaultValue);
}
public Integer get(String key, int defaultValue) {
return mSharedPreferences.getInt(key, defaultValue);
}
public Float get(String key, float defaultValue) {
return mSharedPreferences.getFloat(key, defaultValue);
}
public Boolean get(String key, boolean defaultValue) {
return mSharedPreferences.getBoolean(key, defaultValue);
}
public void deleteSavedData(String key) {
mSharedPreferences.edit().remove(key).apply();
}
}
这个类同样是被@Singleton
注解所标注的,这样就保证了它在整个依赖图中是单例。
这个类同样也是通过构造器被@Inject
注解所标注的Daggar对象得到SharedPreferences
依赖。
那么这个依赖是怎样被提供的呢?下面会再做解释。
步骤六
创建DataManager
类:
@Singleton
public class DataManager {
private Context mContext;
private DbHelper mDbHelper;
private SharedPrefsHelper mSharedPrefsHelper;
@Inject
public DataManager(@ApplicationContext Context context,
DbHelper dbHelper,
SharedPrefsHelper sharedPrefsHelper) {
mContext = context;
mDbHelper = dbHelper;
mSharedPrefsHelper = sharedPrefsHelper;
}
public void saveAccessToken(String accessToken) {
mSharedPrefsHelper.put(SharedPrefsHelper.PREF_KEY_ACCESS_TOKEN, accessToken);
}
public String getAccessToken(){
return mSharedPrefsHelper.get(SharedPrefsHelper.PREF_KEY_ACCESS_TOKEN, null);
}
public Long createUser(User user) throws Exception {
return mDbHelper.insertUser(user);
}
public User getUser(Long userId) throws Resources.NotFoundException, NullPointerException {
return mDbHelper.getUser(userId);
}
}
注:这个类在构造方法中表达了需要Application Context
、DbHelper
和 SharedPrefsHelper
依赖。它提供了所有访问应用api的数据入口。
步骤七
创建继承android.app.Application
类的DemoApplication
public class DemoApplication extends Application {
protected ApplicationComponent applicationComponent;
@Inject
DataManager dataManager;
public static DemoApplication get(Context context) {
return (DemoApplication) context.getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
applicationComponent = DaggerApplicationComponent
.builder()
.applicationModule(new ApplicationModule(this))
.build();
applicationComponent.inject(this);
}
public ApplicationComponent getComponent(){
return applicationComponent;
}
}
添加这个类到Add this class in AndroidManifest.xml
:
<application
...
android:name=".DemoApplication"
...
</application>
这个类通过DI获取了Datamanager
对象,最有趣的部分是ApplicationComponent
。但是我们得一会再讨论它。
步骤八
创建MainActivity
public class MainActivity extends AppCompatActivity {
@Inject
DataManager mDataManager;
private ActivityComponent activityComponent;
private TextView mTvUserInfo;
private TextView mTvAccessToken;
public ActivityComponent getActivityComponent() {
if (activityComponent == null) {
activityComponent = DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this))
.applicationComponent(DemoApplication.get(this).getComponent())
.build();
}
return activityComponent;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getActivityComponent().inject(this);
mTvUserInfo = (TextView) findViewById(R.id.tv_user_info);
mTvAccessToken = (TextView) findViewById(R.id.tv_access_token);
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
createUser();
getUser();
mDataManager.saveAccessToken("ASDR12443JFDJF43543J543H3K543");
String token = mDataManager.getAccessToken();
if(token != null){
mTvAccessToken.setText(token);
}
}
private void createUser(){
try {
mDataManager.createUser(new User("Ali", "1367, Gurgaon, Haryana, India"));
}catch (Exception e){e.printStackTrace();}
}
private void getUser(){
try {
User user = mDataManager.getUser(1L);
mTvUserInfo.setText(user.toString());
}catch (Exception e){e.printStackTrace();}
}
}
同样创建activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_user_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:padding="10dp"/>
<TextView
android:id="@+id/tv_access_token"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:padding="10dp"/>
</LinearLayout>
现在来复习一下Daggar2:
- 为了给一个类提供依赖,,我们不得不创建一个Module类。这个类定义了提供依赖的方法。一个Module类被
@Module
注解标注,它提供依赖的方法被@ProViders
注解所标注。 - 然后,我们必须提供一个类来作为一个桥梁,连接一个被
@Inject
注解所标注的作为依赖的类和一个被@Module
注解所标注的提供依赖的类。 - 为了指出一个Module要提供的依赖,我们必须扫描依赖图中国所有 提供了关系依赖的类,然后找出至少需要提供的依赖对象数量。
现在,根据上面的例子来理解刚才的这三段话。
步骤九
我们不得不提供被注解DemoApplication
所标注的依赖。这个类需要DataManager
,为了得到这个类,我们不得不提供被DataManager
注解所标注的依赖,如前所述构造函数中的Context
、DbHelper
和SharedPrefsHelper
。
-
Context
必须是ApplicationContext
-
DbHelper
需要Context
,dbName
和version
-
SharedPrefsHelper
需要SharedPreferences
现在累计一下所需要的依赖的超集,包括Context
、DbHelper
,SharedPreferences
和SharedPrefsHelper
。
现在,创建ApplicationModule
来提供这些依赖:
@Module
public class ApplicationModule {
private final Application mApplication;
public ApplicationModule(Application app) {
mApplication = app;
}
@Provides
@ApplicationContext
Context provideContext() {
return mApplication;
}
@Provides
Application provideApplication() {
return mApplication;
}
@Provides
@DatabaseInfo
String provideDatabaseName() {
return "demo-dagger.db";
}
@Provides
@DatabaseInfo
Integer provideDatabaseVersion() {
return 2;
}
@Provides
SharedPreferences provideSharedPrefs() {
return mApplication.getSharedPreferences("demo-prefs", Context.MODE_PRIVATE);
}
}
注:我们使用了@Module
标注了该类和使用了@Providers
来标注了他的提供依赖的方法
关于这个类:
- 在这个构造方法,我们已经有了一个
Application
实例,这个实例被用来协助提供其他依赖实例; - 这个类提供了我们在上面步骤中列举的所有依赖。
步骤十
我们创建一个Component
来链接DemoApplication
依赖和ApplicationModule
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
void inject(DemoApplication demoApplication);
@ApplicationContext
Context getContext();
Application getApplication();
DataManager getDataManager();
SharedPrefsHelper getPreferenceHelper();
DbHelper getDbHelper();
}
注:ApplicationComponent
是一个被Daggar2实现的接口,我们区分一个Component类就是看看是否被@Component
注解所标注。
这里我们写了一个inject
方法,携带参数DemoApplication
。为什么要这样做呢?
当通过字段注入,即@Inject
注解使用在成员变量,提供依赖时,我们必须告诉Daggar通过这个接口的实现去扫描这个类。
这个类还提供了用于访问依赖图中已经存在的依赖关系的方法。
步骤十一
同样地,我们不得不为MainActivity
创建一个Module
和对应的Compoonent
@Module
public class ActivityModule {
private Activity mActivity;
public ActivityModule(Activity activity) {
mActivity = activity;
}
@Provides
@ActivityContext
Context provideContext() {
return mActivity;
}
@Provides
Activity provideActivity() {
return mActivity;
}
}
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
void inject(MainActivity mainActivity);
}
注:ActivityComponent
指定了ApplicationComponent
和ActivityModule
.ApplicationComponent
被添加来使用前面步骤中已经生成的或者是存在的依赖图,因为DemoApplication
在Application运行之前都会一直存在???
@PerActivity
被用来告诉Daggar Context
和Activity
是通过ActivityModule
提供的,Activity
每被创建一次便会初始化一次,所以,这些对象会被保存在整个Activity的生命周期中,且每一个Activity对象都持有一份。
我们可能会问DataManager
会随Activity
的创建而创建。但是这是不对的,因为DataManager
是被@Singleton
注解所标注的。
让我们再次回到DemoApplication
和MianActivity
类。我们会在onCreate
方法中取得依赖。
applicationComponent = DaggerApplicationComponent
.builder()
.applicationModule(new ApplicationModule(this))
.build();
applicationComponent.inject(this);
DaggerApplicationComponent
是Daggar自动生成的,实现了ApplicationComponent
。我们提供了ApplicationModule
去创建需要的依赖。
我们也通过DemoApplication
去调用了applicationComponent
的inject
方法,这样做是为了用它来提供DataManager
,ApplicationComponent
实例会被保存起来,用作访问以来途中所有类的入口。
public ActivityComponent getActivityComponent() {
if (activityComponent == null) {
activityComponent = DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this))
.applicationComponent(DemoApplication.get(this).getComponent())
.build();
}
return activityComponent;
}
类似的过程也被用于MainActivity
,唯一不同的是,我们还提供了ActivityModule
中提及的需要依赖解析的ApplicationComponent
。
网友评论