Autofac全面解析系列(版本:3.5)–[依赖注入]

前言

autofac

Autofac是一套高效的依赖注入框架。
Autofac官方网站:http://autofac.org/

依赖注入

依赖注入,这个专业词我们可以分为两个部分来理解:

依赖,也就是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时,构造注入是默认行为。

以上面的代码为例,如果类型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,但是这里不需要做更多地操作
    }
}

属性注入

属性注入也就是通过属性进行注入,我们修改上面的A类,将变量_b通过属性暴露出来,并且删掉有参构造方法,然后让我们看看我们平常写代码时怎么实现属性注入的:

class Program {
    static void Main(string[] args)
    {
        var a = new A();    //点击A查看A类修改后结构
        var b = new B();
        a.B = b;    //通过属性来注入具有依赖关系的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中,还有一些事件,这些事件在不同时期触发,事件相关的具体内容将在后续说明。在注入中能够使用到的事件有OnActivatingOnActivated,他们是在对象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,那就更好了;方法注入还是不怎么推荐的。

其实这里的推荐原则是这样的,需要在注册时进行指定注入的方式实际是不太好的,因为后来的人可能不太清楚每个类型的注入规则,还需要到注册的地方进行查看,而且不同人员写的不同,这样容易混乱。而在获取时进行注入,实际也是不太妥的,因为在实际的用法中,我们会将注册类型与接口进行关联,在获取时直接获取接口类型。也正因为我们获取时获取的是接口类型,我们无法保证接口的实际实现是不是具有我们预期的参数。

如果有任何问题,还希望大家能够提出讨论,互相学习。也希望能够有前辈对博客的内容及表达方式提出意见和建议,谢谢!

相关文章
相关标签/搜索