2.1 依赖注入

依赖注入

  • 什么是依赖注入

    • 传统方式的问题
    • 解决方案
      • 构造函数注入模式
      • 属性注入模式
      • 依赖注入框架
  • ABP依赖注入基础设施
    • 注册依赖注入

      • 传统注册
      • 帮助接口
      • 自定义/直接注册
        • 使用IocManager
    • 分析
    • 构造函数和属性注入
    • IIocResolver,IIocManager和IScopedIocResolver
    • 附加的
      • IShouldInitialize接口
    • ASP.NET MVC和ASP.NET Web API集成
    • ASP.NET Core 集成
    • 最后建议

什么是依赖注入?

  如果你已经知道依赖注入,构造函数和属性注入模式,可以直接跳到下一部分。

  维基百科说:“依赖注入是一种软件设计模式,一个或多个依赖项(或服务)被注入或通过引用传递到一个依赖对象,并且成为客户端状态的一部分。这种模式把客户端依赖项的创建从它自己的行为中分离出来,允许程序设计成松耦合的,遵循依赖倒置和单一职责的原则。和服务定位器模式相比,它允许客户端知道他们使用的系统查找依赖项。”

  不使用依赖注入技术,很难管理依赖项和发布一个结构良好的应用。

传统方法的问题

  在一个应用里,类之间相互依赖。假设我们有一个使用仓储插入实体到数据的应用服务,在这种情况下,这个应用服务类依赖仓储类。如下示例:

public class PersonAppService
{
    private IPersonRepository _personRepository;

    public PersonAppService()
    {
        _personRepository = new PersonRepository();
    }

    public void CreatePerson(string name, int age)
    {
        var person = new Person { Name = name, Age = age };
        _personRepository.Insert(person);
    }
}

  PersonAppService使用PersonRepository向数据库中插入一个Person实体。这段代码有以下问题:

  • PersonAppService在CreatePerson方法中使用IPersonRepository引用,所以这个方法依赖于IpersonRepository而不是PersonRepository的具体类。但是PersonAppService依然在构造函数中依赖PersonRepository。组件应该依赖于接口而不是实现。这就是被熟知的控制反转原则。
  • 如果PersonAppService自己创建PersonRepository,就变成依赖于IPersonRepository接口的具体实现,这样就不能使用另一种实现了。从而,从实现中分离接口就没有意义了。硬依赖使代码紧耦合、低复用。
  • 我们可能在将来会改变PersonRepository的创建。比如说,我们想把它改成单例的(单独共享的实例而不是每次使用都创建一个对象),或者我们想创建多个实现IPersonRepository的类并且我们想根据条件实例化他们中的一个。在这种情况下,我们要更改所有依赖IPersonRepository的类。
  • 使用这样的一个依赖项,对PersonAPPService进行单元测试时很困难(或者不可能)的。

  可以使用工厂模式克服这些问题中的一部分。因此,仓库类的创建应该是抽象的。请看如下代码:

public class PersonAppService
{
    private IPersonRepository _personRepository;

    public PersonAppService()
    {
        _personRepository = PersonRepositoryFactory.Create();
    }

    public void CreatePerson(string name, int age)
    {
        var person = new Person { Name = name, Age = age };
        _personRepository.Insert(person);
    }
}

  PersonRepositoryFactory是一个静态类,它创建并返回一个IPersonRepository。这就是被熟知的服务定位器模式。创建问题解决了,因为PersonAppService不知道如何创建IPersonRepository的实现,并且独立于PersonRepository的实现。但是,仍然有一些问题:

  • 现在,PersonAppService依赖于PersonRepositoryFactory。这样访问性更好但依然是硬依赖。
  • 为每一个仓储或依赖项写一个工厂类或方法是乏味的。
  • 不容易测试,因为很难使用PersonAppService使用一些IPersonRepository的模拟实现。

解决方案

  有一些依赖其他类的最佳实践(模式)。

构造函数注入模式

  上面的实例可以按如下所示的重写:

public class PersonAppService
{
    private IPersonRepository _personRepository;

    public PersonAppService(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }

    public void CreatePerson(string name, int age)
    {
        var person = new Person { Name = name, Age = age };
        _personRepository.Insert(person);
    }
}

  这就是构造函数注入。现在,PersonAppService不知道哪个类实现了IPersonRepository,也不知道怎么创建它。想使用PersonAPPService,首先创建一个IPersonRepository,把它传递给PersonAppService的构造函数,如下所示:

var repository = new PersonRepository();
var personService = new PersonAppService(repository);
personService.CreatePerson("Yunus Emre", 19);

  构造函数注入是使类独立于依赖对象创建的完美方法。但是,上面的代码有一些问题:

  • 创建PersonAppService变得困难。想象如果它有4个依赖项,我们必须创建4个依赖对象并把他们传递给PersonAppService的构造函数。
  • 依赖类可能有其他依赖项(这里,PersonRepository可能有其他依赖项)。所以,我们必须创建PersonAPPService的所有依赖项,所有依赖项的依赖项等等。如果这样的话,我们可能创建不了一个对象,应为依赖图太复杂。

  幸运的是,有依赖注入框架自动管理依赖项。

属性注入模式

  构造函数注入模式是提供类依赖项的完美方法。用这种方式,不提供依赖项就不能创建类的实例。也使用强方式显示的声明类工作所需要的条件。

  但是,在一些情况下,依赖于另一个类的类没有它也可以工作。这通常适用于横切关注点如日志。类可以没有日志照样工作,但是如果提供一个记录器它就可以写日志。在这种情况下,可以把依赖项定义成公共属性而不是在构造函数中获取他们。想象我们想在PersonAppService中写日志。我们可以按如下方式重写这个类:

public class PersonAppService
{
    public ILogger Logger { get; set; }

    private IPersonRepository _personRepository;

    public PersonAppService(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
        Logger = NullLogger.Instance;
    }

    public void CreatePerson(string name, int age)
    {
        Logger.Debug("Inserting a new person to database with name = " + name);
        var person = new Person { Name = name, Age = age };
        _personRepository.Insert(person);
        Logger.Debug("Successfully inserted!");
    }
}

  NullLogger.Instance是一个实现ILogger的单例对象,但是实际上什么也不做(不写日志,它使用空方法体实现ILogger)。所以,现在,如果按如下方式创建PersonAppService对象后设置记录器,它就可以写日志了:

var personService = new PersonAppService(new PersonRepository());
personService.Logger = new Log4NetLogger();
personService.CreatePerson("Yunus Emre", 19);

  假定Log4NetLogger实现了ILogger,且使用Log4Net类库写日志。从而,PersonAppService实际上可以写日志了。如果我们没有设置记录器,它就不写日志。所以,我们可以说ILogger是PersonAppService一个可选的依赖项。
  几乎所有的依赖注入框架都支持属性注入模式。

依赖注入框架

  有许多可以自动解析依赖项的依赖注入框架。他们可以创建对象以及其所有依赖项(可以递归依赖的依赖)。所以,只要使用构造函数、属性注入模式写类,DI框架会处理剩下的工作!在一个好的应用里,类甚至独立于DI框架。在整个应用里,只有几行或几个类显示的与DI框架交互。

  ABP使用Castle Windsor作为依赖注入框架。它是最成熟的框架之一。还有许多其他的框架,如Unity,Ninject,StructureMap,Autofac等等。

  在一个依赖注入框架里,首先需要注册接口或类到依赖注入框架,然后就可以解析(创建)一个对象了。在Castle Windsor里,就像如下所示:

var container = new WindsorContainer();

container.Register(
        Component.For<IPersonRepository>().ImplementedBy<PersonRepository>().LifestyleTransient(),
        Component.For<IPersonAppService>().ImplementedBy<PersonAppService>().LifestyleTransient()
    );

var personService = container.Resolve<IPersonAppService>();
personService.CreatePerson("Yunus Emre", 19);

  

  我们首先创建WindsorContainer。然后使用接口注册PersonRepository和PersonAppService。我们让容器创建一个IPersonAppService。它创建PersonAppService及其依赖项并返回。在这个简单例子里,使用DI框架的优势并不是很明显,但是设想一下在一个真实的企业应用里将会有许多的类和依赖项。当然,注册依赖项只在应用启动的时候注册一次,创建和使用对象可能会在其他的地方。

  注意,我们把对象的生命周期生命为短暂的。意味着无论什么时候我们需要类型的一个对象时,一个新的实例将会被创建。还有许多其他不同的生命周期(如单例)。

ABP依赖注入基础设施

  当你遵循最佳实践和一些约定编写应用的时候,ABP几乎使依赖注入框架的使用无感知的。

注册依赖项

  在ABP中有不同的方式注册类到依赖注入系统。大多数时候,常规的注册就足够了。

常规注册

  ABP默认会自动注册所有的仓储、领域服务、应用服务、MVC控制器和Web API控制器。例如,你有一个IPersonAppService接口和一个它的实现类PersonAppService:

public interface IPersonAppService : IApplicationService
{
    //...
}

public class PersonAppService : IPersonAppService
{
    //...
}

  因为它实现了IApplicationService(是一个空接口),所以ABP会自动注册它。它会被注册为暂时的(每次使用时创建实例)。当你注入(使用构造函数注入)IPersonAppService接口到一个类的时候,PersonAppService对象会被创建并且自动传递给构造函数。

  命名约定在这里是非常重要的。例如,你可以把PersonAppService的名称改为MyPersonAppService或者其他包含‘PersonAppService‘后缀的名字,因为IPersonAppService有这个后缀。但是不可以把服务命名为PeopleService。如果这样做了,就不会自动注册IPersonAppService了(它使用自注册方式注册到DI框架,不使用接口),所以,如果想要注册的话可以手动注册。

  ABP可以根据约定注册程序集。可以让ABP根据约定注册程序集。这是相当简单的:

IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());

  Assembly.GetExeutingAssembly()获取包含这些代码的程序集引用。你可以传递其他程序集到RegisterAssemblyByConvention方法。这些通常在模块初始化的时候执行。可参见ABP模块系统了解更多。

  可以编写自定义的约定注册类,需要实现IConventionalRegisterer接口,并且在类里调用IocManager.AddConventionalRegiisterer方法。需要添加到模块的preinitialize方法中。

帮助接口

  你可能想注册一个特定的类,但这个类不符合约定注册规则。ABP提供了ITransientDependency和ISingletonDependency接口作为捷径。例如:

public interface IPersonManager
{
    //...
}

public class MyPersonManager : IPersonManager, ISingletonDependency
{
    //...
}

  这样,就可以很容易注册MyPersonManager。当需要注入IPersonManager时,会使用MyPersonManager类。注意,依赖声明为单例的。因此,MyPersonManager创建为单例的,所有需要的类都会传入相同的对象。只有在首次使用的时候创建,在应用的整个生命周期都会使用相同的实例。

自定义或直接注册

  如果约定注册不能满足的话,一颗使用IocManager或Castle Windsor注册类和依赖项。

使用IocManager

  可以使用IocManager注册依赖项(通常在模块定义类的PreInitialize方法中):

IocManager.Register<IMyService, MyService>(DependencyLifeStyle.Transient);

使用Castle Windsor API

  可以使用IIocManager.IocContainer属性访问Castle Windsor容器和注册依赖项。例如:
  

IocManager.IocContainer.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());

  了解更多信息,参见Windsor文档

解析

  注册会通知IOC(Inversion of control)容器(a.k.a DI框架)知道有哪些类、依赖项和生命周期。在应用的一些地方,需要使用IOC容器创建对象。ABP提供了一些解析依赖的选择。

构造函数和属性注入

  可以使用构造函数和属性注入获得类的依赖项作为最佳实践。应该在任何可能的地方这样做。例如:

public class PersonAppService
{
    public ILogger Logger { get; set; }

    private IPersonRepository _personRepository;

    public PersonAppService(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
        Logger = NullLogger.Instance;
    }

    public void CreatePerson(string name, int age)
    {
        Logger.Debug("Inserting a new person to database with name = " + name);
        var person = new Person { Name = name, Age = age };
        _personRepository.Insert(person);
        Logger.Debug("Successfully inserted!");
    }
}

  IPersonRepository通过构造函数注入,ILogger使用公共属性注入。这样,代码将不会感知到依赖注入系统。这是使用DI系统最合适的方式。

IIocResolver,IIocManager和IScopedIocResolver

  或许需要直接解析依赖而不是通过构造函数和属性注入。这种情况可能的话应该避免,但是有时候是不可避免的。ABP提供了提供了一些可以被注入且易用的服务。例如:

public class MySampleClass : ITransientDependency
{
    private readonly IIocResolver _iocResolver;

    public MySampleClass(IIocResolver iocResolver)
    {
        _iocResolver = iocResolver;
    }

    public void DoIt()
    {
        //Resolving, using and releasing manually
        var personService1 = _iocResolver.Resolve<PersonAppService>();
        personService1.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" });
        _iocResolver.Release(personService1);

        //Resolving and using in a safe way
        using (var personService2 = _iocResolver.ResolveAsDisposable<PersonAppService>())
        {
            personService2.Object.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" });
        }
    }
}

  MySampleClass是个示例类。它通过构造函数注入IIcResolver并且使用它解析和释放对象。有几个Resolve的重载方法可以在需要的时候使用。Release方法用来释放组件(对象)。如果手动解析了一个对象,那么调用Release方法是很关键的。要不然,应用可能忽悠内存泄露的问题。为了确保释放对象,在任何可能的地方使用ResolveAsDisposable(如上例所示)。它会在using块的结束为止自动调用Release。

  IIocResolver(和IIocManager)有CreateScope扩展方法(定义在Abp.Dependency命名空间),用来安全释放所有解析的依赖对象。例如:

using (var scope = _iocResolver.CreateScope())
{
    var simpleObj1 = scope.Resolve<SimpleService1>();
    var simpleObj2 = scope.Resolve<SimpleService2>();
    //...
}

  在using块的末尾,所有解析的依赖对象自动移除。使用IScopedIocResolver,作用范围也是可以注入的。可以注入这个接口用来解析所有依赖项。当类被释放的时候,所有解析的依赖项也会被释放。但是,需要小心使用;例如,如果有个类有很长的生命(比方是单例)并且解析了大量的对象,所有的这些类都会存在内存中,直到这个类被释放。

  如果想直接使用IOC容器(Castle Windsor)解析依赖项,可以使用构造函数注入IIocManager并使用IIocManager.IocContainer属性。如果在一个静态上下文或者不能注入IIocManager,作为最后的选择,可以使用单例对象IocManager.Instance。但是,这样的话代码就不易测试了。

额外部分

IShouldInitialize 接口

  一些类需要在首次使用的时候初始化。IShouldInitialize有一个Initialize()方法。如果实现了它,当创建对象后(在使用前)Initialize()方法会自动调用。当然,可以注入或解析这个对象以便使用这个特征。

ASP.NET MVC和ASP.NET Web API集成

  我们必须调用依赖注入系统来解决在依赖图中的根对象。在ASP.NET MVC应用里,它通常为一个控制器类。我们可以在控制器里使用构造函数和属性注入模式。当请求到达应用时,控制器使用IOC容器创建,所有的依赖递归解析。所以,谁做这些呢?这些由ABP通过扩展ASP.NET MVC的默认控制器工厂自动完成。同样,ASP.NET Web API也是这样的。不用关心创建和释放对象。

ASP.NET Core集成

  ASP.NET Core 已经有一个内置的依赖注入系统,在Microsoft.Extensions.DependencyInjection包里。ABP使用Castle.Windsor.MsDependencyInjection包集成和ASP.NET Core的依赖注入系统。所以,不需要关心它。

最后建议

  ABP简化且自动使用依赖入住,只要遵循规则并使用上述的结构。大多数时候不需要更多的依赖注入。但是,如果需要,可以直接使用Castle Windsor的力量执行任何任务(如自定义注册,注入钩子,拦截等等)。

返回主目录

时间: 2024-10-13 16:09:05

2.1 依赖注入的相关文章

iOS控制反转(IoC)与依赖注入(DI)的实现

背景 最近接触了一段时间的SpringMVC,对其控制反转(IoC)和依赖注入(DI)印象深刻,此后便一直在思考如何使用OC语言较好的实现这两个功能.Java语言自带的注解特性为IoC和DI带来了极大的方便,要在OC上较好的实现这两个功能,需要一些小小的技巧. 控制反转和依赖注入 控制反转 简单来说,将一个类对象的创建由手动new方式改为从IOC容器内获取,就是一种控制反转,例如我们现在要创建一个ClassA类,则常规方法为 ClassA *a = [ClassA new]; 如果使用控制反转,

详解 Spring 3.0 基于 Annotation 的依赖注入实现(转)

使用 @Repository.@Service.@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发.@Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean.具体只需将该注解标注在 DAO 类上即可.同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用 Bean

工厂模式、控制反转及依赖注入

在介绍工厂模式与控制反转(Inversion of Control)及依赖注入(Dependency Injection)之前,先介绍下类的调用方法.目前调用方法总共有3种:1.自己创建:2.工厂模式:3.外部注入,其中外部注入即为控制反转/依赖注入模式(IoC/DI).我们可以用3个形象的东西来分别表示它们,就是new.get.set.顾名思义,new表示自己创建,get表示主动去取(即工厂),set表示是被别人送进来的(即注入),其中get和set分别表示了主动去取和等待送来两种截然相反的特

spring中依赖注入方式总结

Spring中依赖注入的四种方式 在Spring容器中为一个bean配置依赖注入有三种方式: · 使用属性的setter方法注入  这是最常用的方式: · 使用构造器注入: · 使用Filed注入(用于注解方式). 使用属性的setter方法注入 首先要配置被注入的bean,在该bean对应的类中,应该有要注入的对象属性或者基本数据类型的属性.例如:为UserBiz类注入UserDAO,同时为UserBiz注入基本数据类型String,那么这时,就要为UserDAO对象和String类型设置se

(七)理解angular中的module和injector,即依赖注入

依赖注入(DI)的好处不再赘言,使用过spring框架的都知道.angularjs作为前台js框架,也提供了对DI的支持,这是javascript/jquery不具备的特性.angularjs中与DI相关有angular.module().angular.injector(). $injector.$provide.对于一个DI容器来说,必须具备3个要素:服务的注册.依赖关系的声明.对象的获取.比如spring中,服务的注册是通过xml配置文件的<bean>标签或是注解@Repository.

C# 对轻量级(IoC Container)依赖注入Unity的使用

概述 Unity是一个轻量级的可扩展的依赖注入容器,支持构造函数,属性和方法调用注入.Unity可以处理那些从事基于组件的软件工程的开发人员所面对的问题.构建一个成功应用程序的关键是实现非常松散的耦合设计.松散耦合的应用程序更灵活,更易于维护.这样的程序也更容易在开发期间进行测试.你可以模拟对象,具有较强的具体依赖关系的垫片(轻量级模拟实现),如数据库连接,网络连接,ERP连接,和丰富的用户界面组件.例如,处理客户信息的对象可能依赖于其他对象访问的数据存储,验证信息,并检查该用户是否被授权执行更

【ASP.Net MVC3 】使用Unity 实现依赖注入

什么是Unity? Unity是一个轻量级的可扩展的依赖注入容器,支持构造函数,属性和方法调用注入.Unity可以处理那些从事基于组件的软件工程的开发人员所面对的问题.构建一个成功应用程序的关键是实现非常松散的耦合设计.松散耦合的应用程序更灵活,更易于维护.这样的程序也更容易在开发期间进行测试.你可以模拟对象,具有较强的具体依赖关系的垫片(轻量级模拟实现),如数据库连接,网络连接,ERP连接,和丰富的用户界面组件.例如,处理客户信息的对象可能依赖于其他对象访问的数据存储,验证信息,并检查该用户是

运用Unity实现依赖注入[结合简单三层实例]

一:理论部分 依赖注入:这是 Ioc 模式的一种特殊情况,是一种基于改变对象的行为而不改变类的内部的接口编程技术.开发人员编写实现接口的类代码,并基于接口或者对象类型使用容器注入依赖 的对象实例到类中.用于注入对象实例的技术是接口注入.构造函数注入.属性(设置器)注入和方法调用注入. Unity是微软企业库一部分,是一个轻量级.可扩展的依赖注入容器,支持构造函数.属性和方法调用注入: 针对依赖注入以前我也写过一篇结合三层的文章:spring.net 结合简单三层实例 二:实例简介 1:本实例将通

Asp.Net.Core 系列-中间件和依赖注入进阶篇

上一节讲了中间件和依赖注入的基础,紧接着: 中间件是怎么使用的?使用步骤是什么? 只要把中间件注册到管道中就行了,可以借助Startup对象(DelegateStartup或者ConventionBasedStartup)来完成之外,也可以利用另一个叫做StartupFilter的对象来实现.所谓的StartupFilter是对所有实现了IStartupFilter接口的类型及其对象的统称.IStartupFilter接口定义了如下一个唯一的方法Configure,该方法的参数next返回的Ac

再说php依赖注入

前段时间,有朋友问我yii2的依赖注入是怎么个玩法,好吧, 经常看到却一直不甚理解的概念,这里我再对自己认识的依赖注入深刻的表达下我的理解,依赖注入(DI)以及控制器反转(Ioc). 依赖注入就是组件通过构造器,方法或者属性字段来获取相应的依赖对象. 举个现实生活中的例子来理解, 比如我要一把菜刀 如何获得1.可以自己造一把,对应new一个.2.可以找生产菜刀的工厂去买一把,对应工厂模式.3.可以打电话 让店家送货上门,对应依赖注入 依赖注入(DI)的概念虽然听起来很深奥,但是如果你用过一些新兴