依賴注入之Autofac使用總結

依賴注入粗暴理解

依賴: 

public class A
{     public A(B b)依賴注入粗暴理解
依賴: 
public class A
{     public A(B b)
     {          // do something     }  
}
這樣的代碼,估計沒有程序猿不曾使用。
A類實例化的時候需要一個B的對象作爲構造函數的參數,那麼A就依賴B,這就叫依賴。
當然,不用構造函數的方式,在A類內部去new一個B,其實也是一樣存在A依賴B。
注入:
看到“注入”一詞,第一想到的是不是注射器?哈哈,還生活在童年陰影中。 結合一下“打針”這個場景來簡單理解下依賴注入。
醫生使用注射器(Autofac),將藥物(依賴=類對象),注入到血管(其他類中)。
Autofac的基本使用
搭建項目
創建一個MVC項目,通過Nuget直接添加Autofac。
 注入類本身.AsSelf()
    public class TestController : Controller
    {        private readonly InjectionTestService _testService;        public TestController(InjectionTestService testService)
        {
            _testService = testService;
        }        public ActionResult Index()
        {
            ViewBag.TestValue = _testService.Test();            return View();
        }
    }
    public class InjectionTestService : IService
    {        public string Test()
        {            return "Success";
        }
    }
在Global.asax中加入依賴注入的註冊代碼
            // 創建一個容器
            var builder = new ContainerBuilder();            // 註冊所有的Controller            builder.RegisterControllers(Assembly.GetExecutingAssembly());            // RegisterType方式:
            builder.RegisterType<InjectionTestService>().AsSelf().InstancePerDependency();            // Register方式:
            builder.Register(c => new InjectionTestService()).AsSelf().InstancePerDependency();            // 自動注入的方式,不需要知道具體類的名稱

            /* BuildManager.GetReferencedAssemblies()
             * 程序集的集合,包含 Web.config 文件的 assemblies 元素中指定的程序集、
             * 從 App_Code 目錄中的自定義代碼生成的程序集以及其他頂級文件夾中的程序集。             */

            // 獲取包含繼承了IService接口類的程序集
            var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>()
                .Where(
                    assembly =>
                        assembly.GetTypes().FirstOrDefault(type => type.GetInterfaces().Contains(typeof(IService))) !=                        null
                );            // RegisterAssemblyTypes 註冊程序集
            var enumerable = assemblies as Assembly[] ?? assemblies.ToArray();            if (enumerable.Any())
            {
                builder.RegisterAssemblyTypes(enumerable)
                    .Where(type => type.GetInterfaces().Contains(typeof(IService))).AsSelf().InstancePerDependency();
            }            // 把容器裝入到微軟默認的依賴注入容器中
            var container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
爲接口注入具體類.AsImplementedInterfaces()
    public class TestController : Controller
    {        private readonly IService _testService;        public TestController(IService testService)
        {
            _testService = testService;
        }        public ActionResult Index()
        {
            ViewBag.TestValue = _testService.Test();            return View();
        }
    }
            // Register 方式指定具體類
            builder.Register(c => new InjectionTestService()).As<IService>().InstancePerDependency();            // RegisterType 方式指定具體類
            builder.RegisterType<InjectionTestService>().As<IService>().InstancePerDependency();            // 自動註冊的方式            // 獲取包含繼承了IService接口類的程序集
            var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>()
                .Where(
                    assembly =>
                        assembly.GetTypes().FirstOrDefault(type => type.GetInterfaces().Contains(typeof(IService))) !=                        null
                );            // RegisterAssemblyTypes 註冊程序集
            var enumerable = assemblies as Assembly[] ?? assemblies.ToArray();            if (enumerable.Any())
            {
                builder.RegisterAssemblyTypes(enumerable)
                    .Where(type => type.GetInterfaces().Contains(typeof(IService))).AsImplementedInterfaces().InstancePerDependency();
            }
 利用Named自動注入依賴類
需求場景說明:有A、B、C三個短信平臺提供發送短信業務;分別有三個短信平臺的實現類,AMessage,BMessage,CMessage;客戶端在不同時段選取不同平臺發送短信。    
常規簡單處理方式
新建三個服務類,AMsgService,BMsgService,CMsgService。
在客戶端通過 if else 的方式判斷要選用哪個短信平臺,然後new服務類對象,再調用Send方法發送短信。
缺點
如果有新的短信平臺D加入的話,必須新建一個DSendMsgService,然後修改客戶端if else 代碼。
改造
抽象一個短信平臺的接口
    public interface IMessage
    {        decimal QueryBalance();        bool Send(string msg);        int TotalSend(DateTime? startDate, DateTime? endDate);
    }
 具體實現類
    [MessagePlatform(Enums.MPlatform.A平臺)]    public class ASendMessageService : IMessage
    {        public decimal QueryBalance()
        {            return 0;
        }        public bool Send(string msg)
        {            return true;
        }        public int TotalSend(DateTime? startDate, DateTime? endDate)
        {            return 100;
        }
    }
類有一個自定義屬性標籤MessagePlatform,這個是幹嘛了? 是爲了給這個類做一個標記,結合Named使用,實現自動注入。
    public class TestController : Controller
    {        private Func<int, IMessage> _factory;        public TestController(Func<int, IMessage> factory)
        {
            _factory = factory;
        }        public ActionResult Index()
        {            var retult = _factory((int)Enums.MPlatform.A平臺).Send("去你的吧");            return View(retult);
        }
    }
構造函數參數居然是一個func的委託?
這個factory傳入參數是一個int(定義好的短信平臺枚舉值),就可以拿到這個短信平臺具體的實現類?
沒錯,autofac就是這麼任性。
            builder.RegisterType<ASendMessageService>().Named<IMessage>(
                    (                        // 獲取類自定義屬性
                        typeof(ASendMessageService).GetCustomAttributes(typeof(MessagePlatformAttribute), false).FirstOrDefault()                        as MessagePlatformAttribute

                    ).platform.ToString()
                ).InstancePerRequest();

            builder.Register<Func<int, IMessage>>(c =>
            {                var ic = c.Resolve<IComponentContext>();                return name => ic.ResolveNamed<IMessage>(name.ToString());
            });
疑問:
上面只是給 ASendMessageService類實現了自動注入,那麼BSendMessageService,CSendMessageService怎麼辦了,不可能都去複製一段注入的配置代碼吧?
            typeof(IMessage).Assembly.GetTypes()
            .Where(t => t.GetInterfaces().Contains(typeof(IMessage)))
            .ForEach(type =>
            {
                 // 註冊type
            });
如果你有些實現類不在IMessge這個程序集下,那就不能這麼寫了,要結合具體項目情況來調整代碼。
總結
1.依賴注入的目的是爲了解耦。2.不依賴於具體類,而依賴抽象類或者接口,這叫依賴倒置。3.控制反轉即IoC (Inversion of Control),它把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所謂的“控制反轉”概念就是對組件對象控制權的轉移,從程序代碼本身轉移到了外部容器。4. 微軟的DependencyResolver如何創建controller 【後續學習】    
Autofac創建類的生命週期
1、InstancePerDependency
對每一個依賴或每一次調用創建一個新的唯一的實例。這也是默認的創建實例的方式。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() gets a new, unique instance (default.) 
2、InstancePerLifetimeScope
在一個生命週期域中,每一個依賴或調用創建一個單一的共享的實例,且每一個不同的生命週期域,實例是唯一的,不共享的。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a single ILifetimeScope gets the same, shared instance. Dependent components in different lifetime scopes will get different instances. 
3、InstancePerMatchingLifetimeScope
在一個做標識的生命週期域中,每一個依賴或調用創建一個單一的共享的實例。打了標識了的生命週期域中的子標識域中可以共享父級域中的實例。若在整個繼承層次中沒有找到打標識的生命週期域,則會拋出異常:DependencyResolutionException。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope tagged with any of the provided tags value gets the same, shared instance. Dependent components in lifetime scopes that are children of the tagged scope will share the parent's instance. If no appropriately tagged scope can be found in the hierarchy an DependencyResolutionException is thrown. 
4、InstancePerOwned
在一個生命週期域中所擁有的實例創建的生命週期中,每一個依賴組件或調用Resolve()方法創建一個單一的共享的實例,並且子生命週期域共享父生命週期域中的實例。若在繼承層級中沒有發現合適的擁有子實例的生命週期域,則拋出異常:DependencyResolutionException。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope created by an owned instance gets the same, shared instance. Dependent components in lifetime scopes that are children of the owned instance scope will share the parent's instance. If no appropriate owned instance scope can be found in the hierarchy an DependencyResolutionException is thrown. 
5、SingleInstance
每一次依賴組件或調用Resolve()方法都會得到一個相同的共享的實例。其實就是單例模式。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() gets the same, shared instance. 
6、InstancePerHttpRequest  (新版autofac建議使用InstancePerRequest)
在一次Http請求上下文中,共享一個組件實例。僅適用於asp.net mvc開發。
官方文檔解釋:Share one instance of the component within the context of a single HTTP request.
     {          // do something     }  
}

這樣的代碼,估計沒有程序猿不曾使用。

A類實例化的時候需要一個B的對象作爲構造函數的參數,那麼A就依賴B,這就叫依賴。

當然,不用構造函數的方式,在A類內部去new一個B,其實也是一樣存在A依賴B。

注入:

看到“注入”一詞,第一想到的是不是注射器?哈哈,還生活在童年陰影中。 結合一下“打針”這個場景來簡單理解下依賴注入。
醫生使用注射器(Autofac),將藥物(依賴=類對象),注入到血管(其他類中)。

Autofac的基本使用

搭建項目

創建一個MVC項目,通過Nuget直接添加Autofac。

 注入類本身.AsSelf()

    public class TestController : Controller
    {        private readonly InjectionTestService _testService;        public TestController(InjectionTestService testService)
        {
            _testService = testService;
        }        public ActionResult Index()
        {
            ViewBag.TestValue = _testService.Test();            return View();
        }
    }
    public class InjectionTestService : IService
    {        public string Test()
        {            return "Success";
        }
    }

在Global.asax中加入依賴注入的註冊代碼

            // 創建一個容器
            var builder = new ContainerBuilder();            // 註冊所有的Controller            builder.RegisterControllers(Assembly.GetExecutingAssembly());            // RegisterType方式:
            builder.RegisterType<InjectionTestService>().AsSelf().InstancePerDependency();            // Register方式:
            builder.Register(c => new InjectionTestService()).AsSelf().InstancePerDependency();            // 自動注入的方式,不需要知道具體類的名稱

            /* BuildManager.GetReferencedAssemblies()
             * 程序集的集合,包含 Web.config 文件的 assemblies 元素中指定的程序集、
             * 從 App_Code 目錄中的自定義代碼生成的程序集以及其他頂級文件夾中的程序集。             */

            // 獲取包含繼承了IService接口類的程序集
            var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>()
                .Where(
                    assembly =>
                        assembly.GetTypes().FirstOrDefault(type => type.GetInterfaces().Contains(typeof(IService))) !=                        null
                );            // RegisterAssemblyTypes 註冊程序集
            var enumerable = assemblies as Assembly[] ?? assemblies.ToArray();            if (enumerable.Any())
            {
                builder.RegisterAssemblyTypes(enumerable)
                    .Where(type => type.GetInterfaces().Contains(typeof(IService))).AsSelf().InstancePerDependency();
            }            // 把容器裝入到微軟默認的依賴注入容器中
            var container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

爲接口注入具體類.AsImplementedInterfaces()

    public class TestController : Controller
    {        private readonly IService _testService;        public TestController(IService testService)
        {
            _testService = testService;
        }        public ActionResult Index()
        {
            ViewBag.TestValue = _testService.Test();            return View();
        }
    }
            // Register 方式指定具體類
            builder.Register(c => new InjectionTestService()).As<IService>().InstancePerDependency();            // RegisterType 方式指定具體類
            builder.RegisterType<InjectionTestService>().As<IService>().InstancePerDependency();            // 自動註冊的方式            // 獲取包含繼承了IService接口類的程序集
            var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>()
                .Where(
                    assembly =>
                        assembly.GetTypes().FirstOrDefault(type => type.GetInterfaces().Contains(typeof(IService))) !=                        null
                );            // RegisterAssemblyTypes 註冊程序集
            var enumerable = assemblies as Assembly[] ?? assemblies.ToArray();            if (enumerable.Any())
            {
                builder.RegisterAssemblyTypes(enumerable)
                    .Where(type => type.GetInterfaces().Contains(typeof(IService))).AsImplementedInterfaces().InstancePerDependency();
            }

 利用Named自動注入依賴類

需求場景說明:

有A、B、C三個短信平臺提供發送短信業務;

分別有三個短信平臺的實現類,AMessage,BMessage,CMessage;

客戶端在不同時段選取不同平臺發送短信。

常規簡單處理方式

新建三個服務類,AMsgService,BMsgService,CMsgService。
在客戶端通過 if else 的方式判斷要選用哪個短信平臺,然後new服務類對象,再調用Send方法發送短信。

缺點

如果有新的短信平臺D加入的話,必須新建一個DSendMsgService,然後修改客戶端if else 代碼。

改造

抽象一個短信平臺的接口
    public interface IMessage
    {        decimal QueryBalance();        bool Send(string msg);        int TotalSend(DateTime? startDate, DateTime? endDate);
    }

 具體實現類

    [MessagePlatform(Enums.MPlatform.A平臺)]    public class ASendMessageService : IMessage
    {        public decimal QueryBalance()
        {            return 0;
        }        public bool Send(string msg)
        {            return true;
        }        public int TotalSend(DateTime? startDate, DateTime? endDate)
        {            return 100;
        }
    }

類有一個自定義屬性標籤MessagePlatform,這個是幹嘛了? 是爲了給這個類做一個標記,結合Named使用,實現自動注入。

    public class TestController : Controller
    {        private Func<int, IMessage> _factory;        public TestController(Func<int, IMessage> factory)
        {
            _factory = factory;
        }        public ActionResult Index()
        {            var retult = _factory((int)Enums.MPlatform.A平臺).Send("去你的吧");            return View(retult);
        }
    }

構造函數參數居然是一個func的委託?

這個factory傳入參數是一個int(定義好的短信平臺枚舉值),就可以拿到這個短信平臺具體的實現類?

沒錯,autofac就是這麼任性。

            builder.RegisterType<ASendMessageService>().Named<IMessage>(
                    (                        // 獲取類自定義屬性
                        typeof(ASendMessageService).GetCustomAttributes(typeof(MessagePlatformAttribute), false).FirstOrDefault()                        as MessagePlatformAttribute

                    ).platform.ToString()
                ).InstancePerRequest();

            builder.Register<Func<int, IMessage>>(c =>
            {                var ic = c.Resolve<IComponentContext>();                return name => ic.ResolveNamed<IMessage>(name.ToString());
            });

疑問:

上面只是給 ASendMessageService類實現了自動注入,那麼BSendMessageService,CSendMessageService怎麼辦了,不可能都去複製一段注入的配置代碼吧?

            typeof(IMessage).Assembly.GetTypes()
            .Where(t => t.GetInterfaces().Contains(typeof(IMessage)))
            .ForEach(type =>
            {
                 // 註冊type
            });

如果你有些實現類不在IMessge這個程序集下,那就不能這麼寫了,要結合具體項目情況來調整代碼。

總結

1.依賴注入的目的是爲了解耦。

2.不依賴於具體類,而依賴抽象類或者接口,這叫依賴倒置。

3.控制反轉即IoC (Inversion of Control),它把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所謂的“控制反轉”概念就是對組件對象控制權的轉移,從程序代碼本身轉移到了外部容器。

4. 微軟的DependencyResolver如何創建controller 【後續學習】

Autofac創建類的生命週期

1、InstancePerDependency

對每一個依賴或每一次調用創建一個新的唯一的實例。這也是默認的創建實例的方式。

官方文檔解釋:Configure the component so that every dependent component or call to Resolve() gets a new, unique instance (default.) 

2、InstancePerLifetimeScope

在一個生命週期域中,每一個依賴或調用創建一個單一的共享的實例,且每一個不同的生命週期域,實例是唯一的,不共享的。

官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a single ILifetimeScope gets the same, shared instance. Dependent components in different lifetime scopes will get different instances. 

3、InstancePerMatchingLifetimeScope

在一個做標識的生命週期域中,每一個依賴或調用創建一個單一的共享的實例。打了標識了的生命週期域中的子標識域中可以共享父級域中的實例。若在整個繼承層次中沒有找到打標識的生命週期域,則會拋出異常:DependencyResolutionException。

官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope tagged with any of the provided tags value gets the same, shared instance. Dependent components in lifetime scopes that are children of the tagged scope will share the parent's instance. If no appropriately tagged scope can be found in the hierarchy an DependencyResolutionException is thrown. 

4、InstancePerOwned

在一個生命週期域中所擁有的實例創建的生命週期中,每一個依賴組件或調用Resolve()方法創建一個單一的共享的實例,並且子生命週期域共享父生命週期域中的實例。若在繼承層級中沒有發現合適的擁有子實例的生命週期域,則拋出異常:DependencyResolutionException。

官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope created by an owned instance gets the same, shared instance. Dependent components in lifetime scopes that are children of the owned instance scope will share the parent's instance. If no appropriate owned instance scope can be found in the hierarchy an DependencyResolutionException is thrown. 

5、SingleInstance

每一次依賴組件或調用Resolve()方法都會得到一個相同的共享的實例。其實就是單例模式。

官方文檔解釋:Configure the component so that every dependent component or call to Resolve() gets the same, shared instance. 

6、InstancePerHttpRequest  (新版autofac建議使用InstancePerRequest)

在一次Http請求上下文中,共享一個組件實例。僅適用於asp.net mvc開發。
官方文檔解釋:Share one instance of the component within the context of a single HTTP request.


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