承接《Unity 1.2 依賴注入實戰(一)》,本文將深入探討如何透過 XML 組態檔來配置 Unity 容器,實現型別對應的外部化管理,避免硬式編碼帶來的維護困擾。
沿用上篇的介面與實作類別:
public interface ILogService {
void Write(string message);
}
public class CnsLogService : ILogService {
public void Write(string message) {
Console.WriteLine($"Cns-exception msg:{message}");
}
}
public class DataLogService : ILogService {
public void Write(string message) {
DailyPractice.Utility.Log.AddLog(message);
}
}
namespace DailyPractice.Utility {
public class Log {
public static void AddLog(string message) {
Console.WriteLine($"Data-exception msg:{message}");
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity>
<containers>
<container name="One">
<types>
<type type="DailyPractice.UnityEx.ILogService, DailyPractice.UnityEx"
mapTo="DailyPractice.UnityEx.DataLogService, DailyPractice.UnityEx" />
</types>
</container>
</containers>
</unity>
</configuration>
<configSections>type 屬性固定指向 UnityConfigurationSection。<containers> / <container>name 屬性為選填,用於識別多容器場景。<types> / <type>type 指定介面或基底類別,mapTo 指定具體實作類別。根據官方文件:type 元素定義容器的型別對應。若指定 name 則建立具名對應,否則建立預設對應。可為每個對應指定生命週期管理器,預設為 transient(瞬時模式,每次解析建立新實例)。
public static void Main() {
IUnityContainer myContainer = new UnityContainer();
UnityConfigurationSection section =
(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers[0].Configure(myContainer);
ILogService myServiceInstance = myContainer.Resolve<ILogService>();
myServiceInstance.Write("oh,exception occured!");
}
執行結果將呼叫 DataLogService.Write,輸出 Data-exception msg:oh,exception occured!。
修改 mapTo 指向 CnsLogService,重新執行即可切換行為,無需改動程式碼。
<types>
<type type="DailyPractice.UnityEx.ILogService, DailyPractice.UnityEx"
name="cnslogger"
mapTo="DailyPractice.UnityEx.CnsLogService, DailyPractice.UnityEx" />
<type type="DailyPractice.UnityEx.ILogService, DailyPractice.UnityEx"
name="datalogger"
mapTo="DailyPractice.UnityEx.DataLogService, DailyPractice.UnityEx" />
</types>
關鍵點:必須指定 name 屬性區分不同對應,否則 Unity 內部 GetElementKey 會產生相同鍵值導致例外:
The entry ':DailyPractice.UnityEx.ILogService, DailyPractice.UnityEx' has already been added.
section.Containers[0].Types[0].Configure(myContainer);
section.Containers[0].Types[1].Configure(myContainer);
IEnumerable<ILogService> myServiceInstances = myContainer.ResolveAll<ILogService>();
foreach (var svc in myServiceInstances) {
svc.Write("haha, you have an exception 了吧!");
}
輸出將包含兩個實作的寫入結果。
Unity 組態區塊內部透過 UnityTypeElement 的 GetElementKey 產生唯一鍵:
protected override object GetElementKey(ConfigurationElement element) {
var typeElement = (UnityTypeElement)element;
if (typeElement.Name == null) {
return typeElement.TypeName; // 無 name 時僅用型別名稱
}
return typeElement.Name + ":" + typeElement.TypeName; // 有 name 時組合
}
因此,同一介面的多重對應必須指定 name 避免鍵值衝突。
name 屬性是多重對應的關鍵,亦是 ResolveAll 正常運作的前提