Главная страница   /   13.2. Управление жизненным циклом (Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Марк Симан

13.2. Управление жизненным циклом

В главе 8 обсуждался процесс управления жизненным циклом, в том числе наиболее универсальные стили существования, к примеру, Singleton и Transient. Autofac поддерживает несколько стилей существования и позволяет конфигурировать жизненные циклы всех сервисов. Продемонстрированные в таблице 13-2 стили существования являются частью API.

Примечание

В Autofac стили существования называются областями применения экземпляров.

Таблица 13-2: Стили существования Autofac
Название Комментарии
Per Dependency Стандартный Transient. Эта область применения экземпляра используется по умолчанию. Отслеживание экземпляров выполняет контейнер.
Single Instance Стандартный Singleton.
Per Lifetime Scope Связывает жизненные циклы компонентов с областью применения контейнера (см. раздел 13.2.1).
Contextual Более расширенная версия Per Lifetime Scope

Реализации в Autofac стилей существования Transient и Singleton эквивалентны стилям существования, описанным в главе 8, поэтому в этой главе я не буду тратить время на их рассмотрение.

Подсказка

Используемый по умолчанию стиль Transient – самый безопасный, но не всегда является самым эффективным. Для потоко-безопасных сервисов Singleton – самый эффективный стиль, но вы не должны забывать о явной регистрации этих сервисов.

В этом разделе вы познакомитесь со способами определения стилей существования для компонентов – как с помощью кода, так и при помощи XML. Кроме того, мы рассмотрим сущность областей применения и то, как их можно использовать для реализации Web Request Context и других аналогичных стилей существования. После прочтения этого раздела вы уже сможете использовать стили существования Autofac в своем собственном приложении.

Начнем с рассмотрения способов конфигурирования областей применения для компонентов.

Конфигурирование областей применения экземпляров

В этом разделе мы рассмотрим процесс управления областями применения экземпляров с помощью Autofac. Конфигурирование области применения выполняется в рамках регистрации компонентов, а определить ее можно либо с помощью кода, либо при помощи XML. Поочередно рассмотрим каждую из этих возможностей.

Конфигурирование области применения с помощью кода

Область применения определяется как часть регистраций, которые мы выполняем для экземпляра ContainerBuilder. Это столь же просто, как и приведенный ниже код:

builder.RegisterType<SauceBéarnaise>().SingleInstance();

В этом примере конкретный класс SauceBéarnaise конфигурируется в виде Singleton таким образом, что каждый раз при запросе SauceBéarnaise возвращается один и тот же экземпляр. Если нам необходимо преобразовать абстракцию в конкретный класс, который имеет конкретный жизненный цикл, то мы можем воспользоваться обычным методом As и разместить вызов метода SingleInstance в любом удобном для нас месте. Приведенные ниже регистрации эквивалентны:

builder
	.RegisterType<SauceBéarnaise>()
	.As<IIngredient>()
	.SingleInstance();
builder
	.RegisterType<SauceBéarnaise>()
	.SingleInstance()
	.As<IIngredient>();

Единственное различие между двумя примерами заключается в том, что мы меняем местами вызовы методов As и SingleInstance. Лично я предпочитаю использовать код, продемонстрированный сверху, поскольку вызовы методов RegisterType и As формируют преобразование между конкретным классом и абстракцией. Размещение их рядом друг с другом делает регистрацию более читабельной, и после этого мы можем определять область применения как изменение этого преобразования.

Несмотря на то, что стиль Transient используется по умолчанию, мы все равно можем определить его явным образом. Приведенные ниже примеры эквивалентны:

builder
	.RegisterType<SauceBéarnaise>();
builder
	.RegisterType<SauceBéarnaise>();
	.InstancePerDependency();

Конфигурирование областей применения для регистраций, основанных на соглашениях, выполняется тем же самым методом, который используется и для обычных регистраций:

builder.RegisterAssemblyTypes(typeof(Steak).Assembly)
	.As<IIngredient>()
	.SingleInstance();

Чтобы задать область применения для всех регистраций соглашения, можно воспользоваться методом SingleInstance и другими подходящими методами. В предыдущем примере мы определяли все IIngredient как Singleton'ы.

Аналогично регистрации компонентов с помощью кода и XML, мы также можем сконфигурировать область применения этими же способами.

Конфигурирование области применения с помощью XML

При возникновении необходимости определения компонентов с помощью XML вам также захочется иметь возможность конфигурировать в этом же самом месте области применения этих компонентов. Осуществить это можно в виде составляющей части XML-схемы, которую вы уже видели в разделе 13.1.2 "Конфигурирование ContainerBuilder". Для объявления стиля существования можно использовать необязательный атрибут instance-scope:

<component type="Ploeh.Samples.MenuModel.Steak"
						service="Ploeh.Samples.MenuModel.IIngredient"
						instance-scope="single-instance" />

Отличие этого примера от примера, приведенного в разделе 13.1.2 "Конфигурирование ContainerBuilder", заключается в добавленном атрибуте instance-scope, который конфигурирует экземпляр в виде Singleton. Ранее, когда вы опускали этот атрибут, автоматически использовался стиль Transient.

Как с помощью кода, так и с помощью XML конфигурировать области применения экземпляров для компонентов довольно легко. Во всех случаях выполняется это в слегка декларативной форме. Несмотря на то, что конфигурация выполняется довольно просто, вы не должны забывать о том, что некоторые стили существования содержат объекты-долгожители, которые пользуются ресурсами на протяжении всего своего жизненного цикла.

Высвобождение компонентов

Как уже говорилось в разделе 8.2.2 "Управление устраняемыми зависимостями", важно высвободить объекты после завершения работы с ними. Autofac не имеет явного метода Release, и вместо него использует так называемые области применения жизненного цикла. Область применения жизненного цикла можно рассматривать как неиспользуемую копию контейнера. Как демонстрирует рисунок 13-5, область применения определяет границу, в рамках которой могут повторно использоваться компоненты.

Рисунок 13-5: Области применения контейнера Autofac выступают в роли контейнеров, которые могут совместно использовать компоненты в течение ограниченного времени или для ограниченного круга целей. Компонент с ограниченной областью применения – это Singleton-компонент, расположенный в пределах этой области применения. Независимо от того, сколько раз мы запрашиваем этот компонент, мы получаем один и тот же экземпляр. Другая область применения содержит свой собственный экземпляр, и родительский контейнер управляет поистине совместно используемыми Singleton-компонентами. Transient-компоненты никогда не используются совместно, но их срок действия заканчивается, когда устраняется область применения.

Область применения жизненного цикла определяет производный контейнер, который можно использовать для конкретного срока действия или для конкретной цели. Наиболее очевидным примером является веб-запрос. Мы порождаем область применения от контейнера таким образом, что область применения наследует все Singleton'ы, отслеживаемые родительским контейнером, а область применения, помимо этого, играет роль контейнера "локальных Singleton'ов". Когда из области применения запрашивается компонент, относящийся к этой области применения, мы всегда получаем один и тот же экземпляр. Отличие от настоящих Singleton'ов – при запросе второй области применения мы получаем другой экземпляр.

Однако Transient-компоненты функционируют так, как им и положено, независимо от того, разрешаем ли мы их из корневого контейнера или из области применения.

Подсказка

Области применения можно использовать для реализации контекстных стилей существования, например, стиля существования Web Request Context: создайте новую область применения в начале каждого контекста и используйте его для разрешения компонентов. А после завершения запроса устраните область применения. Тем не менее, что касается области применения веб-запроса, Autofac имеет встроенную возможность интеграции как с Web Forms, так и с ASP.NET MVC, поэтому нам не нужно выполнять эту интеграцию самостоятельно.

Одна из важных характеристик областей применения – они позволяют нам должным образом высвобождать компоненты по истечении срока действия этих областей применения. Новая область применения создается с помощью метода BeginLifetimeScope и высвобождает все соответствующие компоненты посредством вызова метода Dispose:

using (var scope = container.BeginLifetimeScope())
{
	var meal = scope.Resolve<IMeal>();
}

Строка 3: Уничтожение meal

Область применения создается из container посредством вызова метода BeginLifetimeScope. Возвращаемое значение реализует интерфейс IDisposable, поэтому можно поместить это значение в оператор using. Поскольку возвращаемое значение и контейнер реализуют один и тот же интерфейс, scope можно использовать для разрешения компонентов таким же образом, как и в случае работы с самим контейнером.

После окончания работы с областью применения ее можно устранить. При использовании оператора using область применения автоматически устраняется при выходе из оператора. Но это также можно сделать и явным образом путем вызова метода Dispose. При устранении scope мы также высвобождаем все компоненты, созданные с помощью области применения. В этом случае это означает, что вы высвобождаете диаграмму объектов meal.

Примечание

Не забывайте, что высвобождение устранимого компонента и устранение компонента – это не одно и то же. Это сигнал контейнеру о том, что компонент может завершить свой срок действия. Если это Transient-компонент или он ограничен областью применения, то он будет устранен. А если это Singleton компонент, то он будет продолжать действовать.

Ранее в этом разделе уже рассматривались способы конфигурирования компонентов в виде Singleton'ов или Transient-компонентов. Конфигурирование компонента с целью получения границ этого экземпляра, ограниченных областью применения, выполняется аналогичным образом:

builder.RegisterType<SauceBéarnaise>()
	.As<IIngredient>()
	.InstancePerLifetimeScope();

Подобно методам SingleInstance и InstancePerDependency метод InstancePerLifetimeScope можно использовать для указания того, что жизненный цикл компонента должен соответствовать области применения, которая создала экземпляр этого компонента.

Подсказка

Autofac отслеживает большинство Transient-компонентов, даже устраняемых, поэтому важно не забывать разрешать все компоненты области применения и устранять эту область после окончания ее использования.

Пока жизненный цикл компонента не закончен, Singleton'ы, в соответствии с их сущностью не высвобождаются. Однако если нам больше не нужен контейнер, мы можем высвобождать даже эти компоненты. Выполняется это посредством устранения самого контейнера:

container.Dispose();

На практике это не столь важно, поскольку жизненный цикл контейнера находится в близких взаимоотношениях с жизненным циклом поддерживаемого им приложения. Обычно мы сохраняем контейнер активным до тех пор, пока выполняется приложение, поэтому мы устраняем его только, когда приложение закрывается, при этом операционная система восстанавливает память.

Области применения позволяют нам обращаться ко множеству сценариев, в которых мы по обыкновению используем Web Request Context или другой контекстный стиль существования. Это идиоматический способ реализации пользовательского жизненного цикла с помощью Autofac.

На этом наш обзор процесса управления жизненным циклом в рамках Autofac завершается. Компоненты можно конфигурировать с помощью сочетания областей применения экземпляров, и это справедливо даже при регистрации составных реализаций одной и той же абстракции. Мы еще не касались способов работы с составными компонентами, поэтому давайте направим все наше внимание в этом направлении.