天天看点

IOC之Autofac

最近在研究Nopcommerce,发现其内部IOC框架使用了autofac,简单的了解了一下,记录一下简单的一些用法。初浅了解下来,有一个很直观的感受,就是对代码几乎没什么侵入性,其他性能之类没有测试,但据说性能极佳。

1. 构造函数注入,如下示例(后面所有说明都是在以下的示例基础上进行的)

public interface IRepository
    {
        void Update();
    }
    public class FirstRepository : IRepository
    {
        public void Update()
        {
            Console.WriteLine("FirstRepository.Update");
        }
    }
    public class SecondRepository : IRepository
    {
        public void Update()
        {
            Console.WriteLine("SecondRepository.Update");
        }
    }

    public class ConcreteController
    {
        private IRepository _repository;
        public ConcreteController(IRepository repository)
        {
            _repository = repository;
        }

        public void Update()
        {
            _repository.Update();
        }
    }
           

调用如下:

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>().AsSelf();//也可直接写成builder.RegisterType<ConcreteController>()
            builder.RegisterType<FirstRepository>().As<IRepository>();//注册FirstRepository为IRespository默认实现
            IContainer container = builder.Build();
            //默认以类参数最多的构造方法进行构造,构造参数根据类型从注册列表中寻找注册的实现类,此处获取的是FirstRepository
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//FirstRepository.Update
           

对同一接口多次注册时,会以最后一次注册的实现类为默认实现类,如下:

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>().AsSelf();//也可直接写成builder.RegisterType<ConcreteController>()
            builder.RegisterType<FirstRepository>().As<IRepository>();
            builder.RegisterType<SecondRepository>().As<IRepository>();//IRepository的默认实现类为SecondRepository
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//SecondRepository.Update
           

如果有这样的一个需求:如果前面注册过了接口的实现类,则以之前注册过的实现类为默认值,如果没有注册过则以此次注册的实现类为默认值,可以通过以下方式实现:

//如果之前存在默认值则以之前注册的值为默认值,否则以此次注册的实现类为默认值
            builder.RegisterType<SecondRepository>().As<IRepository>().PreserveExistingDefaults();
            ...
            controller.Update();//FirstRepository.Update
           

在注册时,可以通过委托的方式,预先指定一个接口的实现类。即采用ContainerBuilder.Register<T>(委托)的方式来进行实现,如下:

ContainerBuilder builder = new ContainerBuilder();
            //通过Register方法,预先指定一个获取实现的委托来注册接口的实现类
            builder.Register<IRepository>(r => new FirstRepository());
            IContainer container = builder.Build();
            IRepository repository = container.Resolve<IRepository>();//FirstRepository
            repository.Update();//FirstRepository.Update
           

autofac构造注入时,在通过无参的IContainer.resolver获取实现类时,会默认通过实现类最多的参数进行构造,构造方法的参数值为该参数类型注册的实现类。如果该参数值的Type没有在autofac进行注册,则直接报错,如下:

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>();
            IContainer container = builder.Build();
            //ConcreteController的构造方法中需要一个IRepository参数,但autofac检索不到IRepository的实现类,注入失败,报错
            //此处不光是针对参数值是引用类型,对于值类型同样存在这样的问题,例如把参数换成int i,同样注入失败,报错
            ConcreteController controller = container.Resolve<ConcreteController>();//error
           

对于这样的问题,可以通过在Resolve 时,自行装配,如下:

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>();
            IContainer container = builder.Build();
            //之前没有对ConcreteController构造方法中的参数repository类型IRepository进行注册,但可在解析提取时自动注入
            //通过NamedParameter,第一个参数指定参数名称,第二个参数为传递的参数值
            ConcreteController controller = container.Resolve<ConcreteController>(new NamedParameter("repository", new FirstRepository()));
            controller.Update();//FirstRepository.Update
           

除了在Resolver时指定参数值,也可以注册该类时默认指定构造方法的参数值。

ContainerBuilder builder = new ContainerBuilder();
            //在注册时就先指定构造方法参数的默认值,即为SecondRepository
            builder.RegisterType<ConcreteController>().WithParameter(new NamedParameter("repository", new SecondRepository()));
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//SecondRepository.Update
           

如果在注册时指定了参数默认值,在Resolver时指定了参数值,则以Resolver指定的参数值为准,如下:

ContainerBuilder builder = new ContainerBuilder();
            //在注册时就先指定构造方法参数的默认值,即为SecondRepository
            builder.RegisterType<ConcreteController>().WithParameter(new NamedParameter("repository", new SecondRepository()));
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>(new NamedParameter("repository", new FirstRepository()));
            controller.Update();//FirstRepository.Update
           

我们之前谈的一直是一个构造方法的情况,假如现在一个实现类有多个构造方法时,autofac会以构造方法参数最多的那个进行注入构造,如下,我们在ConcreteController中新增一个构造方法:

public ConcreteController(IRepository repository, string name)
        {
            Console.WriteLine("two args contructor was invoked");
            _repository = repository;
        }

        ConcreteController controller = container.Resolve<ConcreteController>();//没有调用两个参数的构造方法
           

运行后,发现autofac并没有调用ConcreteController中的两个参数的构造方法进行构造,奇怪了?仔细一瞧,问题就出现在第二个参数string name,还记得之前我们说过,在构造注入时,会检索string在autofac注册的实现类,但此时显示是检索不到(且该参数没有设置默认值例如 string name=null),如果ConcreteController只存在这个构造方法,则会报错,但是还有另一个单个参数的构造方法,因此在试图通过最多参数方法注入不成功时,会转向另一个构造方法,依次下去,直至成功注入为止。我们再换一个方法,新增一个接口,一个类,同时修改ConcreteController第二个构造方法,如下:

public interface IAnotherRepository
    {

    }
    public class AnotherRepository : IAnotherRepository
    {

    }

     public class ConcreteController
    {
        ...
        public ConcreteController(IRepository repository, IAnotherRepository another)
        {
            Console.WriteLine("two args contructor was invoked");
            _repository = repository;
        }
        ...
    }
     ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>();
            builder.RegisterType<SecondRepository>().As<IRepository>();
            builder.RegisterType<AnotherRepository>().As<IAnotherRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();//此处调用了两个参数的构造方法
           

autofac默认调用实现类参数最多的构造方法进行构造,但如果实现类中有好几个参数个数相同的构造方法,autofac如何调用? 例如ConcreteController中有两个如下的构造方法:

public class ConcreteController
    {
        ...

        public ConcreteController(IRepository repository, string name = null)
        {

        }
        public ConcreteController(IRepository repository, IAnotherRepository another)
        {
            Console.WriteLine("two args contructor was invoked");
            _repository = repository;
        }

        ...
    }

    
            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>();
            builder.RegisterType<SecondRepository>().As<IRepository>();
            builder.RegisterType<AnotherRepository>().As<IAnotherRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();//error报错,autofac不知道调用哪个构造方法
           

这种情况如何解决?可以通过在注册实现类时,显示指定哪个构造方法(当存在不同参数的构造方法时,亦可通过此方法来指定不同参数的构造方法作为默认值),如下:

ContainerBuilder builder = new ContainerBuilder();
            //默认指定了public ConcreteController(IRepository repository, string name = null){} 为注入时调用的构造方法
            builder.RegisterType<ConcreteController>().UsingConstructor(typeof(IRepository), typeof(string));
            builder.RegisterType<SecondRepository>().As<IRepository>();
            builder.RegisterType<AnotherRepository>().As<IAnotherRepository>();
            IContainer container = builder.Build();
            //调用public  ConcreteController(IRepository repository, string name = null){}
            ConcreteController controller = container.Resolve<ConcreteController>();
           

2. 一个接口存在多个实现类时

a、ContainerManager.Register<实现类>().Named<接口>(名称) / IContainer.ResolveNamed<接口>(名称)

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<FirstRepository>().Named<IRepository>("first");
            builder.RegisterType<SecondRepository>().Named<IRepository>("second");
            IContainer container = builder.Build();
            IRepository first = container.ResolveNamed<IRepository>("first");//FirstRepository
            IRepository second = container.ResolveNamed<IRepository>("second");//SecondRepository
           

      当普通注册和Name方式共同存在时,如下:

ContainerBuilder builder = new ContainerBuilder();

            builder.RegisterType<ThirdRepository>().As<IRepository>();
            builder.RegisterType<FirstRepository>().Named<IRepository>("first");
            builder.RegisterType<SecondRepository>().Named<IRepository>("second");

            IContainer container = builder.Build();

            IRepository first = container.ResolveNamed<IRepository>("first");//FirstRepository
            IRepository second = container.ResolveNamed<IRepository>("second");//SecondRepository
            //普通方式和Named方式并不矛盾
            IRepository third = container.Resolve<IRepository>();//ThirdRepository
           

b、ContainerManager.Register<实现类>().Keyed<接口>(object) / IContainer.ResolveKeyed<接口>(object)

public enum RepositoryStyle : int
    {
        First,
        Seond,
        Third
    }

      ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ThirdRepository>().As<IRepository>();
            builder.RegisterType<FirstRepository>().Keyed<IRepository>(RepositoryStyle.First);
            builder.RegisterType<SecondRepository>().Keyed<IRepository>(RepositoryStyle.Seond);
            IContainer container = builder.Build();
            IRepository first = container.ResolveKeyed<IRepository>(RepositoryStyle.First);//FirstRepository
            IRepository second = container.ResolveKeyed<IRepository>(RepositoryStyle.Seond);//SecondRepository
            IRepository third = container.Resolve<IRepository>();//ThirdRepository
           

3.  属性注入

autofac不提倡属性注入,属性注入意味着该属性的set必须向外公开,同时就意味着属性可人为的在外部进行修改。这样就可能造成一些隐患(被外部非本意的更改),autofac提倡构造方法进行注入,注入后在外部都不可更改。意味着干净,无污染

把ConcreteController的类修改一下,如下:

public class ConcreteController
    {
        public IRepository Repository { get; set; }
        public void Update()
        {
            Repository.Update();
        }
    }

            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>().AsSelf();
            builder.RegisterType<FirstRepository>().As<IRepository>();
            IContainer container = builder.Build();
            //autofac默认不会进行属性注入
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//NullReferenceException,提示Repository为null
           

方法1:在注册实现类时,通过委托指定实现类的构造(构造过程中显示进行注入),如下:

ContainerBuilder builder = new ContainerBuilder();
            //在注册ConcreteController时,通过委托创建实现类时显式的对属性进行注入
            builder.Register<ConcreteController>(r => new ConcreteController() { Repository = r.Resolve<IRepository>() });
            builder.RegisterType<FirstRepository>().As<IRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//FirstRepository.Update
           

 方法2:使用PropertiesAutowired为所有属性自动注入

ContainerBuilder builder = new ContainerBuilder();
            //自动注入所有属性
            builder.RegisterType<ConcreteController>().PropertiesAutowired();
            builder.RegisterType<FirstRepository>().As<IRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//FirstRepository.Update
           

 方法3:手工为指定属性注入

ContainerBuilder builder = new ContainerBuilder();
            //手工显示为指定属性名称的属性注入
            builder.RegisterType<ConcreteController>().WithProperty("Repository", new FirstRepository());
            builder.RegisterType<FirstRepository>().As<IRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//FirstRepository.Update
           

4. 非泛形的Register/RegisterType/Resolve方法,RegisterType<接口>() 相当于 RegisterType(typeof(接口)) 

ContainerBuilder builder = new ContainerBuilder();
            //作用和builder.RegisterType<FirstRepository>().As<IRepository>();一样的
            builder.RegisterType(typeof(FirstRepository)).As(typeof(IRepository));
            IContainer container = builder.Build();
            //作用和IRepository obj = container.Resolve<IRepository>();
            IRepository obj = container.Resolve(typeof(IRepository)) as IRepository; //FirstRepository
           

5. 开放的泛型类型

public interface IRepository<T>
    {
        void Update(T t);
    }

    public class FirstRepository<T> : IRepository<T>
    {
        public void Update(T t)
        {

            Console.WriteLine("FirstRepository.Update " + t.ToString());
        }
    }
    public class SeondRepository<T> : IRepository<T>
    {
        public void Update(T t)
        {
            Console.WriteLine("SeondRepository.Update " + t.ToString());
        }
    }
     ContainerBuilder builder = new ContainerBuilder();
            //注册泛形接口时,使用RgisterGeneric方法
            builder.RegisterGeneric(typeof(FirstRepository<>)).As(typeof(IRepository<>));
            IContainer container = builder.Build();

            IRepository<string> repository = container.Resolve<IRepository<string>>();
            repository.Update("hehe"); 
           

6. 自动装配

   a、自动为某个接口注册实现类

ContainerBuilder builder = new ContainerBuilder();
            //自动扫描当前程序集中实现了IRepository的实现类,将其中一个实现了IRepository的实现类进行装配
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).As<IRepository>();
            IContainer container = builder.Build();
            IRepository r = container.Resolve<IRepository>();//返回其中一个实现了IRepository的类实例
           

   b、自动为所有接口注册实现类

ContainerBuilder builder = new ContainerBuilder();
            //自动扫描当前程序集,并将所有实现了接口的类注册为该接口的实现类(选取其中的一个实现类)
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();
            IContainer container = builder.Build();
            IRepository obj1 = container.Resolve<IRepository>();//返回其中一个实现了IRepository的类实例,此处返回ThirdRepository
            IAnotherRepository obj2 = container.Resolve<IAnotherRepository>();//返回其中一个实现了IAnotherRepository的类实例,此处返回AnotherRepositor
           

   c、筛选注册

ContainerBuilder builder = new ContainerBuilder();
            //自动扫描当前程序集,搜索所有实现了IRepository且类名包含"Second"的类,并将其中的一个注册为IRepository的实现类
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.Contains("Second")).As<IRepository>();
            IContainer container = builder.Build();
            IRepository obj1 = container.Resolve<IRepository>();//返回SecondRepository
            IAnotherRepository obj2 = container.Resolve<IAnotherRepository>();//error,因为没有为IAnotherRepository注册实现类
           

7. 实例生命周期

   a、ContainerBuilder.Register<实现类>().As<接口>().InstancePerDependency() 用于控制对象的生命周期,每次加载实例时都是新建一个实例,默认就是这种方式

ContainerBuilder builder = new ContainerBuilder();
            //返回的都是新建的实例
            builder.RegisterType<FirstRepository>().As<IRepository>().InstancePerDependency();
            IContainer container = builder.Build();
            IRepository obj1 = container.Resolve<IRepository>();
            IRepository obj2 = container.Resolve<IRepository>();
            Console.WriteLine(ReferenceEquals(obj1, obj2));//false
           

   b、ContainerBuilder.Register<实现类>().As<接口>().InstancePerDependency()

ContainerBuilder builder = new ContainerBuilder();
            //返回的都是同一个实例
            builder.RegisterType<FirstRepository>().As<IRepository>().SingleInstance();
            IContainer container = builder.Build();
            IRepository obj1 = container.Resolve<IRepository>();
            IRepository obj2 = container.Resolve<IRepository>();
            Console.WriteLine(ReferenceEquals(obj1, obj2));//true
           

8. MVC中的应用

MVC用于DI的一个类是DependencyResolver,其中的Current和CurrentCache的静态属性是一个IDependencyResolver。通过该接口的两个方法  object GetService(Type serviceType); IEnumerable<object> GetServices(Type serviceType)来获取实现类。DependencyResolver默认的IDependencyResolver是DefaultDependcyResolver其内部简单的通过反射创建Type的实例。现在我们利用autofac来代替DefaultDependencyResolver。

首先先添加Autofac.Integration.Mvc程序集,其中定义了个实现了IDependencyResolver的类AutofacDependencyResolver,其内部就是使用autofac来实现控制反转的

/// <summary>
    /// 仓储接口
    /// </summary>
    public interface IRepository
    {
        IEnumerable<object> Get(string id);
        bool Update(object entity);
        bool Save(object entity);
        bool Delete(object entity);
    }
    /// <summary>
    /// 仓储实现
    /// </summary>
    public class ConcreteRepository : IRepository
    {
        public IEnumerable<object> Get(string id)
        {
            return new List<object>();
        }

        public bool Update(object entity)
        {
            return true;
        }

        public bool Save(object entity)
        {
            return true;
        }

        public bool Delete(object entity)
        {
            return true;
        }
    }

    
    /// <summary>
    /// 控制器
    /// </summary>
    public class HomeController : Controller
    {
        IRepository repository;

        public HomeController(IRepository repository)
        {
            this.repository = repository;
        }

        public ActionResult Index()
        {

            var list = repository.Get("");//repository为ConcreteRepository
            return View();
        }
    }


    /// <summary>
    /// Global
    /// </summary>
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ContainerBuilder builder = new ContainerBuilder();
            RegisterContainer(builder);
            //Autofac.Integration.Mvc中定义的扩展方法,注册所有控制器本身
            //类似一个个去注册Controller,例如 builder.RegisterType<HomeController>().AsSelf();   
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
            IContainer container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

        }
        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="builder"></param>
        private void RegisterContainer(ContainerBuilder builder)
        {
            //可以为所有接口注册实现类builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();
            builder.RegisterType<ConcreteRepository>().As<IRepository>();
            
        }
    }