1. 程式人生 > >Autofac 組件、服務、自動裝配 《第二篇》

Autofac 組件、服務、自動裝配 《第二篇》

信息 turn instance 並且 排除 pre 準備 輸出 服務註冊

Autofac 組件、服務、自動裝配 《第二篇》

轉自 :https://www.cnblogs.com/kissdodog/p/3611799.html

一、組件

  創建出來的對象需要從組件中來獲取,組件的創建有如下4種(延續第一篇的Demo,僅僅變動所貼出的代碼)方式:

  1、類型創建RegisterType

  AutoFac能夠通過反射檢查一個類型,選擇一個合適的構造函數,創造這個對象的實例。主要通過RegisterType<T>() 和 RegisterType(Type) 兩個方法以這種方式建立。

  ContainerBuilder使用 As() 方法將Component封裝成了服務使用。

    builder.RegisterType<AutoFacManager>();
    builder.RegisterType<Worker>().As<IPerson>();

  2、實例創建

  builder.RegisterInstance<AutoFacManager>(new AutoFacManager(new Worker()));

  單例

  提供示例的方式,還有一個功能,就是不影響系統中原有的單例:

  builder.RegisterInstance(MySingleton.GetInstance()).ExternallyOwned();  //將自己系統中原有的單例註冊為容器托管的單例

  這種方法會確保系統中的單例實例最終轉化為由容器托管的單例實例。

  3、Lambda表達式創建

  Lambda的方式也是Autofac通過反射的方式實現

    builder.Register(c => new AutoFacManager(c.Resolve<IPerson>()));
    builder.RegisterType<Worker>().As<IPerson>();

  4、程序集創建

  程序集的創建主要通過RegisterAssemblyTypes()方法實現,Autofac會自動在程序集中查找匹配的類型用於創建實例。

    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()); //在當前正在運行的程序集中找
    builder.RegisterType<Worker>().As<IPerson>();

  5、泛型註冊

  泛型註冊通過RegisterGeneric() 這個方法實現,在容易中可以創建出泛型的具體對象。

    //泛型註冊,可以通過容器返回List<T> 如:List<string>,List<int>等等
    builder.RegisterGeneric(typeof(List<>)).As(typeof(IList<>)).InstancePerLifetimeScope();
    using (IContainer container = builder.Build())
    {
        IList<string> ListString = container.Resolve<IList<string>>();
    } 

  6、默認的註冊

  如果一個類型被多次註冊,以最後註冊的為準。通過使用PreserveExistingDefaults() 修飾符,可以指定某個註冊為非默認值。

技術分享圖片
    ContainerBuilder builder = new ContainerBuilder();
    builder.RegisterType<AutoFacManager>();
    builder.RegisterType<Worker>().As<IPerson>();
    builder.RegisterType<Student>().As<IPerson>().PreserveExistingDefaults();   //指定Student為非默認值
    using (IContainer container = builder.Build())
    {
        AutoFacManager manager = container.Resolve<AutoFacManager>();
        manager.Say();  //輸出我是一個工人
    } 
技術分享圖片

  如果不使用PreserveExistingDefaults(),那麽將輸出“我是一個學生”。

二、服務

  Autofac有三種典型的方式區分服務,同一個服務的不同實現可以由類型,名稱和鍵區分。

  1、類型

  類型是描述服務的基本方法

  builder.RegisterType<Worker>().As<IPerson>();   //IPerson類型的服務和Worker的組件連接起來,這個服務可以創建Worker類的實例

  並且上面的服務在自動裝備中也有效

  AutoFacManager manager = container.Resolve<AutoFacManager>();

  2、名字

  服務可以進一步按名字識別。使用這種方式時,用 Named()註冊方法代替As()以指定名字:

  builder.RegisterType<Worker>().Named<IPerson>("worker");

  使用Name可以檢索服務創建實例:

  IPerson p = container.ResolveNamed<IPerson>("worker");

  ResolveNamed()只是Resolve()的簡單重載,指定名字的服務其實是指定鍵的服務的簡單版本。

  3、鍵

  有Name的方式很方便,但是值支持字符串,但有時候我們可能需要通過其他類型作鍵。

  例如,使用枚舉作為key:

  public enum DeviceState { Worker, Student }

  使用key註冊服務,通過Keyed<T>()方法:

  builder.RegisterType<Student>().Keyed<IPerson>(DeviceState.Student);

  顯式檢索

  使用key檢索服務以創建實例,通過ResolveKeyd()方法:

  IPerson p = container.ResolveKeyed<IPerson>(DeviceState.Student);

  ResolveKeyd()會導致容器被當做 Service Locator使用,這是不被推薦的。應該使用IIndex type替代。

  IIndex索引

  Autofac.Features.Indexed.IIndex<K,V>是Autofac自動實現的一個關聯類型。component可以使用IIndex<K,V>作為參數的構造函數從基於鍵的服務中選擇需要的實現。

技術分享圖片
    builder.RegisterType<Student>().Keyed<IPerson>(DeviceState.Student);
    using (IContainer container = builder.Build())
    {
        IIndex<DeviceState, IPerson> IIndex = container.Resolve<IIndex<DeviceState, IPerson>>();
        IPerson p = IIndex[DeviceState.Student];
        p.Say();  //輸出我是一個學生
    } 
技術分享圖片

  IIndex中第一個泛型參數要跟註冊時一致,在例子中是DeviceState枚舉。其他兩種註冊方法沒有這樣的索引查找功能,這也是為什麽設計者推薦Keyed註冊的原因之一。

三、自動裝配

  從容器中的可用服務中選擇一個構造函數來創造對象,這個過程叫做自動裝配。這個過程是通過反射實現的,所以實際上容器創造對象的行為比較適合用在配置環境中。

  1、選擇構造函數

  Autofac默認從容器中選擇參數最多的構造函數。如果想要選擇一個不同的構造函數,就需要在註冊的時候就指定它。

  builder.RegisterType(typeof(Worker)).UsingConstructor(typeof(int));

  這種寫法將指定調用Worker(int)構造函數,如該構造函數不存在則報錯。

  2、額外的構造函數參數

  有兩種方式可以添加額外的構造函數參數,在註冊的時候和在檢索的時候。在使用自動裝配實例的時候這兩種都會用到。

  註冊時添加參數

  使用WithParameters()方法在每一次創建對象的時候將組件和參數關聯起來。

    List<NamedParameter> ListNamedParameter = new List<NamedParameter>() { new NamedParameter("Id", 1), new NamedParameter("Name", "張三") };
    builder.RegisterType<Worker>().WithParameters(ListNamedParameter).As<IPerson>();

  在檢索階段添加參數
  在Resolve()的時候提供的參數會覆蓋所有名字相同的參數,在註冊階段提供的參數會覆蓋容器中所有可能的服務。

  3、自動裝配

  至今為止,自動裝配最大的作用就是減少重復配置。許多相似的component無論在哪裏註冊,都可以通過掃描使用自動裝配。

  builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).As<IPerson>();

  在需要的時候,依然可以創建指定的構造函數創建指定的類。

  builder.Register(c => new Worker(2,"關羽"));

四、程序集掃描

  1、掃描

  Autofac可以使用約定在程序集中註冊或者尋找組件。

  Autofac可以根據用戶指定的規則在程序集中註冊一系列的類型,這種方法叫做convention-driven registration或者掃描。

  builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith("Manager"));

  每個RegisterAssemblyTypes方法只能應用一套規則。如果有多套不同的集合要註冊,那就有必要多次調用RegisterAssemblyTypes。

  2、選擇類型

  RegisterAssemblyTypes接受程序集的集合。默認情況下,程序集中所有公共具體的類都會被註冊。

  如果想要過濾註冊的類型,可以使用Where.向下面這樣:

  Where(t => t.Name.EndsWith("Manager"))

  如果想要排除某些類型,使用Except():

  Except<AutoFacManager)>()

  或者,自定義那些已經排除的類型的註冊:

  Except<Worker>(ct =>ct.As<IPerson>().SingleInstance())

  多個過濾器可以同時使用,這時他們之間是AND的關系。

  3、指定服務

  RegisterAssemblyTypes這個註冊方法是註冊單個方法的超集,所以類似As的方法也可以用在程序集中,例如

  As<IPerson>();

  As和Named這兩個方法額外的重載方法接受lambda表達式來決定服務會提供什麽樣的類型。

五、事件

  1、激活事件

  在component生命周期的不同階段使用事件。

  Autofac暴露五個事件接口供實例的按如下順序調用

  1. OnRegistered
  2. OnPreparing
  3. OnActivated
  4. OnActivating
  5. OnRelease

  這些事件會在註冊的時候被訂閱,或者被附加到IComponentRegistration 的時候。

    builder.RegisterType<Worker>().As<IPerson>()
        .OnRegistered(e => Console.WriteLine("在註冊的時候調用!"))
        .OnPreparing(e => Console.WriteLine("在準備創建的時候調用!"))
        .OnActivating(e => Console.WriteLine("在創建之前調用!"))
        .OnActivated(e => Console.WriteLine("創建之後調用!"))
        .OnRelease(e => Console.WriteLine("在釋放占用的資源之前調用!"));

  以上示例輸出如下:

  技術分享圖片

  OnActivating

  組件被創建之前調用,在這裏你可以:

  1. 將實例轉向另外一個或者使用代理封裝它
  2. 進行屬性註入
  3. 執行其他初始化工作

  OnActivated

  在component被完全創建的時候調用一次。在這個時候你可以執行程序級別的一些工作(這些工作依賴於對象被完全創建)-這種情況很罕見。

  OnRelease

  替代component的標準清理方法。實現了IDisposable 接口的標準清理方法(沒有標記為ExternallyOwned) 通過調用Dispose 方法。沒有實現IDisposable或者被標記為ExternallyOwned的清理方法是一個空函數-不執行任何操作。OnRelease 就是用來覆蓋默認的清理行為的。

六、屬性註入

  屬性註入使用可寫屬性而不是構造函數參數實現註入。

  示例:

    builder.Register(c => new AutoFacManager { person = c.Resolve<IPerson>() });
    builder.RegisterType<Worker>().As<IPerson>();

  為了提供循環依賴(就是當A使用B的時候B已經初始化),需要使用OnActivated事件接口:

    builder.Register(c => new AutoFacManager()).OnActivated(e => e.Instance.person = e.Context.Resolve<IPerson>());
    builder.RegisterType<Worker>().As<IPerson>();

  通過反射,使用PropertiesAutowired()修飾符註入屬性:

    builder.RegisterType<AutoFacManager>().PropertiesAutowired();
    builder.RegisterType<Worker>().As<IPerson>();

  如果你預先知道屬性的名字和值,你可以使用:

    builder.RegisterType<AutoFacManager>().WithProperty("person", new Worker());
    builder.RegisterType<Worker>().As<IPerson>();

七、方法註入

  可以實現方法註入的方式有兩種。

  1、使用Activator

  如果你使用委托來激活,只要調用這個方法在激活中

    builder.Register(c =>
    {
        var result = new AutoFacManager();
        result.SetDependency(c.Resolve<IPerson>());
        return result;
    });

  註意,使用這種方法,AutoFacManager類裏必須要有這個方法:

    public void SetDependency(IPerson MyPerson)
    {
        person = MyPerson;
    }

  2、使用Activating Handler

  如果你使用另外一種激活,比如反射激活,創建激活的事件接口OnActivating,這種方式僅需一行代碼:

  builder.Register<AutoFacManager>(c => new AutoFacManager()).OnActivating(e => e.Instance.SetDependency(new Worker()));

八、Resolve的參數

  當註冊或者檢索component的時候可以使用參數。

  1、傳遞參數給Resolve

  Resolve接受可變參數或IEnumerable<T>傳入多個值

    using (IContainer container = builder.Build())
    {
        AutoFacManager manager = container.Resolve<AutoFacManager>(new NamedParameter("name", "劉備"));
        Console.WriteLine(manager.Name);    //輸出 劉備
        manager.Say();
    }

  此時,AutoFacManager下必須添加如下構造函數

    public AutoFacManager(string name,IPerson MyPerson)
    {
        Name = name;
        person = MyPerson;
    }

  2、可用的參數類型

  Autofac提供幾種不同的參數對應策略:

  1. NamedParameter :像上面那樣對應的參數名字
  2. TypedParameter:對應到參數的類型(必須是具體的類型)
  3. ResolvedParameter:靈活的參數匹配
  4. NamedParameter 和TypedParameter:只能提供常量參數

  3、從表達式中使用參數

  如果使用表達式註冊的方式,可以使用第二個可用的委托參數來獲得參數。

技術分享圖片
    builder.Register((c, p) => new AutoFacManager(p.Named<string>("name"), c.Resolve<IPerson>()));
    builder.RegisterType<Worker>().As<IPerson>();
    using (IContainer container = builder.Build())
    {
        AutoFacManager manager = container.Resolve<AutoFacManager>(new NamedParameter("name", "劉備"));
        Console.WriteLine(manager.Name);    //輸出劉備
        manager.Say();
    }
技術分享圖片

九、元數據

  Autofac提供一些機制去創建和使用component的元數據。元數據是存儲component中的關於這個component的信息,不需要創建實例也能訪問。

  1、在註冊的時候添加元數據

  值描述的元數據在註冊階段和component聯系起來,每個元數據都是一個鍵值對:

    builder.RegisterType<AutoFacManager>();
    builder.Register(c => new Worker()).As<IPerson>().WithMetadata("大將", "趙雲");

  用XML文件可以表示為:

技術分享圖片
  <component
      type="ConsoleApplication3.Program.Worker, ConsoleApplication3"
      service="ConsoleApplication3.Program.IPerson, ConsoleApplication3" >
      <metadata>
          <item name="大將" value="趙雲" type="System.String" />
       </metadata>
  </component>
技術分享圖片

  2、使用元數據

  不用於一般的屬性,元數據和component本身是相互獨立額度。

  這使得在運行條件下從很多component中選擇一個時非常有用,或者元數據不是component實例的固有屬性時。元數據可以表述ITask 執行的時間,或者實現了ICommand的按鈕標題。

  另外一些component可以通過Meta 使用元數據。

十、循環依賴

  循環依賴是指運行期間對象之間的相互依賴

  目前,Autofac僅僅由處理構造函數/屬性依賴的方法。

  1、構造函數/屬性依賴

  使用含有屬性依賴的類時,使用Activated事件的InjectUnsetProperties。

技術分享圖片
  public class Student
  {
      public Student(Worker worker) { }
  }
 
  public class Worker
  {
      public Student Student { get; set; }
  }
 
  ContainerBuilder cb = new ContainerBuilder();
  cb.Register<Student>();
  cb.Register<Worker>().OnActivated(ActivatedHandler.InjectUnsetProperties);
技術分享圖片

十一、泛型

  給定一個開放的泛型,Autofac會提供一個具體的實現。

  開放的泛型類型使用泛型服務註冊需要給定一個服務類型和一個實現類型。

    builder.RegisterGeneric(typeof(List<>)).As(typeof(IList<>));
    using (IContainer container = builder.Build())
    {
        var tt = container.Resolve<IList<int>>();
        Console.WriteLine(tt.GetType().FullName);
    }

  Autofac關心泛型約束。如果一個有約束的實現類型對服務不可用,那麽這個實現類型將被忽略。

十二、適配器和裝飾器

  Autofac提供一些機制來實現適配器模式和裝飾器模式。

  1、適配器

  一個適配器使用一個服務並且適配另外一個。
  如果一個適配器在Autofac中被註冊,Autofac會為每一個適配服務的實現創建單獨的適配器。
  這個介紹性的文章描述了適配器在Autofac中是如何實現的。

  2、裝飾器

  裝飾器像適配器一樣,在其中封裝了一個具體的服務的實現,但是和適配器相比,裝飾器暴露出的服務和它封裝的一樣。

十三、實例生命周期

  實例生命周期決定在同一個服務的每個請求的實例是如何共享的。

  當請求一個服務的時候,Autofac會返回一個單例 (single instance作用域), 一個新的對象 (per lifetime作用域) 或者在某種上下文環境中的單例。比如 一個線程 或者一個HTTP請求 (per lifetime 作用域)。

  這條規則適用於顯式調用Resolve從容器中檢索對象或者滿足依賴而隱式實現的對象。

  1、Per Dependency

  在其他容器中也稱作瞬態或者工廠,使用Per Dependency作用域,服務對於每次請求都會返回互補影響實例。

  在沒有指定其他參數的情況下,這是默認是作用域。

  builder.RegisterType<Worker>();
 
  // or
 
  builder.RegisterType<Worker>().InstancePerDependency();

  2、Single Instance

  使用Single Instance作用域,所有對父容器或者嵌套容器的請求都會返回同一個實例。

  builder.RegisterType<Worker>().SingleInstance();

  3、Per Lifetime Scope

  這個作用域適用於嵌套的生命周期。一個使用Per Lifetime 作用域的component在一個 nested lifetime scope內最多有一個實例。

  當對象特定於一個工作單元時,這個非常有用。比如,一個HTTP請求,每一個工作單元都會創建一個nested lifetime,如果在每一次HTTP請求中創建一個nested lifetime,那麽其他使用 per-lifetime 的component在每次HTTP請求中只會擁有一個實例。

  這種配置模型在其他容器中等價於per-HTTP-request, per-thread等。

  builder.RegisterType<Worker>().InstancePerLifetimeScope();

  ASP.NET和WCF集成中,每一次web請求或者方法調用,InstancePerLifetimeScope會被默認附加到component上。

  4、上下文

  上下文作用域和per-lifetime作用域類似,但是對可見性提供更多顯示的控制。

  在大多數程序中,同一層次的容器嵌套代表一個工作單元,如果需要多層嵌套(例如global->request->transation),可以使用標簽確保component在多層結構中的某一層共享。

  builder.RegisterType<XWorker>().InstancePerMatchingLifetimeScope(MyContextHierarchy.UserSession);

  提供的標簽和生命周期作用域是對應的

  var userSessionLifetime = container.BeginLifetimeScope();
  userSessionLifetime.Tag = MyContextHierarchy.UserSession;

Autofac 組件、服務、自動裝配 《第二篇》