1. 首页
  2. 文章列表
  3. 深入浅出依赖注入容器——Autofac
  4. 历史版本
  5. 深入浅出依赖注入容器——Autofac

1.写在前面

相信大家对IOC和DI都耳熟能详,它们在项目里面带来的便利大家也都知道,微软新出的.NetCore也大量采用了这种手法。

如今.NetCore也是大势所趋了,基本上以.Net为技术主导的公司都在向.NetCore转型了,我也一直在想抽时间写几篇.NetCore的文章,可无奈最近的项目实在太赶,也没时间写什么文章。

但今天这篇文章不是专门讲.NetCore的。

算了,废话不多说,开始今天的主题吧。

本篇文章主要讲解Autofac的基本使用和高级用法,以及可能会踩到的一些坑。

2.基本概念

懒得勤快的博客_全栈开发者_互联网分享精神

相信大家都知道IOC、DIP和DI是什么,用倒是网上抄点代码过来,项目就能跑起来了,但真要你讲出个花样,估计还是有点悬吧,这也是找工作面试时候经常被问起的。

懒得勤快的博客_全栈开发者_互联网分享精神

控制反转

 谁控制谁?

IoC/DI容器控制应用主程序。

 控制什么?

IoC/DI容器控制对象本身的创建、实例化;控制对象之间的依赖关系。

 何谓反转(对应于正向)?

因为现在应用程序不能主动去创建对象了,而是被动等待对象容器给它注入它所需要的资源,所以称之为反转。

 哪些方面反转了?

1.创建对象

2.程序获取资源的方式反了

 为何需要反转?

1.引入IoC/DI容器过后,体系更为松散,而且管理和维护以及项目升级更有序;

2.类之间真正实现了解耦

依赖

 什么是依赖(按名称理解、按动词理解)?

依赖(按名称理解):依赖关系;依赖(按动词理解):依赖的动作

 谁依赖于谁?

应用程序依赖于IoC/DI容器

 为什么需要依赖?

因为发生了反转,应用程序依赖的资源都是IoC/DI容器里面

 依赖什么东西?

应用程序依赖于IoC/DI容器为它注入所需要的资源。(比如:依赖关系)

注入

 谁注入于谁?

IoC/DI容器注入于应用程序。

 注入什么东西?

注入应用程序需要的对象,比如依赖关系。

 为何要注入?

因为程序要正常运行需要访问这些对象。

IOC(控制反转Inversion of Control)

控制反转(Inversion of Control)就是使用对象容器反过来控制应用程序所需要的外部资源,这样的一种程序开发思想,调用者不再创建被调用者的实例,由IOC框架实现(容器创建)所以称为控制反转;创建对象和对象非托管资源的释放都由外部容器去完成,实现项目层与层之间的解耦的一种设计思想。

DI(依赖注入Dependency Injection)和DIP(依赖倒置原则Dependency Inversion Principle)

相信很多人还分不清楚DI和DIP这两个词,甚至认为它们就是同一个词。

依赖倒置原则(Dependency Inversion Principle)为我们提供了降低模块间耦合度的一种思路,而依赖注入(Dependency Injection)是一种具体的实施方法,容器创建好实例后再注入调用者称为依赖注入,就是应用程序依赖IOC容器来注入所需要的外部资源,这样一种程序的开发思想。

能做什么(What)?松散耦合对象,解耦项目架构层。

怎么做(How)?使用Autofac/Unity/Spring等框架类库,里面有实现好了的IoC/DI容器。

用在什么地方(Where)?凡是程序里面需要使用外部资源的情况,比如创建对象,都可以考虑使用IoC/DI容器。

DI和IOC是同一概念吗?

肯定不是同一概念啊,但它们两个描述的是同一件事件,从不同的角度来说:IOC是从对象容器的角度;DI是从应用程序的角度。

控制反转的描述:对象容器反过来控制应用程序,控制应用程序锁所需要的一些对象,比如DbContext。

依赖注入的描述:应用程序依赖对象容器,依赖它注入所需要的外部资源。

对IoC的理解:

a. 应用程序无需主动new对象,而是描述对象应该如何被创建(构造方法、属性、方法参数等)。

b. 应用程序不需要主动装配对象之间的依赖关系,而是描述需要哪个服务,IoC容器会帮你装配,被动接受装配。

c. 主动变被动,是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。

3.Autofac/Unity简介

Autofac是.NET领域最为流行的IOC框架之一,传说是速度最快的一个,而今微软也很青睐的一个轻量高效的IOC框架,简单易上手且让人着迷;

Unity是微软官方出品的IOC框架,用法和Autofac大致差不多。

4.基本使用

通过nuget引入autofac;

懒得勤快的博客_全栈开发者_互联网分享精神

准备几个实例对象:

public class Doge
{
    public void SayHello()
    {
        Console.WriteLine("我是小狗,汪汪汪~");
    }
}

我们传统的做法当然是直接new啦,但现在有了IOC容器,怎么还能那样做呢!

接下来准备IOC容器,通过IOC容器来实例化对象;

var builder = new ContainerBuilder();//准备容器
builder.RegisterType<Doge>();//注册对象
var container = builder.Build();//创建容器完毕
var dog = container.Resolve<Doge>();//通过IOC容器创建对象
dog.SayHello();

懒得勤快的博客_全栈开发者_互联网分享精神

5.以接口方式注入

接着刚才的例子,添加个接口IAnimal,让Doge来实现它;

public interface IAnimal
{
    void SayHello();
}
public class Doge : IAnimal
{
    public void SayHello()
    {
        Console.WriteLine("我是小狗,汪汪汪~");
    }
}

然后IOC注册对象的方式改变为:

var builder = new ContainerBuilder();//准备容器
builder.RegisterType<Doge>().As<IAnimal>();//映射对象
var container = builder.Build();//创建容器完毕
var dog = container.Resolve<IAnimal>();//通过IOC容器创建对象
dog.SayHello();

懒得勤快的博客_全栈开发者_互联网分享精神

6.MVC控制器和WebAPI控制器注入

当然是web MVC项目了,要在MVC或WebApi项目中用autofac,当然需要以下nuget包了,

懒得勤快的博客_全栈开发者_互联网分享精神

准备几个Repository和Service;

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}
public interface IRepository
{
    List<Person> GetPersons();
}
public class RepositoryBase : IRepository
{
    public List<Person> Persons { get; set; } = new List<Person>();
    public RepositoryBase()
    {
        for (int i = 0; i < 10; i++)
        {
        Persons.Add(new Person()
        {
            Id = i + 1,
            Name = "张三" + i,
            Age = 10 + i * 2
        });
        }
    }
    public List<Person> GetPersons()
    {
        return Persons;
    }
}
public class PersonRepository : RepositoryBase
{
}
public interface IService
{
    List<Person> GetPersons();
}
public class ServiceBase : IService
{
    public IRepository Repository { get; set; }
    public ServiceBase(IRepository repository)
    {
        Repository = repository;
    }
    public List<Person> GetPersons()
    {
        return Repository.GetPersons();
    }
}
public class PersonService : ServiceBase
{
    public PersonService(IRepository repository) : base(repository)
    {
    }
}

网站启动时注册容器,在Global的Application_Start方法中注册IOC容器;

//注册IOC容器
var builder = new ContainerBuilder();
//告诉autofac将来要创建的控制器类存放在哪个程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly());//注册MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());//注册WebAPI控制器
//注册Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().InstancePerDependency();
var container = builder.Build();
//将当前容器交给MVC底层,保证容器不被销毁,控制器由autofac来创建
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);//先给WebAPI注册
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));//再给MVC注册

InstancePerDependency:为你注入的这个服务的生命周期.(注:生命周期我们后面讲)

现在就可以在控制器中通过构造函数注入对象了

public class HomeController : Controller
{
    public IService Service { get; set; }
    public HomeController(IService service)
    {
        Service = service;
    }
    // GET: Home
    public ActionResult Index()
    {
        var ps = Service.GetPersons();
        return Json(ps, JsonRequestBehavior.AllowGet);
    }
}

懒得勤快的博客_全栈开发者_互联网分享精神

7.属性注入

有时候我们需要对对象的属性进行注入,比如EF上下文对象DbContext,很简单,两句话搞定;

我们先来模拟一个DbContext:

public class DataContext
{
    public ICollection<Person> Persons { get; set; } = new List<Person>();
    public DataContext()
    {
        for (int i = 0; i < 10; i++)
        {
            Persons.Add(new Person()
            {
                Id = i + 1,
                Name = "张三" + i,
                Age = 10 + i * 2
            });
        }
    }
}

在IOC容器中注入;

//注册IOC容器
var builder = new ContainerBuilder();
//告诉autofac将来要创建的控制器类存放在哪个程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();//注册MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();//注册WebAPI控制器
builder.RegisterFilterProvider();//特性注入
builder.RegisterType<DataContext>().InstancePerRequest();
//注册Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
var container = builder.Build();
//将当前容器交给MVC底层,保证容器不被销毁,控制器由autofac来创建
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);//先给WebAPI注册
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));//再给MVC注册

PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies)表示属性注入,当实例对象存在有被IOC容器对象托管的时候,IOC容器会自动给属性实例化,Controller则可以不通过构造函数注入,直接属性注入;

public class HomeController : Controller
{
    public DataContext DataContext { get; set; }
    public ActionResult GetPersons()
    {
        return Json(DataContext.Persons, JsonRequestBehavior.AllowGet);
    }
}

懒得勤快的博客_全栈开发者_互联网分享精神

同样,我们也可以改造刚才的Repository,通过属性注入DataContext;

public class RepositoryBase : IRepository
{
    public DataContext DataContext { get; set; }
    public List<Person> GetPersons()
    {
        return DataContext.Persons.ToList();
    }
}

8.Attribute注入

有时候我们还需要注入一些Attribute特性,在使用MVC的时候,肯定会用到特性,比如ActionFilter,肯定会有一些自己定义的特性,我们想在里面做一些逻辑操作,比如用户登录状态检查,我们就需要在ActionFilter里面实例化Service对象,那么这些特性里面要用到相关的服务,该怎么注入呢?

很简单,我们只需要在IOC容器构建时再加上builder.RegisterFilterProvider()即可;

//注册IOC容器
var builder = new ContainerBuilder();
//告诉autofac将来要创建的控制器类存放在哪个程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly());//注册MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());//注册WebAPI控制器
builder.RegisterFilterProvider();//特性注入
//注册Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().InstancePerDependency();
var container = builder.Build();
//将当前容器交给MVC底层,保证容器不被销毁,控制器由autofac来创建
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);//先给WebAPI注册
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));//再给MVC注册

然后就可以直接通过属性的方式注入到ActionFilter中,是的,你没看错,就只需要这一行代码就可以了,特性里面就可以取到想要的服务了;

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public IService Service { get; set; }
    /// <summary>在执行操作方法之前由 ASP.NET MVC 框架调用。</summary>
    /// <param name="filterContext">筛选器上下文。</param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var ps = Service.GetPersons();
        base.OnActionExecuting(filterContext);
    }
}

9.Hangfire注入

如果我们项目中还用到了hangfire这样的分布式任务调度框架,我们也可以通过autofac来进行对象的依赖注入;

首先我们引入hangfire的一些基础包,并配置好hangfire;

当然,还要引入hangfire的autofac支持库:

懒得勤快的博客_全栈开发者_互联网分享精神

Startup.cs类:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        //配置任务持久化到内存
        GlobalConfiguration.Configuration.UseMemoryStorage();
        //启用dashboard
        app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 10 });
        app.UseHangfireDashboard("/taskcenter", new DashboardOptions()
        {
            Authorization = new[] { new MyRestrictiveAuthorizationFilter() }
        }); //注册dashboard的路由地址
    }
}
public class MyRestrictiveAuthorizationFilter : IDashboardAuthorizationFilter
{
    //public RedisHelper RedisHelper { get; set; } = new RedisHelper();
    public bool Authorize(DashboardContext context)
    {
        return true;
    }
}

Global.Application_Start():

Hangfire.GlobalConfiguration.Configuration.UseMemoryStorage();
Hangfire.GlobalConfiguration.Configuration.UseAutofacActivator(container); //注册IOC容器
Server = new BackgroundJobServer(new BackgroundJobServerOptions
{
    ServerName = $"{Environment.MachineName}", //服务器名称
    SchedulePollingInterval = TimeSpan.FromSeconds(1),
    ServerCheckInterval = TimeSpan.FromSeconds(1),
    WorkerCount = Environment.ProcessorCount * 2,
});

配置hangfire后台任务;

public interface IHangfireBackJob
{
    DataContext DataContext { get; set; }
    void UpdatePersons();
}
public class HangfireBackJob : IHangfireBackJob
{
    public DataContext DataContext { get; set; }
    public void UpdatePersons()
    {
        DataContext.Persons.Clear();
        for (int i = 0; i < 10; i++)
        {
            DataContext.Persons.Add(new Person()
            {
                Name = "李四" + i,
                Age = 20 + i * 2,
                Id = i + 1
            });
        }
    }
}

配置IOC容器:

builder.RegisterType<DataContext>().InstancePerBackgroundJob(MatchingScopeLifetimeTags.RequestLifetimeScopeTag, AutofacJobActivator.LifetimeScopeTag); //指定生命周期为每个后台任务依赖,并且每次http请求内单例
builder.RegisterType<BackgroundJobClient>().SingleInstance();//指定生命周期为单例
builder.RegisterType<HangfireBackJob>().As<IHangfireBackJob>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();

创建任务:

public ActionResult CreateJob()
{
    var type = typeof(IHangfireBackJob);
    var job = new Job(type, type.GetMethod("UpdatePersons"));
    BackgroundJobClient.Create(job, new EnqueuedState());
    return Content("ok");
}

懒得勤快的博客_全栈开发者_互联网分享精神

10. SignalR注入

如果我们项目中还用到了SignalR这样的socket通信框架,我们也可以通过autofac来进行对象的依赖注入;按照刚才hangfire的套路,我们先引入SignalR的一些基础包,并配置好SignalR;当然,还要引入SignalR的autofac支持库:

懒得勤快的博客_全栈开发者_互联网分享精神

注册容器的方式和上面的例子都有些不同了,SignalR的IOC容器注入必须在Startup里面注入,不能在Global中注入,因为SignalR是Owin项目,OWIN 集成常见错误为使用GlobalHost。OWIN中配置你会抓狂. OWIN集成中,任何地方你都不能引用 。当年博主我也不太清楚,反正当时也踩了这一大坑。至于hangfire也是owin项目,为什么可以在Global里面注入,因为hangfire不是用GlobalHost去注入的,而是GlobalConfiguration

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var builder = new ContainerBuilder();
        var config = new HubConfiguration();
        builder.RegisterHubs(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
        builder.RegisterType<DataContext>().InstancePerLifetimeScope();
        var container = builder.Build();
        config.Resolver = new AutofacDependencyResolver(container);
        app.UseAutofacMiddleware(container);
        app.MapSignalR("/signalr", config);
    }
}

然后,hub就可以用属性注入,构造函数注入等方式了;

[HubName("myhub")]//声明hub的显式名字
public class MyHub : Hub
{
    public DataContext DataContext { get; set; }
    public void Send()
    {
        Clients.All.hello(DataContext.Persons.ToJsonString());
    }
}

前端代码:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>WebSocket</title>
<script src="~/Scripts/jquery-3.3.1.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.3.js"></script>
<script src="~/signalr/hubs"></script><!--后端SignalR根据注册的路由生成的js脚本-->
</head>
<body>
<span class="msg"></span>
</body>
</html>
<script>
$(function () {
    //客户端都以驼峰命名法使用
    let hubProxy = $.connection["myhub"]; //hub代理对象
    var $msg = $(".msg");
    //注册客户端方法
    hubProxy.client.hello = function (msg) {
        $msg.text(msg);
    }
    //向服务端发数据
    $.connection.hub.start().done(function () {
        hubProxy.server.send();
    });
});
</script>

懒得勤快的博客_全栈开发者_互联网分享精神

注意:由于 SignalR 是内部构件,所以不支持SignalR每请求的生命周期依赖。

11.注册程序集

然而大多数时候我们的项目很多代码是直接用代码生成器生成的,像Repository和Service并非完全手写的,并不想这么一个一个类的去builder.RegisterType,那多麻烦啊,所以autofac还提供了程序集批量注入的选项;一句话搞定:

builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith("Repository") || t.Name.EndsWith("Service")).AsSelf().AsImplementedInterfaces().PropertiesAutowired(PropertyWiringOptions.PreserveSetValues).InstancePerDependency();

这里真的只有一句话!

12. 对象生命周期

InstancePerDependency

对每一个依赖或每一次调用创建一个新的唯一的实例。这也是默认的创建实例的方式。

官方文档解释:Configure the component so that every dependent component or call to Resolve() gets a new, unique instance (default.)

InstancePerLifetimeScope

在一个生命周期域中,每一个依赖或调用创建一个单一的共享的实例,且每一个不同的生命周期域,实例是唯一的,不共享的,也就是线程内唯一对象。

官方文档解释:Configure the component so that every dependent component or call to Resolve() within a single ILifetimeScope gets the same, shared instance. Dependent components in different lifetime scopes will get different instances.

InstancePerMatchingLifetimeScope

在一个做标识的生命周期域中,每一个依赖或调用创建一个单一的共享的实例。打了标识了的生命周期域中的子标识域中可以共享父级域中的实例。若在整个继承层次中没有找到打标识的生命周期域,则会抛出异常:DependencyResolutionException。

官方文档解释:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope tagged with any of the provided tags value gets the same, shared instance. Dependent components in lifetime scopes that are children of the tagged scope will share the parent's instance. If no appropriately tagged scope can be found in the hierarchy an DependencyResolutionException is thrown.

InstancePerOwned

在一个生命周期域中所拥有的实例创建的生命周期中,每一个依赖组件或调用Resolve()方法创建一个单一的共享的实例,并且子生命周期域共享父生命周期域中的实例。若在继承层级中没有发现合适的拥有子实例的生命周期域,则抛出异常:DependencyResolutionException。

官方文档解释:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope created by an owned instance gets the same, shared instance. Dependent components in lifetime scopes that are children of the owned instance scope will share the parent's instance. If no appropriate owned instance scope can be found in the hierarchy an DependencyResolutionException is thrown.

SingleInstance

每一次依赖组件或调用Resolve()方法都会得到一个相同的共享的实例。其实就是单例模式。

官方文档解释:Configure the component so that every dependent component or call to Resolve() gets the same, shared instance.

InstancePerRequest

在一次Http请求上下文中,共享一个组件实例。仅适用于asp.net mvc开发。

13. 需要Dispose的对象的注入

像RedisClient、DbContext这类对象需要用完之后被Dispose的,也很简单;

改造成可Dispose的DataContext:

public class DataContext : IDisposable
{
    public ICollection<Person> Persons { get; set; } = new List<Person>();
    public DataContext()
    {
        for (int i = 0; i < 10; i++)
        {
            Persons.Add(new Person()
            {
                Id = i + 1,
                Name = "张三" + i,
                Age = 10 + i * 2
            });
        }
    }
    /// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary>
    public void Dispose()
    {
        Persons = null;
    }
}

然后在创建IOC容器的时候:

builder.RegisterType<DataContext>().OnRelease(db => db.Dispose());

给对象注册OnRelease事件,每次使用完的时候会由IOC容器去释放。

14. .NetCore中使用Autofac

在 Starpup 中 配置 Autofac,注意的是要将 ConfigureServices 的返回类型从void类型 改成IServiceProvider,并 return new AutofacServiceProvider(ApplicationContainer); 官方解释是,让第三方容器接管Core的默认DI。

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    var builder = new ContainerBuilder();
    builder.RegisterType<DataContext>().InstancePerLifetimeScope();
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith("Repository") || t.Name.EndsWith("Service")).AsSelf().AsImplementedInterfaces().PropertiesAutowired(PropertyWiringOptions.PreserveSetValues).InstancePerDependency();
    builder.Populate(services);
    var container = builder.Build();
    return new AutofacServiceProvider(container);
}



前一版本: 没有了