一、依赖注入
引入依赖注入的目的是为了解耦和。说白了就是面向接口编程,通过调用接口的方法,而不直接实例化对象去调用。这样做的好处就是如果添加了另一个种实现类,不需要修改之前代码,只需要修改注入的地方将实现类替换。上面的说的通过接口调用方法,实际上还是需要去实例化接口的实现类,只不过不需要我们手动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 原理都是先将接口和对应的实现类注入到容器中,当要使用的时候,组件会自动通过构造函数创建实例。这里有个问题如果有个实现类有多个构造函数,组件会找满足参数最多的那个构造函数。