控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。用来注册服务、解析实例。
依赖注入(Dependency Injection,简称DI)是一个将行为从依赖中分离的技术,简单地说,它允许开发者定义一个方法函数依赖于外部其他各种交互,而不需要编码如何获得这些外部交互的实例。
为了方便理解,我使用工厂模式来理解它。将IOC容器理解为一个工厂,IOC容器里面的服务就是工厂的产品。而依赖注入就是将服务注册到工厂当中去。依赖查找(Dependency Lookup)也是可以的实现相同的功能的。
.NET Core原生依赖注入框架
扩展包(NuGet):
Microsoft.Extensions.DependencyInjection.Abstractions:抽象的接口。
Microsoft.Extensions.DependencyInjection:具体实现。
示例:
public static class demo01
{
public interface IAccount { }
public interface IMessage { }
public interface ITool { }
public class Account : IAccount { }
public class Message : IMessage { }
public class Tool : ITool { }
public static void test()
{
var provider = new ServiceCollection()
.AddTransient<IAccount, Account>() // 瞬时模式,一次性的不保存的
.AddScoped<IMessage, Message>() // 作用域模式,保存在当前模式,即在当前作用域下为单例。provider.CreateScope().ServiceProvider创建子容器。
.AddSingleton<ITool, Tool>() // 单例模式,保存在根容器
.BuildServiceProvider();
Debug.Assert(provider.GetService<IAccount>() is Account);
Debug.Assert(provider.GetService<IMessage>() is IMessage);
Debug.Assert(provider.GetService<ITool>() is ITool);
}
}
注:
- 在注册的时候并不会创建实例,只是将类型信息写入。在获取服务的时候才会创建实例。
- ASP.NET Core核心之一就是依赖注入。对于ASP.NET Core应用来说,它具有一个与当前应用绑定,并且代表全局根容器的服务提供对象。在ASP.NET Core处理请求的时候,会根据这个根容器来创建一个与请求对应的服务范围对象。简单来说就是,当有请求进来的时候,就会创建一个与请求对应的子容器,用来处理这个请求所需要的服务。当请求处理完成就释放子容器,而由这个子容器提供的服务都会被销毁。
源码示例:
在源码中声明了一个ServiceDescriptor(服务描述对象)的集合。我们在注册服务的时候相当于.NET给我们创建了一个服务描述对象,然后将服务添加到服务描述对象集合中。当我们需要消费某个服务实例的时候,我们只需要调用GetService()方法就会获取我们想要的指定的类型。服务提供对象会根据我们想要的类型从服务描述对象集合中去找到响应的服务描述。找到之后会根据服务描述对象提供的信息来创建实例。
如果对源码感兴趣的可以查看源码:
- ServiceProvider服务提供对象,源码位置:runtime\src\libraries\Microsoft.Extensions.DependencyInjection\src\ServiceProvider.cs
- ServiceCollection服务集合默认实现源码位置:runtime\src\libraries\Microsoft.Extensions.DependencyInjection.Abstractions\src\ServiceCollection.cs
- ServiceCollectionServiceExtensions服务集合服务扩展,是ServiceCollection的扩展,扩展了服务注册。源码位置:runtime\src\libraries\Microsoft.Extensions.DependencyInjection.Abstractions\src\ServiceCollectionServiceExtensions.cs
- ServiceDescriptor服务描述对象,源码位置:runtime\src\libraries\Microsoft.Extensions.DependencyInjection.Abstractions\src\ServiceDescriptor.cs
- ServiceProviderServiceExtensions服务提供对象扩展,源码位置:runtime\src\libraries\Microsoft.Extensions.DependencyInjection.Abstractions\src\ServiceProviderServiceExtensions.cs
服务提供对象创建实例是调用构造方法,当构造方法存在参数的时候,只需要将需要的参数对象先注册到服务提供对象中,服务提供对象就会自动的创建需要的参数对象的实例,然后注入到构造函数中,而当出现如下代码的情况时,服务提供对象在创建实例的时候又是如何选择的呢?
示例:
public class demo03
{
public interface IAccount { }
public interface IMessage { }
public interface ITool { }
public interface ITest { }
public class Account : IAccount { }
public class Message : IMessage { }
public class Tool : ITool { }
public class Test : ITest
{
public Test(IAccount account)
{
Console.WriteLine($"Ctor:Test(IAccount)");
}
public Test(IAccount account, IMessage message)
{
Console.WriteLine($"Ctor:Test(IAccount,IMessage)");
}
public Test(IAccount account,IMessage message, ITool tool)
{
Console.WriteLine($"Ctor:Test(IAccount,IMessage,ITool)");
}
}
public static void test()
{
var provider = new ServiceCollection()
.AddTransient<IAccount, Account>()
.AddTransient<IMessage, Message>()
.AddTransient<ITest, Test>()
.BuildServiceProvider()
.GetService<ITest>();
}
}
执行结果:
从结果不难看出,此时调用的是Test(IAccount account, IMessage message)构造函数,即可简单认为服务提供对象提供满足参数最多的构造函数。
但是,当构造函数如下代码的时候,服务提供对象又是如何选择的呢?
示例:
public class demo04
{
public interface IAccount { }
public interface IMessage { }
public interface ITool { }
public interface ITest { }
public class Account : IAccount { }
public class Message : IMessage { }
public class Tool : ITool { }
public class Test : ITest
{
public Test(IAccount account, IMessage message)
{
Console.WriteLine($"Ctor:Test(IAccount,IMessage)");
}
public Test(IAccount account, ITool tool)
{
Console.WriteLine($"Ctor:Test(IAccount,ITool)");
}
}
public static void test()
{
var provider = new ServiceCollection()
.AddTransient<IAccount, Account>()
.AddTransient<IMessage, Message>()
.AddTransient<ITool, Tool>()
.AddTransient<ITest, Test>()
.BuildServiceProvider()
.GetService<ITest>();
}
}
执行结果:
此时,服务提供对象直接报错。这时候我们应认为服务提供对象选择的方式是:如果某个构造函数的参数类型集合,能够成为所有合法构造函数参数类型集合的超集,那么这个构造函数就会被服务提供对象选择。
第三方依赖注入框架
Autofac
扩展包(NuGet):
Autofac:Autofac本体。
Autofac.Extensions.DependencyInjection:用来和.NET内置DI集成的包。
示例:
public static class demo02
{
public interface IAccount { }
public interface IMessage { }
public interface ITool { }
public interface ITest { }
public class Base
{
public Base()
{
Console.WriteLine($"Created:{GetType().Name}");
}
}
public class Account : Base, IAccount { }
public class Message : Base, IMessage { }
public class Tool : Base, ITool { }
public class Test : ITest {
public IMessage Message { get; set; }
public Test(IAccount account, ITool tool)
{
Console.WriteLine($"Ctor:Test(IAccount,ITool)");
}
}
public static void test()
{
//使用内置DI的注入方式
var services = new ServiceCollection()
.AddTransient<IAccount, Account>()
.AddTransient<IMessage, Message>();
// 初始化Autofac容器
var containerBuilder = new ContainerBuilder();
// 方式一:将服务集合里面的注册项添加到Autofac的容器中
containerBuilder.Populate(services);
// 方式二:自己往Autofac容器中注册
containerBuilder.RegisterType<Tool>().As<ITool>();
// 属性注入
containerBuilder.RegisterType<Test>().As<ITest>().PropertiesAutowired();
// 程序集注册
containerBuilder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
//筛选集类为Base
.Where(t => t.BaseType == typeof(Base))
//暴露第一个接口
.As(t => t.GetInterfaces()[0])
//声明周期模式为Scope
.InstancePerLifetimeScope();
var container = containerBuilder.Build();
var provider = new AutofacServiceProvider(container);
Debug.Assert(provider.GetService<IAccount>() is Account);
Debug.Assert(provider.GetService<IMessage>() is Message);
Debug.Assert(provider.GetService<ITool>() is Tool);
}
}
Scrutor
Scrutor不是依赖注入框架,而实.NET内置DI的扩展包,弥补.NET内置DI服务注册方式。
扩展包(NuGet):
Scrutor。
示例:
public void ConfigureServices(IServiceCollection services)
{
services.Scan(scan =>
//加载程序集
scan.FromAssemblyOf<Startup>()
//要注册的程序集中的类
.AddClasses(classes => classes.Where(t => t.Name.EndsWith("Service",StringComparison.OrdinalIgnoreCase)))
//注册策略
.UsingRegistrationStrategy(RegistrationStrategy.Throw)
//暴露注册类型接口
.AsImplementedInterfaces()
.AsSelf()
//生命周期
.WithScopedLifetime()
);
}