前言
autofac
Autofac是一套高效的依赖注入框架。
Autofac官方网站:http://autofac.org/
Autofac在Github上的开源项目:https://github.com/autofac/Autofac
Autofac安装:通过VS的Nuget可以很方便的获取。
依赖注入
依赖注入,这个专业词我们可以分为两个部分来理解:
依赖,也就是UML中描述事物之间关系的依赖关系,依赖关系描述了事物A在某些情况下会使用到事物B,事物B的变化会影响到事物A;
注入,医生通过针头将药物注入到病人体内。注入也就是由外向内注入、灌输一些东西。
综合上面的说明,依赖注入就是A类依赖B类,B类的实例由外部向A注入,而不是由A自己进行实例化或初始化。
三种注入方式
构造器注入
我们先理解构造器注入的字面意思,构造器注入也就表示,依赖关系通过构造器进行注入。
这种我们平时是非常常见的,类A依赖于类B,类A的构造方法中,有一个参数为类B,在new 类A,会从外部为类B传入实例,这就是构造注入:
class Program { static void Main(string[] args) { var b = new B(); var a = new A(b); } } class A { private B _b; public A(B b) { this._b = b; } } class B { }上面说明了构造注入的含义以及构造注入的表现形式,下面我们来看看autofac中的构造注入。
还记得在上一篇博文Autofac全面解析系列(版本:3.5) – [使用篇(推荐篇):2.解析获取] 中的Resolve对象构造方法选择原则小结中提到了构造注入这个概念。在使用autofac时,构造注入是默认行为。
以上面的代码为例,如果类型A和类型B都注册到了autofac中,那么在通过autofac解析获取A时,autofac会检测到A的构造方法中是要一个参数B,而类型B是已经注册到autofac中的,所以autofac会自动创建b参数,然后传入A的构造方法中的。这样,autofac就自动帮我们完成了构造注入的工作。
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<A>(); builder.RegisterType<B>(); var container = builder.Build(); var a = container.Resolve<A>(); //A的构造方法需要参数b,但是这里不需要做更多地操作 } }class A { private B _b; public A(B b) { this._b = b; } } class B { }
属性注入
属性注入也就是通过属性进行注入,我们修改上面的A类,将变量_b通过属性暴露出来,并且删掉有参构造方法,然后让我们看看我们平常写代码时怎么实现属性注入的:
class Program { static void Main(string[] args) { var a = new A(); //点击A查看A类修改后结构 var b = new B(); a.B = b; //通过属性来注入具有依赖关系的B } }class A { private B _b; public B B { get { return _b; } set { _b = value; } } } class B { }这种代码在日常中我们写过了无数遍,即使是这么平常的代码,但这就是属性注入。
依赖注入注意点
但是有一点还是要注意的,我们不能随便把这种类似的代码拿出去就告诉别人,我们需要注意一点,需要分清两者之间是否真的是依赖关系。比如领域模型,简单的领域模型就是将数据表映射为一个类,对于数据表的每个字段,我们会生成一个对应的属性,对于这种,我们不能够在为每个属性进行赋值时就说“这是依赖注入”,这并不是依赖注入,更多情况下,字段与表的关系是一个组合关系。这一点对于之前的构造注入和后面会讲到的方法注入都适用。
说完注意点,让我们再来看看autofac是怎么进行属性注入的:
自动属性注入
属性注入的所有注入方式都是在注册时定义的,不像构造注入那般,可以在Resolve时传参注入。
构造器注入是默认行为,不需要设置,默认会去检查,而属性注入并不是默认行为。但是我们可以通过设置,让属性注入也成为自动注入。
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); // 通过PropertiesAutowired制定类型A在获取时会自动注入A的属性 builder.RegisterType<A>().PropertiesAutowired(); builder.RegisterType<B>(); var container = builder.Build(); var a = container.Resolve<A>(); Console.Write("Press any key to continue..."); Console.ReadKey(); } }使用PropertiesAutowired也只是能指定某个类会自动进行属性注入,没有一键设置所有类型都会自动注入属性的设置。而且还需要注意一点,设置了自动属性注入后,也不代表所有属性都会自动注入,只有注册到Autofac中的类型才能自动注入。
WithProperty、WithProperties
PropertiesAutowired方式会自动注入所有可以注入的属性,但是如果只想注入指定几个属性,可以使用除PropertiesAutowired以外的几种注入方式,WithProperty就是其中一种:
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<A>().WithProperty(new NamedPropertyParameter("B", new B())); // builder.RegisterType<A>().WithProperty("B", new B()); //效果与上面相同
var container = builder.Build(); var a = container.Resolve<A>(); Console.Write("Press any key to continue..."); Console.ReadKey(); } }用法简单,WithProprtties的使用方式与WithProperty相似,在此就不贴代码了。
lambda
在注册篇里面有讲到一种lambda注册方式,lambda注册时,因为是写lambda表达式进行注册,其lambda内容可以写很多,其中就可以进行属性注入:
var builder = new ContainerBuilder(); builder.Register(c => { var _a = new A(); _a.B = new B(); //手动注入 return _a; });这里的注入,就是最开始讲到属性注入时的那种赋值注入。
事件
在autofac中,还有一些事件,这些事件在不同时期触发,事件相关的具体内容将在后续说明。在注入中能够使用到的事件有OnActivating和OnActivated,他们是在对象Resolve出来后触发,可以在事件中修改或替换返回对象,同样也可以进行属性注入:
var builder = new ContainerBuilder(); builder.RegisterType<A>().OnActivating(e => { e.Instance.B = new B(); //Instance为Resolve出来的实例,类型为A });OnActivated事件的写法与OnActivating相同,关于两个事件的区别,将在后续博文中进行说明,请持续关注!
方法注入
方法注入也不是默认行为,而且还没有提供像属性注入那样的自动注入设置。
方法注入有两种方式,也就是属性注入的后两种方式:lambda以及事件。大家应该已经能够想到注入的代码是什么样了:
var builder = new ContainerBuilder(); // lambda builder.Register(cc => { var _a = new A(); _a.MethodInjection(new B()); return _a; }); // 事件 builder.RegisterType<A>().OnActivated(e => { e.Instance.MethodInjection(new B()); });MethodInjection为A的一个方法,并且它需要一个类型为B的参数,我们在外部通过方法的方式将B传入,这就是方法注入。这里需要特别贴一下A类型的代码,相对之前有所改动,不仅仅是添加了一个方法:
class A { public void MethodInjection(B b) { // 做一些操作 } }这段代码可能与一些朋友想象中的不一样,有些朋友可能想着A中还有一个成员_b,然后在方法MethodInjection中将b赋值给_b。这里我特意将成员_b去掉,为的就是说明一个问题:
不是两个类型之间一定是成员关系,然后才能有依赖注入,我们得理解依赖的含义。A类型在某些操作中需要使用到B类型,而并不将B类型持久的保存起来,临时使用也是一种依赖关系。关于为什么这样就算作依赖注入,在我们了解刚刚说的依赖关系后,再来看看依赖的注入与不注入的不同形式,如果不是注入的方式,那么B类型将不做为参数传入,而直接在方法中new。
而依赖注入的好处,在这里还不能很好的看到,因为现在是在讲autofac中关于注入的方式。如果想更只管的看到注入的好处,我们将参数B换成接口IClass,使用注入的方式,我们在外部传入IClass的实例,因为IClass是接口,我们可以传入不同的实现,在更换实现时,这个方法内部的代码是不需要改动的,反之是需要改动的。依赖注入的好处,我们点到为止了,主要还是要在日常多使用对比,这样才能更切身的体会它的美妙之处!
尾述
疑问
关于注入这块儿,其实我个人有个疑问,关于autofac。属性注入中,我们可以通过设置PropertiesAutowired进行自动注入,但是有时,可能大部分属性我们都希望能够自动注入,然而有时会有那么几个属性我们需要自动注入忽略掉他们,在我想来,应该是有一个Attribute用于标记属性,被标记的属性会在属性自动注入时被忽略。
而我想的这种Attribute,我找了找,autofac中貌似并没看到。也可能是我自己忽略掉了,如果大家谁有知道的,烦请指导一下,谢谢
尾述
个人还是推荐使用默认最简单的构造注入,不需要传参的那种;属性注入推荐设置自动属性注入,如果能够找到疑问中说到的那种Attribute,那就更好了;方法注入还是不怎么推荐的。
其实这里的推荐原则是这样的,需要在注册时进行指定注入的方式实际是不太好的,因为后来的人可能不太清楚每个类型的注入规则,还需要到注册的地方进行查看,而且不同人员写的不同,这样容易混乱。而在获取时进行注入,实际也是不太妥的,因为在实际的用法中,我们会将注册类型与接口进行关联,在获取时直接获取接口类型。也正因为我们获取时获取的是接口类型,我们无法保证接口的实际实现是不是具有我们预期的参数。
如果有任何问题,还希望大家能够提出讨论,互相学习。也希望能够有前辈对博客的内容及表达方式提出意见和建议,谢谢!