一、依賴注入
引入依賴注入的目的是為了解耦和。說白了就是面向接口編程,通過調用接口的方法,而不直接實例化對象去調用。這樣做的好處就是如果添加了另一個種實現類,不需要修改之前代碼,只需要修改注入的地方將實現類替換。上面的說的通過接口調用方法,實際上還是需要去實例化接口的實現類,只不過不需要我們手動new 構造實現類,而是交給如微軟的DI、Autofac這些工具去構建實現類。我們只需要告訴它們,某個類是某個接口的實現類,當用到的時候,工具會自動通過構造函數實例化類。
二、.Net Core中自帶的DI
本來想寫依賴注入源碼的講解的,看到網上有篇文章關於源碼講解的,很詳細、清楚,就不再寫了。地址:http://www.cnblogs.com/bill-shooting/p/5540665.html。我在這里就說說使用吧。
依賴注入有三種生命周期,每種生命周期的注入方式大同小異,下面我以作用域生命周期舉例,其他兩種跟這個不同,我會特別說明。
下面為用到的兩個服務。
public class UserService : IUserService { public string GetName() { return "UserName"; } } public interface IUserService { string GetName(); }
public class ConfigReader : IConfigReader { private string configFilePath;//需要傳一個路徑,去讀取路徑下文件的內容 public ConfigReader(string configFileName) { this.configFilePath = configFileName; } public string Reader() { return File.ReadAllText(configFilePath); } } public interface IConfigReader { string Reader(); }
1、最常用的注入方式,以接口形式暴露服務
services.AddScoped(typeof(IUserService), typeof(UserService));
services.AddScoped<IUserService, UserService>();
兩種注入方式是一個意思,這種方式適合實現類為無參構造函數或者有參構造函數中參數已經被注入過了。
2、自己注入自己,以實現形式暴露服務
services.AddScoped<UserService>();
services.AddScoped(typeof(UserService));
這種注入方式適合只有實現類,沒有借口類的注冊。
3、需要傳參的構造函數的類的注入
services.AddScoped<IConfigReader, ConfigReader>(x => { return new ConfigReader("c:/a.txt"); });
services.AddScoped<IConfigReader>(x => { return new ConfigReader("c:/a.txt"); });
services.AddScoped(typeof(IConfigReader), x => { return new ConfigReader("c:/a.txt"); });
前兩個匿名方法參數是IServiceProvider,返回值為一個實例,第三個返回值是Object。上面舉的例子沒有用到IServiceProvider ,下面再舉一個例子。修改上面的UserService類,將構造方法需要一個IConfigReader參數。
public class UserService : IUserService {private IConfigReader configReader; public UserService(IConfigReader configReader) { this.configReader = configReader; } public string GetName() { return "UserName" + configReader.Reader(); } }
注冊的時候,如下:
services.AddScoped<IConfigReader, ConfigReader>(x => { return new ConfigReader("c:/a.txt"); });
//通過ServiceProvider獲取已經注冊的IConfigReader services.AddScoped<IUserService, UserService>(x => { return new UserService(x.GetService<IConfigReader>()); });
//或者
services.AddScoped<IUserService, UserService>(x => { return new UserService(new ConfigReader("c:/a.txt")); });
單例類型的生命周期多了兩種注入方式:
services.AddSingleton<IConfigReader>(new ConfigReader("c:/a.txt"));
services.AddSingleton(typeof(IConfigReader), new ConfigReader("C:/a.txt"));
自帶的依賴注入工具也可以批量注入
var assembly = Assembly.GetExecutingAssembly() .DefinedTypes .Where(a => a.Name.EndsWith("Service") && !a.Name.StartsWith("I")); foreach (var item in assembly) { services.AddTransient(item.GetInterfaces().FirstOrDefault(), item); }
注意:當一個服務有多個實現時,調用的時候通過 IEnumerable<IPayService> PayServices 獲取所有的實現服務。
services.AddTransient<IPayService, AliPayService>();
services.AddTransient<IPayService, WeChatPayService>();
使用的時候:
三、Autofac
1、以接口形式暴露服務
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); var builder = new ContainerBuilder(); builder.Populate(services); builder.RegisterType<UserService>().As<IUserService>().InstancePerLifetimeScope(); var container = builder.Build(); return new AutofacServiceProvider(container); }
2、通過實現類暴露服務
builder.RegisterType<UserService>();
3、需要傳參的構造函數的類的注入
builder.Register(c => new ConfigReader("c:/a.txt")).As<IConfigReader>();
4、通過程序集注入
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .Where(c => c.Name.EndsWith("Service")) .AsImplementedInterfaces();
總結:
不論是微軟的依賴注入組件還是Autofac 原理都是先將接口和對應的實現類注入到容器中,當要使用的時候,組件會自動通過構造函數創建實例。這里有個問題如果有個實現類有多個構造函數,組件會找滿足參數最多的那個構造函數。