Unity 初始化主要是注冊類型映射並指定其生命周期。
在本文中,我們使用了一個接口 IDialer、一個實現了接口的抽象基類 Dialer,二個繼承自 Dialer 的具體類 ButtonTypeDialer 和 FigurePlateDialer 類,以及一個使用 Dialer 的 Telephone 類。
生命周期管理
之所以將生命周期的管理放在開始,是因為Unity 會根據在類型的注冊時需要指定的生命周期來管理對象的創建和解析。
Unity 使用繼承自 LifetimeManager 基類的類來決定如何保存對象實例的引用,以及如何銷毀對象。Unity 自帶了二個用於生命周期管理的類:ContainerControlledLifetimeManager 和 ExternallyControlledLifetimeManager ,下面分別對這二個類進行描述。如果指定了這二種管理方式,無論是以 RegisterType 還是以 RegisterInstance 注冊的類型,都將處理成單件對象。
ContainerControlledLifetimeManager 類是指定 Unity 容器管理注冊的對象,即對象的生命周期與 Unity 容器一致,在銷毀 Unity 時將銷毀所有其管理的對象。同時,
ExternallyControlledLifetimeManager 類指定 Unity 僅保持對象的弱引用,對象的保存和銷毀由 Unity 容器外部來控制。
如果不需要處理成單件對象,可以在調用 RegisterType 方法時不指定其 LifetimeManager 類,這樣 Unity 容器將使用臨時生命周期管理,即為每次對獲取對象的請求都創建一個新的實例。
用 RegisterType 注冊類型映射
RegisterType 包含多個支持泛型的重載,同時還包含了一一對應的非泛型重載。在此僅對其泛型重載進行闡述,非泛型重載與其對應。
RegisterType 注冊類型映射可以是注冊接口或基類所對應的類型,也可以直接注冊類型。對於前者,在此僅給出接口或基類注冊的一種,另一種直接替換即可。
RegisterType<TFrom, TTo>( )
此方法注冊一個默認的類型映射,並且為獲取對象的每次請求創建一個新的實例。例如如下代碼:
在上例中的代碼中,前二行即是對類型的注冊。運行此代碼,結果如下:
而這種注冊的對應的配置文件如下:
同時,將上面的代碼修改為如下代碼:
運行此代碼,我們將得到同樣的結果。
RegisterType<TFrom, TTo>(LifetimeManager lifetime)
此方法與上一方法的不同之處在於可以指定生命周期的管理。例如,我們有如下代碼:
我們將可以得到如下結果:
可以看出,在使用 ContainerControlledLifetimeManager 之後,二次獲取的都是同一對象,即 Unity 容器在第一次獲取時創建了一個對象後就將其保存了下來供後繼獲取使用。
然後,我們將這幾行語句拆成二個方法,如下所示:
這時,我們可以得到與前面同樣的結果,讀者可以將其中的 ContainerControlledLifetimeManager 類替換成 ExternallyControlledLifetimeManager 類檢查這二個類所產生的不同效果。
如果要使用配置文件來完成注冊,我們可以使用如下配置:
同時,我們需要將上面的第 64 行代碼修改第22、23行代碼以應用配置文件。
RegisterType<TFrom, TTo>(String name)
此方法用於注冊一個命名的類型映射,而不是默認的類型注冊。如果 name 為 null,其作用將與 RegisterType<TFrom, TTo>( ) 方法相同。示例代碼如下:
從上面的代碼可以看出,此方法的不同點在於在獲取對象的時候需要指定其類型注冊的名稱。相應的配置文件如下:
RegisterType<TFrom, TTo>(String name, LifetimeManager lifetime)
此方法是前面三種方法的組合,在此不再詳述。
其他
其他方法還包括 RegisterType<T>(LifetimeManager lifetime)、RegisterType<T>(String name, LifetimeManager lifetime)等方法,使用方法與前面類似,不再詳述。
用 RegisterInstance 注冊已存在的對象實例
RegisterInstance 用於將一個已創建的對象實例注冊成單件類型映射,默認情況下,使用 ContainerControlledLifetimeManager 生命周期管理。
RegisterInstance<T>(T instance)
此方法注冊一個默認的單件類型映射。示例代碼如下:
在此示例中,我們先創建了一個 ButtonTypeDialer 類型的對象,然後將其注冊為 Dialer 類型的映射,然後通過 Unity 來獲取它。
與 RegisterType 不同的是,它的配置文件如下所示:
在配置文件中,我們使用了一個 DialerConverter 類,這是一個類型轉換類。在 Instances 元素中 value 元素是必須的,如果要創建一個自定義類型的實例,就必須有一個合適的類型轉換類來將字段串表示的 value 值轉換成需要的類型。相應的,我們的代碼修改如下:
注意:在Resolve 方法中指定的類型必須與配置文件中的類型一致,否則會觸發異常。
RegisterInstance<TInterface>(TInterface instance, LifetimeManager lifetime)
此方法在注冊一個已有對象的實例的類型映射的同時指定一個生命周期管理。示例代碼如下:
大家可以將 ContainerControlledLifetimeManager 替換成 ExternallyControllerLifetimeManager 試試,這時會出現異常。
這個方法對應的配置文件是,遺憾的是,配置文件不支持此方法的生命周期的配置。
其他
其他還有 RegisterInstance<TInterface>(String name, TInterface instance) 和 RegisterInstance<TInterface>(String name, TInterface instance, LifetimeManager lifetime) 等方法,大家可以參考 RegisterType 方法進行學習。
Unity 容器的層次
Unity 容器支持容器的嵌套,那麼在什麼情況下需要使用容器的嵌套呢。如針對同一類型注冊多個類型映射,類型映射需要有不同的生命周期管理時,都需要使用嵌套。下面僅對同一類型注冊多個類型映射的情況進行解釋。
示例代碼如下:
運行代碼,我們可以得到如下的結果:
可能有人會想,這樣還不如直接創建二個容器,但之所以這麼做,是為了所有對象都可以在父容器中進行管理,而且,子容器多用於管理一些臨時性的映射。另外,可以在子容器使用在你父容器中注冊的映射,也就是說,容器會自己向上搜索注冊。
Unity 的配置文件並不直接支持容器的嵌套,這時,我們可以通過在配置文件中將父容器定義成默認的容器,而將子容器配置成命名的容器,然後將命名配置應用到子容器上即可。例如,我們有如下配置:
通過將代碼修改如下,我們將可以得到同樣的結果:
小結
通過上面的學習,我們已經全面了解了 Unity 依賴注入容器的初始化,在經過這些不同的注冊後,我們將可以通過不同的方式獲取對象,使用對象。
本文配套源碼