ABP框架系列之二十:(Dependency-Injection-依赖注入)

时间:2022-05-21 09:40:42

What is Dependency Injection

If you already know Dependency Injection concept, Constructor and Property Injection patterns, you can skip to the next section.

Wikipedia says: "Dependency injection is a software design pattern in which one or more dependencies (or services) are injected, or passed by reference, into a dependent object (or client) and are made part of the client's state. The pattern separates the creation of a client's dependencies from its own behavior, which allows program designs to be loosely coupled and to follow the dependency inversion and single responsibility principles. It directly contrasts the service locator pattern, which allows clients to know about the system they use to find dependencies.".

It's very hard to manage dependencies and develop a modular and well structured application without using dependency injection techniques.

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

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

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

Problems of Traditional Way(传统方法问题)

In an application, classes depend on each other. Assume that we have an application service that uses a repository to insert entities to database. In this situation, the application service class is dependent to the repository class. See the example:

在应用程序中,类彼此依赖。假设我们有一个应用程序服务,它使用存储库将实体插入数据库中。在这种情况下,应用程序服务类依赖于存储库类。看例子:

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 uses PersonRepository to insert a Person to the database. Problems of this code:

  • PersonAppService uses IPersonRepository reference in CreatePerson method, so this method depends on IPersonRepository, instead of PersonRepository concrete class. But the PersonAppService still depends on PersonRepository in it's constructor. Components should depend on interfaces rather than implementation. This is known as Dependency Inversion principle.
  • personappservice使用ipersonrepository参考createperson方法,所以这种方法取决于ipersonrepository,代替personrepository具体类。但personappservice仍取决于personrepository在它的构造函数。组件应该依赖于接口而不是实现。这就是所谓的依赖倒置原则。
  • If the PersonAppService creates the PersonRepository itself, it become dependent to a specific implementation of IPersonRepository interface and can not work with another implementation. Thus, separating interface from implementation becomes meaningless. Hard-dependencies makes code base tightly coupled and low re-usable.
  • 如果personappservice creates the personrepository itself to a specific依赖性,它成为ipersonrepository and Implementation of work with another接口不能实现。
    因此,从分离成为接口实现。让代码低耦合可重用

  • We may need to change creation of PersonRepository in the future. Say, we may want to make it singleton (single shared instance rather than creating an object for each usage). Or we may want to create more than one class those implement IPersonRepository and we want to create one of them conditionally. In this situation, we should change all classes that depends on IPersonRepository.
  • 我们可能需要在未来的创作personrepository变化。例如,我们可能想使它成为单例(单个共享实例,而不是为每个用法创建对象)。或者我们可以创建多个类的实现ipersonrepository我们想创造条件其中之一。在这种情况下,我们应该改变的所有类,取决于IPersonRepository。
  • With such a dependency, it's very hard (or impossible) to unit test the PersonAppService.
  • 这样的依赖,很难(或不可能)对单元测试的personappservice。

To overcome some of these problems, factory pattern can be used. Thus, creation of repository class is abstracted. See the code below:

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 is a static class that creates and returns an IPersonRepository. This is known as Service Locator pattern. Creation problems are solved since PersonAppService does not know how to create an implementation of IPersonRepository and it's independent from PersonRepository implementation. But,still there are some problems:

personrepositoryfactory是一个静态类,创建并返回一个ipersonrepository。这称为服务定位器模式。创作的问题都解决了personappservice以来不知道如何创建一个实现ipersonrepository是独立于personrepository实施。但是,仍然存在一些问题:

  • In this time, PersonAppService depends on PersonRepositoryFactory. This is more acceptable but still there is a hard-dependency.
  • It's tedious to write a factory class/method for each repository or for each dependency.
  • It's not well testable again, since it's hard to make PersonAppService to use some mock implementation of IPersonRepository.
  • 在这个时候,personappservice取决于personrepositoryfactory。这是可以接受的,但仍然存在硬依赖。
    为每个存储库或每一个依赖项编写一个工厂类/方法是单调乏味的。
    没有很好的测试了,因为让personappservice使用ipersonrepository模拟实现很难。

Solution

There are some best practices (patterns) to depend on other classes.

Constructor Injection Pattern(构造函数注入)

The example above can be re-written as shown below:

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);
}
}

This is known as constructor injection. Now, PersonAppService does not know which classes implement IPersonRepository and how to create it. Who needs to use PersonAppService, first creates an IPersonRepository and pass it to constructor of PersonAppService as shown below:

这称为构造函数注入。现在,personappservice不知道哪类实现了IPersonRepository和如何创建它。谁需要使用personappservice,首先创建一个ipersonrepository并把它传递给构造函数的personappservice如下所示:

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

Constructor Injection is a perfect way of making a class independent to creation of dependent objects. But, there are some problems with the code above:

  • Creating a PersonAppService become harder. Think that it has 4 dependency, we must create these 4 dependent object and pass them into the constructor of the PersonAppService.
  • Dependent classes may have other dependencies (Here, PersonRepository may has dependencies). So, we must create all dependencies of PersonAppService, all dependencies of dependencies and so on.. In that way, we may not even create a single object since dependency graph is too complex.
  • 创建一个personappservice越来越难。认为它有4的依赖,我们必须创造这4个依赖对象并通过他们的personappservice构造函数。
    依赖的类可以有其他的依赖(在这里,personrepository可能依赖)。因此,我们必须创建的所有依赖项personappservice,所有依赖的依赖等等。这样一来,由于依赖图太复杂,我们甚至无法创建单个对象。

Fortunately, there are Dependency Injection frameworks automates to manage dependencies.

Property Injection pattern(属性注入)

Constructor injection pattern is a perfect way of providing dependencies of a class. In this way, you can not create an instance of the class without supplying dependencies. It's also a strong way of explicitly declaring what's the requirements of the class to properly work.

But, in some situations, the class depends on another class but also can work without it. This is usually true for cross-cutting concerns such as logging. A class can work without logging, but it can write logs if you supply a logger. In this case, you can define dependencies as public properties rather than getting them in constructor. Think that we want to write logs in PersonAppService. We can re-write the class as like below:

构造函数注入模式是提供类依赖关系的一种完美方式。这样,就不能在不提供依赖项的情况下创建类的实例。它也是明确声明类的需求如何正确工作的强大方法。

但是,在某些情况下,类依赖于另一类,但也可以不使用它。对于横切关注点,例如日志记录,这通常是正确的。一个类可以在没有日志记录的情况下工作,但是如果您提供一个记录器,它就可以编写日志。在这种情况下,可以将依赖关系定义为公共属性,而不是将其作为构造函数。认为我们想在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 is a singleton object that implements ILogger but actually does nothing (does not write logs. It implements ILogger with empty method bodies). So, now, PersonAppService can write logs if you set Logger after creating the PersonAppService object like below:

NullLogger的实例是一个实现ILogger其实不单例对象(不写日志。它实现了用空的方法体ILogger)。所以,现在,personappservice可以如果你设置记录器后创造personappservice对象像下面写日志:

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

Assume that Log4NetLogger implements ILogger and write logs using Log4Net library. Thus, PersonAppService can actually write logs. If we do not set Logger, it does not write logs. So, we can say the ILogger is an optional dependency of PersonAppService.

Almost all of Dependency Injection frameworks support Property Injection pattern.

假设log4netlogger实现ILogger写使用log4net库日志。因此,personappservice实际上可以写日志。如果我们不设置记录器,它就不会写日志。因此,我们可以说,ILogger是personappservice可选的依赖。

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

Dependency Injection frameworks(依赖注入的框架)

There are many dependency injection framework that automates resolving dependencies. They can create objects with all dependencies (and dependencies of dependencies recursively). So, you just write your class with constructor & property injection patterns, DI framework handles the rest! In a good application, your classes are independent even from DI framework. There will be a few lines of code or classes that explicitly interact with DI framework in your whole application.

ASP.NET Boilerplate uses Castle Windsor framework for Dependency Injection. It's one of the most mature DI frameworks. There are many other frameworks like Unity, Ninject, StructureMap, Autofac and so on.

In a dependency injection framework, you first register your interfaces/classes to the dependency injection framework, then you can resolve (create) an object. In Castle Windsor, it's something like that:

有许多依赖注入框架可以自动解决依赖关系。它们可以创建具有所有依赖关系的对象(递归地依赖依赖关系)。因此,只需用构造函数和属性注入模式编写类,DI框架处理其余部分!在良好的应用程序中,即使从DI框架中,您的类也是独立的。将有几行代码或类显式地与整个应用程序中的DI框架交互。

ASP.NET样板使用依赖注入框架Castle Windsor。它是最成熟的DI框架之一。还有很多其他的框架统一,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);

We first created the WindsorContainer. then registered PersonRepository and PersonAppService with their interfaces. Then we asked to container to create an IPersonAppService. It created PersonAppService with dependencies and returned back. Maybe it's not so clear of advantage of using a DI framework in this simple example, but think you will have many classes and dependencies in a real enterprise application. Surely, registering dependencies will be somewhere else from creation and using objects and made only one time in an application startup.

Notice that we also declared life cycle of objects as transient. That means whenever we resolves an object of these types, a new instance will be created. There are many different life cycles (like singletion).

我们首先创造了windsorcontainer。然后注册personrepository和personappservice与接口。然后我们要创建一个ipersonappservice容器。它创造了personappservice依赖关系并返回。也许在这个简单示例中使用DI框架的优势并不明显,但您认为在实际的企业应用程序中有许多类和依赖项。当然,注册依赖项将位于创建和使用对象的其他地方,并且只在应用程序启动中生成一次。

注意,我们还声明对象的生命周期是暂时的。这意味着每当我们解析这些类型的对象时,就会创建一个新实例。有许多不同的生命周期(如单件模式)。

ASP.NET Boilerplate Dependency Injection Infrastructure

ASP.NET Boilerplate almost makes invisible of using the dependency injection framework when you write your application by following best practices and some conventions.

ASP.NET样板几乎使无形的使用依赖注入框架,当你写你的应用程序通过以下最佳实践和一些公约。

Registering Dependencies

There are different ways of registering your classes to Dependency Injection system in ASP.NET Boilerplate. Most of time, conventional registration will be sufficient.

Conventional Registrations

ASP.NET Boilerplate automatically registers all RepositoriesDomain ServicesApplication Services, MVC Controllers and Web API Controllers by convention. For example, you may have a IPersonAppService interface and a PersonAppService class that implements it:

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

ASP.NET Boilerplate automatically registers it since it implements IApplicationService interface (It's just an empty interface). It's registered as transient (created instance per usage). When you inject (using constructor injection) IPersonAppService interface to a class, a PersonAppService object is created and passed into constructor automatically.

Naming conventions are very important here. For example you can change name of PersonAppService to MyPersonAppService or another name which contains 'PersonAppService' postfix since the IPersonAppService has this postfix. But you can not name your service as PeopleService. If you do it, it's not registered for IPersonAppService automatically (It's registered to DI framework but with self-registration, not with interface), so, you should manually register it if you want.

ASP.NET样板自动注册它因为它实现iapplicationservice接口(它只是一个空的接口)。它注册为临时(每次使用创建实例)。当你将(使用构造函数注入)ipersonappservice接口类,一个personappservice对象被创建并传递到构造函数自动。

命名约定在这里非常重要。例如,你可以改变personappservice到mypersonappservice或另一个名称中包含personappservice”后缀自ipersonappservice这个后缀名。但你不能叫你的服务peopleservice。如果你这样做,这不是ipersonappservice自动注册(注册的DI框架但自注册,不是接口),所以,你应该手动登记如果你想。

ASP.NET Boilerplate can register assemblies by convention. You can tell ASP.NET Boilerplate to register your assembly by convention. It's pretty easy:

IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());

Assembly.GetExecutingAssembly() gets a reference to the assembly which contains this code. You can pass other assemblies to RegisterAssemblyByConvention method. This is generally done when your module is being initialized. See ASP.NET Boilerplate's module system for more.

You can write your own conventional registration class by implementing IConventionalRegisterer interface and calling IocManager.AddConventionalRegisterer method with your class. You should add it in pre-initialize method of your module.

Assembly.GetExecutingAssembly() gets a reference to the assembly which contains this code。你可以通过其他组件registerassemblybyconvention方法。这通常是在初始化模块时完成的。查看更多ASP.NET样板的模块系统。

你可以通过执行iconventionalregisterer接口和你的类调用iocmanager.addconventionalregisterer方法编写自己的常规登记类。您应该将它添加到模块的预初始化方法中。

Helper Interfaces

You may want to register a specific class that does not fit to conventional registration rules. ASP.NET Boilerplate provides ITransientDependency and ISingletonDependency interfaces as a shortcut. For example:

您可能希望注册一个不符合常规注册规则的特定类。ASP.NET样板提供itransientdependency和isingletondependency接口的一个捷径。例如:

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

In that way, you can easily register MyPersonManager. When need to inject a IPersonManager, MyPersonManager class is used. Notice that dependency is declared as Singleton. Thus, a single instance of MyPersonManager is created and same object is passed to all needed classes. It's just created in first usage, then same instance is used in whole life of the application.

这样,你可以很容易地登记mypersonmanager。当需要注入一个ipersonmanager,mypersonmanager类的使用。注意,依赖项被声明为单例。因此,对mypersonmanager单实例被创建相同的对象被传递到所有需要的类。它只是在第一次使用时创建的,然后在应用程序的整个生命周期中使用相同的实例。

Custom/Direct Registration

If conventional registrations are not sufficient for your situation, you can either use IocManager or Castle Windsor to register your classes and dependencies.

如果传统的注册是不够的你的情况,你可以用iocmanager或Castle Windsor。

Using IocManager

You can use IocManager to register dependencies (generally in PreInitialize of your module definition class):

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

Using Castle Windsor API

You can use IIocManager.IocContainer property to access to the Castle Windsor Container and register dependencies. Example:

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

For more information, read Windsor's documentation

Resolving

Registration informs IOC (Inversion of Control) Container (a.k.a. DI framework) about your classes, their dependencies and lifetimes. Somewhere in your application you need to create objects using IOC Container. ASP.NET Provides a few options for resolving dependencies.

Constructor & Property Injection

You can use constructor and property injection to get dependencies to your classes as best practice. You should do it that way wherever it's possible. An example:

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 is injected from constructor and ILogger is injected with public property. In that way, your code will be unaware of dependency injection system at all. This is the most proper way of using DI system.

IIocResolver, IIocManager and IScopedIocResolver

You may have to directly resolve your dependency instead of constructor & property injection. This should be avoided when possible, but it may be impossible. ASP.NET Boilerplate provides some services those can be injected and used easily. Example:

您可能必须直接解析依赖项,而不是构造函数和属性注入。如果可能的话,应该避免这种情况,但这可能是不可能的。ASP.NET样板提供一些服务可以注射,使用方便。例子:

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 in an example class in an application. It constructor-injected IIcResolver and used it to resolve and release objects. There are a few overloads of Resolve method can be used as needed. Release method is used to release component (object). It's critical to call Release if you're manually resolving an object. Otherwise, your application may have memory leak problem. To be sure of releasing the object, useResolveAsDisposable (as shown in the example above) wherever it's possible. It automatically calls Release at the end of the using block.

在应用程序的类的例子MySampleClass。它的构造函数注入iicresolver并用它来解决和释放对象。有一些重载的解析方法可以根据需要使用。释放方法用于释放组件(对象)。如果您手动解析一个对象,调用释放是至关重要的。否则,应用程序可能存在内存泄漏问题。为了确保释放的对象,useresolveasdisposable(如上面的例子)在可能的地方。它自动调用在使用块结束时的释放。

IIocResolver (and IIocManager) have also CreateScope extension method (defined in Abp.Dependency namespace) to safely release all resolved dependencies. Example:

IIocResolver(和iiocmanager)也createscope扩展方法(ABP。依赖命名空间中定义的)安全地释放所有的解决依赖关系。例子:

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

In the end of using block, all resolved dependencies automatically removed. A scope is also injectable using IScopedIocResolver. You can inject this interface and resolve dependencies. When your class is released, all resolved dependencies will be released. But use it carefully; For example, if your class has a long life (say it's a singleton) and you are resolving too many objects, then all of them will remain in the memory until your class is released.

If you want to directly reach to IOC Container (Castle Windsor) to resolve dependencies, you can consturctor-inject IIocManager and use IIocManager.IocContainer property. If you are in a static context or can not inject IIocManager, as a last chance, you can use singleton object IocManager.Instance everywhere. But, in that case your codes will not be easy to test.

在使用块结束时,所有解析的依赖项自动删除。一个范围也可注射用iscopediocresolver。您可以注入此接口并解析依赖项。当您的类被释放时,所有解析的依赖项都将被释放。但是要小心使用它;例如,如果你的类有很长的生命(比如它是一个单体)并且你解决了太多的对象,那么所有的对象都会留在内存中直到你的类被释放为止。

如果你想直接达到IOC容器(Castle Windsor)来解决依赖关系,你可以consturctor注入iiocmanager和使用iiocmanager.ioccontainer属性。如果你是在一个静态的语境或不能注入iiocmanager,作为一个最后的机会,你可以使用单例对象iocmanager实例随处可见。但是,那样的话,你的代码就不容易测试了。

Extras

IShouldInitialize interface

Some classes need to be initialized before first usage. IShouldInitialize has an Initialize() method. If you implement it, then your Initialize() method is automatically called just after creating your object (before used). Surely, you should inject/resolve the object in order to work this feature.

某些类需要在首次使用之前初始化。ishouldinitialize有initialize()方法。如果你实现它,那么你的initialize()方法只是在创建你的对象自动调用(使用前)。当然,为了执行此功能,您应该注入/解析对象。

ASP.NET MVC & ASP.NET Web API integration

We must call dependency injection system to resolve the root object in dependency graph. In an ASP.NET MVC application, it's generally a Controller class. We can use contructor injection and property injection patterns also in controllers. When a request come to our application, the controller is created using IOC container and all dependencies are resolved recursively. So, who does that? It's done automatically by ASP.NET Boilerplate by extending ASP.NET MVC's default controller factory. As similar, it's true for ASP.NET Web API also. You don't care about creating and disposing objects.

我们必须调用依赖注入系统来解决依赖图中的根对象。在ASP.NET的MVC应用程序,它通常是一个控制器类。我们可以在控制器也使用构造函数注入和资产注入模式。当请求到达我们的应用程序时,控制器是使用IOC容器创建的,所有依赖关系都是递归解决的。那么,谁做的?它是由ASP.NET的样板通过扩展ASP.NET MVC的默认控制器工厂自动完成。类似的,ASP.NET Web API还是一样。您不关心创建和处理对象。

ASP.NET Core Integration

ASP.NET Core has already a built-in dependency injection system with Microsoft.Extensions.DependencyInjection package. ABP uses Castle.Windsor.MsDependencyInjection package to integrate it's dependency injection system with ASP.NET Core's. So, you don't have to think about it.

ASP.NET的核心已经有microsoft.extensions.dependencyinjection包内置的依赖注入系统。ABP采用castle.windsor.msdependencyinjection包装整合与ASP.NET的核心,所以依赖注入系统,你不去想它。

Last notes

ASP.NET Boilerplate simplifies and automates using dependency injection as long as you follow the rules and use the structures above. Most of times you will not need more. But if you need, you can directly use all power of Castle Windsor to perform any task (like custom registrations, injection hooks, interceptors and so on).

ASP.NET样板简化并自动化使用依赖注入,只要你遵守规则,采用以上结构。大多数时候你不需要更多。但是,如果您需要,您可以直接使用Castle Windsor的所有权力来执行任何任务(如自定义注册,注射挂钩,拦截器等)。