Dagger2 是如何實現依賴注入的?

Dagger2流行已經有一段時間了,是一個很強大的依賴注入框架。以前接觸過Spring的IOC,瞭解過它的實現原理是用反射實現的。而移動端是對資源很敏感的,Dagger2作爲移動端的一個主流框架,肯定不會用一樣的套路去玩。所以抽空研究了一下它的實現方式。

從一個最簡單的例子入手

public class ActivityLogin extends Activity implements ILoginView {
	
	    private static final String TAG = "ActivityLogin_";
	    @Inject
	    LoginPresenter loginPresenter;
	
	    Button loginBtn;
	
	    @Override
	    protected void onCreate(Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        setContentView(R.layout.activity_main2);
	        DaggerMyComponent.create().inject(this);
	        loginPresenter.attachView(this);
	        loginBtn = findViewById(R.id.btn_login);
	        loginBtn.setOnClickListener(new View.OnClickListener() {
	            @Override
	            public void onClick(View v) {
	                loginPresenter.login();
	            }
	        });
	    }
	
	    @Override
	    public void onLoginSuccess() {
	        Log.i("MyModule", TAG + "LoginSuccess_");
	        Toast.makeText(this, "ActivityLogin onLoginSuccess", Toast.LENGTH_LONG).show();
	    }
	
	    @Override
	    public void onLoginError(String errorMsg) {
	
	    }
	}

上面的例子是一個MVP的例子:要在一個Activity中注入一個LoginPresenter(在上面加了@Inject 註解)。來看LoginPresenter:

	public class LoginPresenter extends BasePresenter<ILoginView> {
	    private static final String TAG = "LoginPresenter";
	    @Inject
	    public LoginPresenter() {
	
	    }
	
	    @Override
	    public void onDetach() {
	        Log.i("MyModule", TAG+" "+hashCode()+" detach_" + getActivityNameBelongTo());
	    }
	
	    @Override
	    public void onAttach() {
	        Log.i("MyModule", TAG+" "+hashCode()+" attach_" + getActivityNameBelongTo());
	    }
	
	    public void login() {
	        Log.i("MyModule",TAG+" http request ...");
	        getView().onLoginSuccess();
	    }
	}

需要注入的類,LoginPresenter的構造函數上也加了@Inject註解。
然後看Component:

@Component()
	public interface MyComponent {
	    void inject(ActivityLogin activityLogin);
	}

可以看到MyComponent是個接口,@Component註解是可以帶參數的(Class<?>[] 數組),裏面指定了依賴要從哪裏拿,這個例子默認都由Dagger2生成依賴。

然後發現ActivityLogin中,我們想要的實例LoginPresenter loginPresenter 就被生成了。

那它是怎麼做到的呢?依賴的對象實例究竟是從哪裏來的呢?

看看生成的文件

工程的目錄結構:
build/generate/source/apt/debug/xxx(包名)下的結構:
可以看到,Dagger2幫我們生成了幾個文件:
生成的目錄結構

  1. 被注入依賴類,也就是ActivityLogin 對應一個ActivityLogin_MembersInjector類,以“XXX_MembersInjector”命名(XXX是被注入依賴類的類名),這個類會放在和ActivityLogin同一個包目錄下。
  2. 依賴的類,也就是LoginPresenter ,對應一個LoginPresenter_Factory類,以“XXX_Factory”命名(XXX是依賴的類的類名,放在了LoginPresenter同一個包目錄下。
  3. Component註解接口對應的一個類,以“DaggerXXX”命名(XXX師Component註解對應的接口類名),同樣放在了對應的目錄下。

我們使用的時候是DaggerXXXX.creat().inject(this),看下這個生成的DaggerXXXX:

public final class DaggerMyComponent implements MyComponent {
	  private Provider<LoginPresenter> loginPresenterProvider;
	
	  private MembersInjector<ActivityLogin> activityLoginMembersInjector;
	
	  private DaggerMyComponent(Builder builder) {
	    assert builder != null;
	    initialize(builder);
	  }
	
	  public static Builder builder() {
	    return new Builder();
	  }
	
	  public static MyComponent create() {
	    return builder().build();
	  }
	
	  @SuppressWarnings("unchecked")
	  private void initialize(final Builder builder) {
	
	    this.loginPresenterProvider =
	        LoginPresenter_Factory.create(MembersInjectors.<LoginPresenter>noOp());
	
	    this.activityLoginMembersInjector =
	        ActivityLogin_MembersInjector.create(loginPresenterProvider);
	  }
	
	  @Override
	  public void inject(ActivityLogin activityLogin) {
	    activityLoginMembersInjector.injectMembers(activityLogin);
	  }
	
	  public static final class Builder {
	    private Builder() {}
	
	    public MyComponent build() {
	      return new DaggerMyComponent(this);
	    }
	  }
	}

這種情況是最簡單的(依賴的類LoginPresenter沒有其他依賴),所以這時候分析文件結構最清晰。
貼出來LoginPresenter_Factory:

public final class LoginPresenter_Factory implements Factory<LoginPresenter> {
	  private final MembersInjector<LoginPresenter> loginPresenterMembersInjector;
	
	  public LoginPresenter_Factory(MembersInjector<LoginPresenter> loginPresenterMembersInjector) {
	    assert loginPresenterMembersInjector != null;
	    this.loginPresenterMembersInjector = loginPresenterMembersInjector;
	  }
	
	  @Override
	  public LoginPresenter get() {
	    return MembersInjectors.injectMembers(loginPresenterMembersInjector, new LoginPresenter());
	  }
	
	  public static Factory<LoginPresenter> create(
	      MembersInjector<LoginPresenter> loginPresenterMembersInjector) {
	    return new LoginPresenter_Factory(loginPresenterMembersInjector);
	  }
	}

可以看到LoginPresenter_Factory 會有一個MembersInjector 成員,這個成員是構造函數依賴進來的;並且get方法裏,有new的動作,也就是說,如果依賴的類是無參構造,最後依賴對象的實例化是在依賴對應的Factory中生成的

看ActivityLogin_MembersInjector:

public final class ActivityLogin_MembersInjector implements MembersInjector<ActivityLogin> {
	  private final Provider<LoginPresenter> loginPresenterProvider;
	
	  public ActivityLogin_MembersInjector(Provider<LoginPresenter> loginPresenterProvider) {
	    assert loginPresenterProvider != null;
	    this.loginPresenterProvider = loginPresenterProvider;
	  }
	
	  public static MembersInjector<ActivityLogin> create(
	      Provider<LoginPresenter> loginPresenterProvider) {
	    return new ActivityLogin_MembersInjector(loginPresenterProvider);
	  }
	
	  @Override
	  public void injectMembers(ActivityLogin instance) {
	    if (instance == null) {
	      throw new NullPointerException("Cannot inject members into a null reference");
	    }
	    instance.loginPresenter = loginPresenterProvider.get();
	  }
	
	  public static void injectLoginPresenter(
	      ActivityLogin instance, Provider<LoginPresenter> loginPresenterProvider) {
	    instance.loginPresenter = loginPresenterProvider.get();
	  }
	}

ActivityLogin_MembersInjector 會有一個Provider 的成員,這個成員是構造的參數,並且injectMembers方法持有被注入依賴類(ActivityLogin)的實例引用,**instance.loginPresenter = loginPresenterProvider.get()**真正做了“橋樑”,爲ActivityLogin中的loginPresenter賦值。
看個UML圖:
UML圖
##梳理一下幾個類的關係

簡單的總結就是,DaggerMyComponent通過Factory獲取了Provider包裝的依賴的對象(Provider的實例)然後用它生成了ActivityLogin_MembersInjector的實例,最後這個實例包含了被依賴和依賴對象的引用,就可以完成搭橋了。
看一下關係圖
關係圖

給LoginPresenter加依賴

 @Inject
    public LoginPresenter(Dependence1 dependence1) {
	
    }

如果這時候什麼都不做,編譯的時候會報錯:
這是因爲Dagger2並不知道Dependence1 的實例要從哪裏來。對Dependence1做處理:

	public class Dependence1 {
	    @Inject
	    public Dependence1(){}
	}

給構造函數加Inject註解。
描述下變化:

  • 對應目錄下多了一個Dependence1_Factory的類:
	public enum Dependence1_Factory implements Factory<Dependence1> {
		  INSTANCE;
			
		  @Override
		  public Dependence1 get() {
		    return new Dependence1();
		  }
			
		  public static Factory<Dependence1> create() {
		    return INSTANCE;
		  }
	}

這是個枚舉單例,實現了Factory。可以看到,這裏對Dependence1做了實例化(new 動作)此時 DaggerMyComponent中的initalize方法:

private void initialize(final Builder builder) {

    this.loginPresenterProvider =
        LoginPresenter_Factory.create(
            MembersInjectors.<LoginPresenter>noOp(), Dependence1_Factory.create());

    this.activityLoginMembersInjector =
        ActivityLogin_MembersInjector.create(loginPresenterProvider);
  }

對比之前,就是LoginPresenter_Factory.create方法多加了一個參數,這個參數由Dependence1_Factory提供。這樣可能還是有些邏輯不清楚,稍做改動:

private void initialize(final Builder builder) {

   this.activityLoginMembersInjector =
        ActivityLogin_MembersInjector.create(LoginPresenter_Factory.create(
                MembersInjectors.<LoginPresenter>noOp(), Dependence1_Factory.create()));
  }

這樣是不是可以看出一些層級關係?

對應的示意圖:
單個被注入類層級關係示意圖

再做一次抽象:
抽象層級
很明顯這是個樹形圖。
之所以Dagger2生成的文件不是我改動後的這種,是因爲每個生成的Provider,都有可能被其他MemberInjector複用。我只是爲了讓關係層級更清晰。
所以基本可以看出:

  1. DaggerXXX 實現了 標註了@Component 的接口XXX,也就說有了要Inject的宿主類;
  2. 生成的DaggerXXX類中會持有所有的注入依賴類對象的Provider和被注入對象的MemberInjector;
  3. 所有的提供依賴的類都會被包裝成Provider,所有需要注入依賴的類都被包裝成MemberInjector;
  4. 需要注入依賴的MemberInjector會通過create方法把所有依賴的Provider傳進去,有幾個依賴,create就有幾個參數;
  5. 所有Provider的實例都是通過對應的Factory提供的。
  6. 最後的inject其實是MembersInjector代理的。

小結

這只是一個最簡單的使用Dagger2的例子,這樣可以很清晰的搞清楚Dagger2爲我們生成這麼多文件它們之間的關係。後面會把其他的註解也分析,整體的分析思路是一樣的。這一趟分析個人感覺有幾個很nice的設計思路:

  • @Inject 修飾的成員不能是private的,因爲生成的橋樑類MemberInjector和被注入類在一個包目錄下面,private是不能訪問的。放在含有註解的類所在包目錄下面,可以最大程度保護成員的訪問權限,不然只能是public;

  • DaggerXXX用了外觀模式,拿到了本來只能在包目錄下才能訪問的依賴實例;

  • DaggerXXX實現了標註了@Component 的接口,這個接口指定了哪些類是需要注入依賴的類,所以我們在要注入依賴的類裏使用DaggerXXX.create().inject(this)才這麼方便。

使用的設計模式: 外觀模式、 工廠模式、 單例模式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章