依赖注入

什么是依赖注入?

如果您已经知道依赖注入,构造函数和属性注入模式概念,则可以跳到下一部分

维基百科说:“ 依赖注入是一种软件设计模式,其中一个或多个依赖关系(或服务)被注入或通过引用传递到依赖对象(或客户端),并成为客户端状态的一部分。从自己的行为中创建客户端的依赖关系,允许程序设计松散耦合并遵循依赖性倒置和单一责任原则。它直接对比服务定位器模式,允许客户端了解他们用于查找依赖关系的系统。 “

在不使用依赖注入技术的情况下,很难管理依赖关系并开发模块化且结构良好的应用程序。

传统方式的问题

在应用程序中,类依赖于彼此。假设我们有一个使用 仓储 将 实体插入数据库的 应用程序服务在这种情况下,应用程序服务类依赖于仓储类。请参阅以下示例:

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使用PersonRepositoryPerson插入数据库。虽然这看起来无害,但这段代码存在一些问题:

为了克服其中一些问题,可以使用工厂模式。因此,抽象了存储库类的创建。请参阅以下代码:

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实现,因此解决了创建问题。还有一些问题:

解决方案

有一些最佳实践(模式)可以帮助我们依赖其他类。

构造函数注入模式

上面的例子可以重写,如下所示:

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("John Doe", 32);

构造函数注入是使类独立于依赖对象的创建的一种很好的方法,但是上面的代码存在一些问题:

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

物业注入模式

构造函数注入模式是提供类的依赖关系的好方法。这样,您无法在不提供依赖项的情况下创建类的实例。它也是一种强有力的方式,可以明确地声明类的要求,以便它可以正常工作。

在某些情况下,该类可能依赖于另一个类,但可以在没有它的情况下工作。对于诸如记录(日志)之类的跨领域问题,这通常是正确的。类可以在没有日志记录的情况下工作,但如果您向其提供记录器,它可以写入日志。在这种情况下,您可以将依赖项定义为公共属性,而不是在构造函数中获取它们。想想我们如何写入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对象后设置Logger属性,PersonAppService可以编写日志:

var personService = new PersonAppService(new PersonRepository());
personService.Logger = new Log4NetLogger();
personService.CreatePerson("John Doe", 32);

假设Log4NetLogger实现ILogger并使用Log4Net库写入日志,以便PersonAppService可以实际写入日志。如果我们不设置Logger,它就不会写日志。我们可以说ILogger是PersonAppService 可选依赖项。

几乎所有依赖注入框架都支持Property Injection模式。

依赖注入框架

有许多依赖注入框架可以自动解决依赖关系。他们可以递归地创建具有所有依赖项和依赖项依赖项的对象。只需使用构造函数和属性注入模式编写类,DI框架将处理其余的!在一个好的应用程序中,您的类甚至可以独立于DI框架。在整个应用程序中,只有几行代码或类与DI框架明确交互。

ASP.NET Boilerplate使用Castle Windsor 框架进行依赖注入。它是最成熟的DI框架之一。还有许多其他框架,例如Unity,Ninject,StructureMap和Autofac。

在依赖注入框架中,首先将接口/类注册到依赖注入框架,然后解析(创建)对象。在温莎城堡,它是这样的:

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("John Doe", 32);

首先,我们创建了WindsorContainer 并使用它们的接口注册了 PersonRepository和PersonAppService。然后,我们使用容器创建IPersonAppService。它使用它的依赖项创建了具体类PersonAppService,然后返回它。在这个简单的例子中,可能不清楚使用DI框架的优点是什么。但是,您将在真实的企业应用程序中拥有许多类和依赖项。依赖项的注册与对象的创建和使用是分开的,并且在应用程序启动期间只进行一次。

请注意,我们还将对象生命周期设置瞬态这意味着每当我们解析这些类型的对象时,都会创建一个新实例。例如,有许多不同的生命周期,例如单一生命周期

ASP.NET Boilerplate依赖注入基础结构

ASP.NET Boilerplate使得依赖注入框架几乎不可见。它还可以帮助您按照最佳实践和约定编写应用程序。

注册依赖项

在ASP.NET Boilerplate中有不同的方法将类注册到依赖注入系统。大多数时候,传统的注册就足够了。

常规注册

ASP.NET Boilerplate按照惯例自动注册所有 仓储,领域服务应用程序服务,MVC控制器和Web API控制器。例如,您可能有一个IPersonAppService接口和一个实现它的PersonAppService类:

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

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

ASP.NET Boilerplate自动注册它,因为它实现了 IApplicationService接口(它只是一个空接口)。它被注册为瞬态,意味着每次使用时都会创建它。当您将IPersonAppService接口(使用构造函数注入)注入到类中时,将自动创建PersonAppService对象并将其传递到构造函数中。

命名约定在这里非常重要。例如,您可以将PersonAppService的名称更改为MyPersonAppService或包含“PersonAppService”后缀的其他名称。这会将其注册到IPersonAppService,因为它具有相同的后缀。但是,如果没有后缀,则不能命名您的服务,例如“PeopleService”。如果这样做,它不会自动注册到IPersonAppService。相反,它使用自注册(而不是接口)注册到DI框架。在这种情况下,您可以手动注册它。

ASP.NET Boilerplate可以按惯例注册程序集。这很简单:

IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());

Assembly.GetExecutingAssembly()获取对包含此代码的程序集的引用。您可以将其他程序集传递给RegisterAssemblyByConvention方法。这通常在初始化模块时完成。有关详细信息,请参阅ASP.NET Boilerplate的模块系统

您可以通过实现IConventionalRegisterer接口然后在类中调用 IocManager.AddConventionalRegisterer方法来编写自己的常规注册 类。您应该在模块的预初始化方法中添加它。

帮助程序接口

您可能希望注册不符合传统注册规则的特定类。ASP.NET Boilerplate提供 ITransientDependencyIPerWebRequestDependencyISingletonDependency接口作为快捷方式。例如:

public interface IPersonManager
{
    //...
}

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

这样,您就可以轻松注册MyPersonManager。当您需要注入IPersonManager时,使用MyPersonManager类。请注意,依赖项声明为Singleton创建单个MyPersonManager实例,并将相同的对象传递给所有需要的类。它在第一次使用时被实例化,然后在应用程序的整个生命周期中使用。

注:IPerWebRequestDependency只能在网络层中使用。

自定义/直接注册

如果传统注册不能满足您的需求,您可以使用IocManagerCastle 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(控制反转)容器(也称为DI框架)关于您的类,它们的依赖关系和生命周期。在应用程序的某处,您需要使用IOC容器创建对象。ASP.NET提供了一些用于解决依赖关系的选项。

构造函数和属性注入

作为最佳实践,您应该使用构造函数和属性注入来获取类的依赖项。你应该尽可能这样做。例:

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

您可能必须直接解决您的依赖项,而不是使用构造函数和属性注入。应尽可能避免这种情况,但可能无法实现。ASP.NET Boilerplate提供了一些可以轻松注入和使用的服务。例:

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 = "John", Surname = "Doe" });
        _iocResolver.Release(personService1);

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

应用程序中的示例类中的MySampleClass。它是使用IIocResolver构造注入的,并使用它来解析和释放对象。Resolve方法有一些重载可以根据需要使用。推出方法被用来释放的成分(对象)。这是 关键的调用Release如果你手动解决的对象。否则,您的应用程序可能会有内存泄漏。要确保释放对象,请尽可能使用ResolveAsDisposable(如上例所示)。在使用块结束时自动调用释放。

IIocResolver(和IIocManager)还具有CreateScope扩展方法(在Abp.Dependency命名空间中定义),以安全地释放所有已解析的依赖项。例:

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

在使用块结束时,将自动删除所有已解析的依赖项。范围也可以使用IScopedIocResolver进行注入您可以注入此接口并解析依赖关系。发布类后,将释放所有已解析的依赖项。仔细使用!如果你的类有很长的生命(说明它是一个单例),而你正在解析太多的对象,那么所有这些对象都会留在内存中,直到你的类被释放。

如果要直接到达IOC容器(Castle Windsor)来解析依赖关系,可以构造函数注入IIocManager并使用IIocManager.IocContainer属性。如果您处于静态上下文或无法注入IIocManager,作为最后的手段,您可以在任何地方使用单个对象IocManager.Instance但是,在这种情况下,您的代码将不容易测试。

附加功能

IShouldInitialize接口

有些类需要在首次使用之前进行初始化。IShouldInitialize有一个Initialize()方法。如果实现它,那么在创建对象之后(在使用之前)会自动调用Initialize()方法。您需要注入/解析对象才能使用此功能。

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

我们必须调用依赖注入系统来解析依赖图中的根对象。ASP.NET MVC应用程序中,它通常是一个Controller类。我们还可以在控制器中使用构造函数和属性注入模式。当请求到达我们的应用程序时,使用IOC容器创建控制器,并且递归地解析所有依赖项。是怎么回事?这一切都是由ASP.NET Boilerplate通过扩展ASP.NET MVC的默认控制器工厂自动完成的。对于ASP.NET Web API也是如此。您不必担心创建和处理对象。

ASP.NET Core 集成

ASP.NET Core已经有一个带有Microsoft.Extensions.DependencyInjection 包的内置依赖注入系统 ABP使用Castle.Windsor.MsDependencyInjection 包将它的依赖注入系统与ASP.NET Core集成,因此您不必考虑它。

最后的笔记

只要遵循规则并使用上述结构,ASP.NET Boilerplate就可以简化和自动化依赖注入。大多数时候你不需要更多。如果您确实需要更多,您可以直接使用Castle Windsor的原始功能来执行许多任务,例如自定义注册,注射钩子,拦截器等。

nidie.com.cn - 用心与你沟通