原文鏈接:http://docs.autofac.org/en/latest/register/registration.html 所謂註冊組件,是指創建 ContainerBuilder 的實例,並告訴它哪些組件暴露哪些服務。 組件可以用反射創建,可以提供已經創建好的對象的實例,還可以用拉姆達表達
原文鏈接:http://docs.autofac.org/en/latest/register/registration.html
所謂註冊組件,是指創建 ContainerBuilder 的實例,並告訴它哪些組件暴露哪些服務。
組件可以用反射創建,可以提供已經創建好的對象的實例,還可以用拉姆達表達式創建。ContainerBuilder 有一組 Register 方法來進行裝配。
每個組件暴露一到多個服務,這些服務用生成器的 As 方法連接起來。
// 創建生成器,生成器用來註冊組件和服務 var builder = new ContainerBuilder(); // 註冊暴露介面的類型 builder.RegisterType<ConsoleLogger>().As<ILogger>(); // 註冊已存在的對象實例 var output = new StringWriter(); builder.RegisterInstance(output).As<TextWriter>(); // 註冊創建對象的表達式 builder.Register(c => new ConfigReader("mysection")).As<IConfigReader>(); // 生成容器,完成註冊,準備解析對象 var container = builder.Build(); // 現在可以用 Autofac 解析服務,例如, // 這行代碼將執行拉姆達表達式解析 IConfigReader 服務 using(var scope = container.BeginLifetimeScope()) { var reader = container.Resolve<IConfigReader>(); }
反射組件
用類型註冊
由反射生成的組件通常按類型註冊:
var builder = new ContainerBuilder(); builder.RegisterType<ConsoleLogger>(); builder.RegisterType(typeof(ConfigReader));
使用基於反射的組件時,Autofac 選取可用參數最多的構造函數。比如,一個類有三個構造函數:
public class MyComponent { public MyComponent() { /* ... */ } public MyComponent(ILogger logger) { /* ... */ } public MyComponent(ILogger logger, IConfigReader reader) { /* ... */ } }
並使用以下代碼註冊:
var builder = new ContainerBuilder(); builder.RegisterType<MyComponent>(); builder.RegisterType<ConsoleLogger>().As<ILogger>(); var container = builder.Build(); using(var scope = container.BeginLifetimeScope()) { var component = container.Resolve<MyComponent>(); }
解析時,Autofac 發現 ILogger 已註冊,但 IConfigReader 未註冊。於是選取第二個構造函數,因為它是可從容器獲取參數最多的構造函數。
重要說明: 通過 RegisterType 註冊的組件必須是具體類型。組件可以將抽象類和介面暴露為服務,但不能把抽象類和介面註冊為組件。Autofac 要創建組件的實例,抽象類和介面不能實例化。
指定構造函數
註冊組件時,通過使用 UsingConstructor 方法並指定構造函數的參數類型列表,可以選擇特定的構造函數,這將覆蓋自動選擇:
builder.RegisterType<MyComponent>() .UsingConstructor(typeof(ILogger), typeof(IConfigReader));
註意,解析時必須保證參數可用,否則將出現錯誤。參數既可以在註冊時傳遞,也可以在解析時傳遞。
實例組件
可使用 RegisterInstance 方法把預先生成的實例註冊到容器:
var output = new StringWriter(); builder.RegisterInstance(output).As<TextWriter>();
由於 Autofac 會自動清理對象,如果想自己控制對象的生命周期,而不是由 Autofac 調用 Dispose,就需要用 ExternallyOwned 方法來註冊實例:
var output = new StringWriter(); builder.RegisterInstance(output) .As<TextWriter>() .ExternallyOwned();
將Autofac 集成到現有程式時,註冊實例是一個技巧。組件可能會用到單例模式提供的服務,與其直接引用單件,不如將單件註冊到容器:
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();
這樣就消除了組件對單件的引用,改由容器提供。
實例暴露的預設服務是實例的具體類型,參考“服務和組件”一節。
拉姆達表達式組件
反射是創建組件的好方式。但是,如果創建邏輯超出簡單的調用構造函數時,反射就不夠用了。
Autofac 可以接受一個創建組件的委托或拉姆達表達式:
builder.Register(c => new A(c.Resolve<B>()));
參數 c 是組件上下文(IComponentContext),組件在此上下文中註冊。可以用它從容器解析出其他值,來輔助創建組件。應使用組件上下文,而不是閉包來訪問容器,這很要緊,只有這樣資源清理和嵌套容器才不會出問題。
額外的依賴可以用此上下文參數來滿足,例如, A的構造函數需要B類型的參數,並且 B 可能有其他依賴項。
表達式創建的組件暴露的預設服務是從表達式推斷出的返回類型。
以下是反射方式不能勝任,但拉姆達表達式工作良好的例子。
複雜參數
構造函數參數不能總是聲明為簡單常量。與其為使用 XML 配置語法創建特定類型的值而大傷腦筋,不如使用類似的代碼:
builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));
(當然, session 過期時間在配置文件中更好 – 這裡只是說明個大概)
屬性註入
儘管有更好的屬性註入方式,仍然可以使用表達式和屬性初始化器來組裝屬性:
builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() });
ResolveOptional 方法嘗試解析值,即使服務沒有註冊,也不會拋出異常。(如果服務已註冊但不能正確解析仍然會拋出異常) 這是解析服務的選項之一。
多數情況下不推薦屬性註入。如果組件有可選的依賴項,通過空對象模式, 重載構造函數,或構造函數參數預設值等替代方案,就可以用構造函數註入的方式來創建整潔的,“穩定的(immutable)”組件。
通過參數值選擇實現類
把組件的創建動作隔離出來後,依賴項的具體類型可以變換,這是一個很大的好處。變換通常在運行時完成,而不單是配置時:
builder.Register<CreditCard>( (c, p) => { var accountId = p.Named<string>("accountId"); if (accountId.StartsWith("9")) { return new GoldCard(accountId); } else { return new StandardCard(accountId); } });
本例,CreditCard 有兩個實現類,GoldCard 和 StandardCard,使用哪個類是在運行時由 accountId 決定的。
例子里的第二個參數名為 p,這是可選參數。
解析組件:
var card = container.Resolve<CreditCard>(new NamedParameter("accountId", "12345"));
聲明創建CreditCard 實例的委托,使用委托工廠,可以得到更整潔,類型安全的語法。
開放式泛型組件
Autofac 支持開放式泛型類型。使用 RegisterGeneric 生成器方法進行註冊:
builder.RegisterGeneric(typeof(NHibernateRepository<>)) .As(typeof(IRepository<>)) .InstancePerLifetimeScope();
從容器請求匹配的服務類型時,Autofac 映射到等價的閉合版本:
// Autofac 返回 NHibernateRepository<Task> var tasks = container.Resolve<IRepository<Task>>();
註冊的特定服務類型 (如IRepository<Person>)會覆蓋開放式泛型版本。
服務和組件
註冊組件時必須告訴 Autofac 它暴露哪些服務。預設情況下,暴露的服務是組件自身的類型:
// 服務是 "CallLogger" builder.RegisterType<CallLogger>();
組件僅可通過它暴露的服務來解析。對於本例來說:
// 沒問題 scope.Resolve<CallLogger>(); // 有問題,因為註冊時沒有將 ILogger 介面設置為組件的服務 scope.Resolve<ILogger>();
可以讓組件暴露多個服務:
builder.RegisterType<CallLogger>() .As<ILogger>() .As<ICallInterceptor>();
暴露服務後,就可以通過它解析組件。註意,將組件暴露為特定的服務後,預設服務(組件類型)會被覆蓋:
// 以下均可工作: scope.Resolve<ILogger>(); scope.Resolve<ICallInterceptor>(); // 但這一行不再有用,因為指定的服務覆蓋了組件類型 scope.Resolve<CallLogger>();
使用AsSelf 方法,可在暴露其他服務的同時也將自身類型暴露為服務:
builder.RegisterType<CallLogger>() .AsSelf() .As<ILogger>() .As<ICallInterceptor>();
這樣全部代碼都可工作:
// 註冊時暴露了合適的服務,因此都起作用 scope.Resolve<ILogger>(); scope.Resolve<ICallInterceptor>(); scope.Resolve<CallLogger>();
預設註冊
如果多個組件暴露相同的服務,Autofac 將使用最後註冊的組件作為服務的預設提供程式:
builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>();
在此場景中,FileLogger 是 ILogger 的預設組件,因為它是最後註冊的。
使用 PreserveExistingDefaults 方法可以覆蓋這個行為:
builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>().PreserveExistingDefaults();
這時,ConsoleLogger 將是預設的 ILogger 因為後面註冊的 FileLogger 使用了 PreserveExistingDefaults()。
配置
可使用XML 配置或編程配置(“modules”)成組提供註冊信息,或在運行時更改註冊信息。 Autofac modules 還可實現註冊信息動態生成,或實現條件註冊邏輯。
動態註冊
Autofac modules 是引入動態註冊邏輯或簡單正交特性的最簡單方式。例如,將 log4net logger 實例動態附加到正在解析的服務。
如果需要更加動態的行為,比如添加隱式關聯類型支持,請參考check out the registration sources section in the advanced concepts area.