c#中的IOC容器之Autofac容器
IOC(inversion of control)控制反转
以前我们把框架分为三层架构:UI层,BLL层,DAL层。在三层架构中我们,我们是一层一层的调用对象里面的方法,需要实例化对象:类名 对象名 = new 类名();这种叫传统工艺。
现在我们不需要在依赖某一个类来获取到实例。而是依赖于抽象,怎么获取?交给了第三方(称:IOC容器)通过抽象(接口,抽象类,普通父类)的方式获取到类的实例。
IOC内置容器:ServiceCollection
1.创建库类接口【Interfaces】里面什么都不写,只是做依赖注入的关系
public interface IMicrophone{ }
2.创建一个服务类库【Services】做我们需要调用的具体对象
public class Microphone: IMicrophone { public Microphone() { Console.WriteLine($"{this.GetType().Name}被构造了"); } }
3.通过容器依赖注入到项目中来
using Interfaces;//接口类库 using Services;//服务类库 using Microsoft.Extensions.DependencyInjection;//内置IOC容器 ServiceCollection service = new ServiceCollection();//1.创建一个容器,需要nuget引入:Microsoft.Extensions.DependencyInjection service.AddTransient<IMicrophone, Microphone>();//2.建立关系或是注册抽象和具体普通类之间的关系,注册方式有很多,这里只是其中一种 ServiceProvider sp = service.BuildServiceProvider();//3.进行集中管理,统一注入到ServiceProvider,而BuildServiceProvider就是获取依赖关系 IMicrophone microphone= sp.GetService<IMicrophone>();//4.获取抽象的具体实例,就相当于Microphone被实例化后的具体对象,通过接口抽象出来的
传统工艺:new 实例化对象。对象创建在项目中是多个地方的。
ioc容器可以在全局,配置抽象和具体普通类之间的关系,是可以修改AddTransient<抽象, 具体类>();修改为什么获取的就是什么。
ioc容器,最大的好处在于DI依赖注入:如果A依赖于B,B依赖于C;那么在创建A时,会把C先创建出来交给B,在把B创建出来交给A,从而创建出A对象来。
public interface IHeadphone{ }//接口 public class Headphone : IHeadphone //依赖于IPower { public Headphone(IPower power) { Console.WriteLine($"{this.GetType().Name}被构造了"); } }
Power依赖于IMicrophone
public interface IPower { } //接口 public class Power : IPower { public Power(IMicrophone microphone) { Console.WriteLine($"{this.GetType().Name}被构造了"); } }
Microphone有继承了IMicrophone
public interface IMicrophone{ }//接口 public class Microphone: IMicrophone { public Microphone() { Console.WriteLine($"{this.GetType().Name}被构造了"); } }
这种依赖注入关系叫构造函数注入
using Interfaces;//接口类库 using Services;//服务类库 using Microsoft.Extensions.DependencyInjection;//内置IOC容器 Console.WriteLine("传统工艺,实例化对象-------------------"); IMicrophone m =new Microphone(); IPower p = new Power(m); IHeadphone h = new Headphone(p); Console.WriteLine("IOC容器支持的依赖注入-----------------------"); ServiceCollection service = new ServiceCollection();//1.创建容器:ioc容器本质就是个工厂用来创建对象的 service.AddTransient<IMicrophone, Microphone>();//2.建立关系 service.AddTransient<IPower, Power>(); service.AddTransient<IHeadphone, Headphone>(); ServiceProvider sp = service.BuildServiceProvider();//3.进行集中管理,获取依赖关系 IHeadphone headphone = sp.GetService<IHeadphone>();//4.获取实例
依赖注入有三种【1.构造函数注入;2.方法注入;3.属性注入】但内置ioc容器只支持构造函数注入,其他两种只能去扩展
ServiceCollection的生命周期
ServiceCollection service = new ServiceCollection(); service.AddTransient<IMicrophone, Microphone>();//Transient:瞬时生命周期,每一次创建都是新的实例 service.AddSingleton<IHeadphone, Headphone>();//Singleton:单例的生命周期,相同的一个引用地址,同一对象实例 service.AddScoped<IPower, Power>();//Scoped:作用域生命周期,保存在相同的ServiceProvider对象里,获取是相同的实例,反之不同 ServiceProvider sp = service.BuildServiceProvider();//获取依赖关系保存到对象 IMicrophone m1 = sp.GetService<IMicrophone>(); IMicrophone m2 = sp.GetService<IMicrophone>(); Console.WriteLine($"Transient:瞬时生命周期比较m1和m2是否一样:{object.ReferenceEquals(m1,m2)}"); IHeadphone h1 = sp.GetService<IHeadphone>(); IHeadphone h2 = sp.GetService<IHeadphone>(); Console.WriteLine($"Singleton:单例生命周期比较h1和h2是否一样:{object.ReferenceEquals(h1, h2)}"); IPower p1 = sp.GetService<IPower>(); IPower p2 = sp.GetService<IPower>(); Console.WriteLine($"Scoped:作用域生命周期比较p1和p2是否一样:{object.ReferenceEquals(p1, p2)}");
ServiceCollection的多种注册:上面使用的是泛型注册
ServiceCollection service = new ServiceCollection(); service.AddTransient(typeof(IMicrophone), typeof(Microphone));//方法注册和泛型注册一样的 service.AddTransient(typeof(Microphone));//可以直接给个具体实例,调用也是实例 service.AddTransient<Microphone>();//泛型注册,也可以直接给一个实例 service.AddTransient(typeof(IPower), a => { //委托,注册抽象和一段业务逻辑 //在这里我们可以直接来决定创建这个对象的实例,可以对这个实例做加工。 IMicrophone m = a.GetService<IMicrophone>(); return new Power(m); }); ServiceProvider sp = service.BuildServiceProvider(); IMicrophone m1 = sp.GetService<IMicrophone>(); Microphone m2 = sp.GetService<Microphone>();//泛型注册也是一样的,可以直接给一个实例 IPower p = sp.GetService<IPower>();
ServiceCollection容器在asp.net core中的使用:首先在【Program.cs】
builder.Services.AddTransient<IMicrophone, Microphone>();//依赖:在顶级程序中注册内置ioc容器。
注入:到控制器(构造方法)中注入使用
private readonly IMicrophone microphone;public HomeController(IMicrophone microphone, IServiceProvider sp)//home控制器的构造方法 { this.microphone = microphone;//方法1:注入进来的ioc容器 this.microphone = sp.GetService<IMicrophone>();//方法2::这两种获取方式是一样 }
Autofac
新的容器替代内置容器,需要引入:Autofac
ContainerBuilder containerBuilder = new ContainerBuilder();//实例化对象ioc容器,需要引入:Autofac containerBuilder.RegisterType<Microphone>().As<IMicrophone>();//配置关系 IContainer container = containerBuilder.Build();//获取关系存放在集合中 IMicrophone microphone = container.Resolve<IMicrophone>();//获取实例
多种注册
ContainerBuilder containerBuilder = new ContainerBuilder();//创建:ioc容器对象 containerBuilder.RegisterInstance(new Microphone());//方法1,:注册一个具体实例
containerBuilder.RegisterType<Microphone>().As<IMicrophone>();//方法2:建立接口和实例的关系,然后委托一段业务逻辑 containerBuilder.Register<IPower>(context => {//委托,注册一段业务逻辑 //这里的业务逻辑创建出IPower的实例----给一个入口,自己创建出对象的实例 IMicrophone microphone = context.Resolve<IMicrophone>(); IPower power = new Power(microphone); return power; });
containerBuilder.RegisterGeneric(typeof(List<>)).As(typeof(IList<>));//方法3:注册泛型 IContainer container = containerBuilder.Build();//获取关系,保存到集合 IMicrophone microphone = container.Resolve<IMicrophone>();//1.接口和实例关系创建的 IPower power = container.Resolve<IPower>();//2.注册一段业务逻辑 IList<IMicrophone> mlist = container.Resolve<IList<IMicrophone>>();//3.注册泛型
Assembly程序集注册,需要引入:System.Reflection;
using Interfaces;//接口类库 using Services;//服务类库 using Autofac;//ioc容器 using System.Reflection;//Assembly获取程序集 ContainerBuilder containerBuilder = new ContainerBuilder();//创建:ioc容器对象 Assembly InterfaceAssembly = Assembly.LoadFrom("Interfaces.dll");//接口程序集 Assembly serviceAssembly = Assembly.LoadFrom("Services.dll");//实例类程序集 containerBuilder.RegisterAssemblyTypes(InterfaceAssembly, serviceAssembly).AsImplementedInterfaces();//建立程序集关系 IContainer container = containerBuilder.Build();//获取关系,保存到集合 IEnumerable<IMicrophone> microphone = container.Resolve<IEnumerable<IMicrophone>>();//泛型实例,普通实例也可以
依赖注入:依赖指的是上面的ioc容器建立的依赖关系;注入【1.构造函数注入,2.方法注入,3.属性注入】
1.构造函数注入:UsingConstructor( )
public class Power : IPower { private readonly IMicrophone microphone;//通过ioc容器注入进来的实例 public Power(IMicrophone microphone) { //这种叫构造函数注入this.microphone = microphone; } }
如果有多个构造函数【默认选择参数最多的构造函数】如果需要指定执行的构造函数,注册时需要调用:UsingConstructor( ) 方法来决定执行哪个构造函数
ContainerBuilder containerBuilder = new ContainerBuilder(); containerBuilder.RegisterType<Microphone>().As<IMicrophone>();//UsingConstructor指定多少参数就能行哪个构造函数,默认执行最多参数的构造函数 containerBuilder.RegisterType<Power>().As<IPower>().UsingConstructor(typeof(IMicrophone));//指定执行的构造函数必须只有一个参数类型为:IMicrophone IContainer container = containerBuilder.Build(); IPower microphone = container.Resolve<IPower>();
2.属性注入:PropertiesAutowired() ----------所有属性都注入
ContainerBuilder containerBuilder = new ContainerBuilder(); containerBuilder.RegisterType<Microphone>().As<IMicrophone>(); containerBuilder.RegisterType<Power>().As<IPower>().PropertiesAutowired();//指定:属性注入 IContainer container = containerBuilder.Build(); IPower microphone = container.Resolve<IPower>();
public class Power : IPower { public IMicrophone microphone { get; set; }//属性注入直接传进属性来的 public Power() //参数不需要传 { } }
指定属性注入(PropertySelector接口):首先创建一个【特性】用于做属性注入的标记,哪个属性标记了这个特性,就代表这个属性支持属性注入,否则就是普通属性
[AttributeUsage(AttributeTargets.Property)]//声明特性只能标记在属性上面有效 public class CusotmSelectAttribute : Attribute { }//做标记;表示谁使用了这个特性谁就支持属性注入,否则就是普通属性 public class CusotmPropertySelector : IPropertySelector {//在这里面判断哪些属性是需要做属性注入 public bool InjectProperty(PropertyInfo propertyInfo, object instance) { return propertyInfo.CustomAttributes.Any(c => c.AttributeType == typeof(CusotmSelectAttribute));//标记了这个特性,就需要实现注入 } }
在注册依赖关系时让PropertySelector生效
containerBuilder.RegisterType<Power>().As<IPower>().PropertiesAutowired(new CusotmPropertySelector());//指定属性注入
特性的引用
public class Power : IPower { [CusotmSelectAttribute]//指定了属性注入 public IMicrophone microphone { get; set; }//这个是注入后的实例 public IHeadphone headphone { get; set; }//就是个普通属性 public Power() //参数不需要传 { } }
3、方法注入:OnActivated(委托);在构造出某一个对象后,自动去调用对象的某一个方法,方法需要什么参数,自动构造出来,传入参数。
ContainerBuilder containerBuilder = new ContainerBuilder(); containerBuilder.RegisterType<Microphone>().As<IMicrophone>(); containerBuilder.RegisterType<Power>().As<IPower>().OnActivated(active => { //指定字段与方法行成依赖关系 IMicrophone micro = active.Context.Resolve<IMicrophone>();//Context从上下文中获取到实例 active.Instance.index111(micro);//使用index111方法 }); IContainer container = containerBuilder.Build(); IPower microphone = container.Resolve<IPower>();
public class Power : IPower { public IMicrophone _microphone;//字段必须是公开的public public Power() //参数不需要传,同时也支持构造方法注入,传进来也一样可以获取 { } public void index111(IMicrophone microphone)//和构造方法注入类似 { this._microphone = microphone; } }
单抽象多实现:默认创建出的对象,是最后注册的服务关系。
ContainerBuilder containerBuilder = new ContainerBuilder(); containerBuilder.RegisterType<Microphone>().As<IMicrophone>();//Microphone继承接口IMicrophone containerBuilder.RegisterType<Headphone>().As<IMicrophone>();//Headphone也继承接口IMicrophone IContainer container = containerBuilder.Build(); IMicrophone microphone = container.Resolve<IMicrophone>();//这么默认获取到最后一个实例,也就是Headphone IEnumerable<IMicrophone> m = container.Resolve<IEnumerable<IMicrophone>>();//想获取多个就使用可迭代泛型
如果需要获取某个实例,可以指定一个标识Keyed
ContainerBuilder containerBuilder = new ContainerBuilder(); containerBuilder.RegisterType<Microphone>().Keyed<IMicrophone>("Microphone");//指定一个标识 containerBuilder.RegisterType<Headphone>().Keyed<IMicrophone>("Headphone"); IContainer container = containerBuilder.Build(); IMicrophone m = container.ResolveKeyed<IMicrophone>("Microphone");//获取标识 IMicrophone h = container.ResolveKeyed<IMicrophone>("Headphone");//如果是集合获取也需要指定标识ResolveKeyed
想在mvc里使用Autofac还需要引用:Autofac.Extensions.DependencyInjection 替换内置工厂
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());//1.通过工厂替换为Autofac(把内置的AOP替换掉) builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => { //2.创建一个工厂 containerBuilder.RegisterType<Microphone>().As<IMicrophone>();//3.注册关系 });
控制器中构造方法调用(3种注入方式)
private readonly IMicrophone microphone; private readonly IEnumerable<IMicrophone> list; private readonly IServiceProvider sp; public HomeController(IMicrophone microphone, IEnumerable<IMicrophone> list, IServiceProvider sp, IComponentContext componentContext) { this.microphone = microphone; this.sp = sp; this.list = list; IMicrophone m = sp.GetService<IMicrophone>(); IMicrophone m2 = componentContext.Resolve<IMicrophone>();//Autofac的上下文也可以注入 }
属性注入(特性上面写了就不写了)
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => { containerBuilder.RegisterType<Microphone>().As<IMicrophone>(); //2.注册某个控制器和抽象之间的关系 var controllerBaseType = typeof(ControllerBase);//控制器都继承他,借用这个父类来识别是否是控制器。 containerBuilder.RegisterAssemblyTypes(typeof(Program).Assembly)//注册程序集(当前控制台所在的程序集) .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)//找到的是控制器,ControllerBase类型,但又不等于他 .PropertiesAutowired(new CusotmPropertySelector());//支持属性注入,这里就可以在属性上面标注特性了。 }); //1.支持控制器的这个实例让ioc容器来创建---其实就是让Autofac来创建 builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());//扩展作用是替换(参数1的事交给参数2来处理)
[CusotmSelectAttribute] public IMicrophone microphone { get; set; }
三、Autofac支持AOP
需要先引入程序包:Castle.Core 还需要引入:Autofac.Extras.DynamicProxy 来支持AOP扩展
public class CusotmInterceptor : IInterceptor//切入者逻辑 {//这个是Autofac支持AOP的扩展方法,需要引入:Castle.Core public void Intercept(IInvocation invocation) { Console.WriteLine("call方法之前执行"); invocation.Proceed();//执行call方法 Console.WriteLine("call方法之后执行"); } }
注册时调用:EnableClassInterceptors() 方法他们叫拦截器--------标记在类中,只对虚方法有效
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => { containerBuilder.RegisterType<Microphone>().As<IMicrophone>().EnableClassInterceptors();//支持aop扩展必须调用此方法。只会拦截虚方法。 containerBuilder.RegisterType<CusotmInterceptor>();//把aop扩展方法也注入进来才能使用,里面写方法前后执行动作。 });
using Autofac.Extras.DynamicProxy; using Interfaces; namespace Services { [Intercept(typeof(CusotmInterceptor))]//指定被aop扩展,EnableClassInterceptors标记在类中,只对类中的虚方法有有效 public class Microphone: IMicrophone { public Microphone() { Console.WriteLine($"{this.GetType().Name}被构造了"); } public virtual void call() { Console.WriteLine("call方法以执行,必须定义为虚方法才支持aop扩展。"); } public void ca() { Console.WriteLine("普通方法不是虚方法,不支持aop扩展。"); }//实现接口的两个方法 } }
EnableInterfaceInterceptors()-----------------------------标记在接口,对私有实现接口方法有效。
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => { containerBuilder.RegisterType<Microphone>().As<IMicrophone>().EnableInterfaceInterceptors();//标记在接口,对私有实现接口方法有效。 containerBuilder.RegisterType<CusotmInterceptor>();//把aop扩展方法也注入进来才能使用,里面写方法前后执行动作。 });
using Autofac.Extras.DynamicProxy; namespace Interfaces { [Intercept(typeof(CusotmInterceptor))]//EnableInterfaceInterceptors()标记在接口,对私有实现接口方法有效 public interface IMicrophone{ void call(); void ca(); } }
控制台的注入
public class HomeController : Controller { private readonly IMicrophone microphone; public HomeController(IMicrophone microphone) { this.microphone = microphone; } public IActionResult Index() { microphone.call();//虚方法 microphone.ca();//普通方法 return View(); } }
在项目中可以做为日志记录,记录方法的动态变化
using Castle.Core.Logging; using Castle.DynamicProxy; using Microsoft.Extensions.Logging; namespace Interfaces { public class CusotmInterceptor : IInterceptor//切入者逻辑 {//这个是Autofac支持AOP的扩展方法,需要引入:Castle.Core private readonly ILogger<CusotmInterceptor> logger; public CusotmInterceptor(ILogger<CusotmInterceptor> logger) { this.logger = logger; } public void Intercept(IInvocation invocation) { logger.LogInformation($"参数:{Newtonsoft.Json.JsonConvert.SerializeObject(invocation.Arguments)}"); logger.LogInformation($"{invocation.Method.Name}方法正在执行"); invocation.Proceed();//执行call方法 logger.LogInformation($"{invocation.Method.Name}方法执行完毕"); logger.LogInformation($"返回值:{Newtonsoft.Json.JsonConvert.SerializeObject(invocation.ReturnValue)}"); } } }
还可以做缓存处理
public class CusotmInterceptor : IInterceptor//切入者逻辑 { private static Dictionary<string, object> cacheDictionary = new Dictionary<string, object>();//记录缓存 public void Intercept(IInvocation invocation) { string cacheKey = invocation.Method.Name;//用方法名做钥匙,存在方法就不缓存,不存在就缓存数据。 if (cacheDictionary.ContainsKey(cacheKey))//如果有缓存 { invocation.ReturnValue = cacheDictionary[cacheKey];//返回缓存的结果。 } else //无缓存 { invocation.Proceed();//执行方法 cacheDictionary[cacheKey] = invocation.ReturnValue;//记录缓存 } } }
IInterceptorSelector 扩展,不需要在去类和接口上标记 [Intercept(typeof(CusotmInterceptor))] 就可以做拦截
public class CusotmInterceptorSelector : IInterceptorSelector//IInterceptor的扩展:用来选择切入者逻辑的类简单执行哪一个 { public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors) { //return interceptors;//返回所有,类和接口上标记的都可以获取到。 return new IInterceptor[] { new CusotmInterceptor() };//也可以选择哪一个切入者,多个在new。 } }
还需要ioc容器注册依赖关系
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => { containerBuilder.RegisterType<Microphone>().As<IMicrophone>() .EnableInterfaceInterceptors(new ProxyGenerationOptions(){ //指定使用的IInterceptor,不需要在去类和接口上标记:[Intercept(typeof(CusotmInterceptor))] Selector = new CusotmInterceptorSelector()//指定了选择者,交给这个类来执行我们需要拦截的切入者,指定了就会执行选择者,不需要在标记 }); containerBuilder.RegisterType<CusotmInterceptor>();//把aop扩展方法也注入进来才能使用,里面写方法前后执行动作。 });