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

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

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

Марк Симан

12.3. Работа с составными компонентами

DI-контейнеры процветают благодаря их индивидуальности, но их неопределенность порождает ряд трудностей. При использовании Constructor Injection единичный конструктор предпочтительнее перегружаемых конструкторов, поскольку в этом случае ясно, какой конструктор использовать в ситуации, когда у вас нет выбора. То же самое касается и преобразования абстракций к конкретным типам. Если мы пытаемся преобразовать конкретные составные типы к одной и той же абстракции, это приводит к неопределенности.

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

  • Для разных потребителей должны использоваться разные специфичные типы
  • Зависимости являются последовательностями
  • Используются Decorator'ы

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

Как вы уже видели в разделе 12.1.2 "Конфигурирование контейнера", по сравнению с большинством других DI-контейнеров автоматическая интеграция не является поведением по умолчанию в Spring.NET. Более разветвленное управление интеграцией столь же проверенная опция и может использоваться для осуществления выбора между составными кандидатами.

Выбор из составных кандидатов

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

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

Конфигурирование составных реализаций одной и той же абстракции

До настоящего момента вы конфигурировали именованные объекты, но у объектов необязательно должны быть имена. Вы можете конфигурировать составные объекты, не присваивая им имена:

<objects xmlns="http://www.springframework.net">
	<object type="SauceBéarnaise" />
	<object type="Steak" />
</objects>

Классы SauceBéarnaise и Steak конфигурируются без имен. Поскольку вы явным образом не задали имена для объектов SauceBéarnaise и Steak, Spring.NET присваивает каждому из них автоматически сгенерированное имя. Если бы вы знали алгоритм, который Spring.NET использует для генерирования имени, то вы могли бы запрашивать объекты посредством метода GetObject. Но это может стать некоторого рода хрупким решением. Вместо этого метода вы можете использовать метод GetObjectsOfType, который также был введен в разделе 12.1.1 "Разрешение объектов". Как только мы сконфигурируем в Spring.NET тип, мы сможем извлечь его с помощью типов, от которых он унаследован.

Чтобы получить экземпляр конкретного класса Steak, например, можно сочетать метод GetObjectsOfType с парой методов расширения LINQ:

var meat = context.GetObjectsOfType(typeof(Steak))
		.Values
		.OfType<Steak>()
		.FirstOrDefault();

Вы запрашиваете тип Steak в методе GetObjectsOfType. Spring.NET найдет все сконфигурированные объекты, соответствующие запрашиваемому типу (независимо от того, именованные они или нет), и вернет их в виде словаря. Ключевыми словами данного словаря являются имена объектов, но поскольку вы не знаете имен, вас интересуют только значения.

Свойство Values – это экземпляр не generic-интерфейса ICollection, поэтому для того чтобы использовать LINQ, мы должны каким-либо образом привести его к generic-последовательности. Один и вариантов – использовать метод Cast<T>, но более безопасно – использовать фильтр OfType<T>. Несмотря на то, что метод Cast мог бы выдать исключение в случае наличия элемента, который нельзя привести к желаемому типу, метод OfType фильтрует последовательность. Наконец, мы получаем из последовательности объект. В данном случае мы использовали FirstOrDefault, но более строгое ограничение вводится с помощью метода расширения Single.

Оба класса SauceBéarnaise и Steak реализуют интерфейс IIngredient. При конфигурировании объектов Spring.NET не накладывает никаких ограничений на то, сколько объектов данного интерфейса мы можем сконфигурировать, но он все равно позволяет нам разрешать их с помощью метода GetObjectsOfType:

var ingredients = context.GetObjectsOfType(typeof(IIngredient));

При наличии предыдущей конфигурации возвращаемый словарь ingredients будет содержать экземпляры как SauceBéarnaise, так и Steak, и для извлечения конкретных интересующих нас элементов мы можем использовать LINQ-запросы, как делали это в предыдущем примере.

Несмотря на то, что мы можем сконфигурировать несколько объектов IIngredient без имен, при необходимости мы можем присвоить им имена:

<objects xmlns="http://www.springframework.net">
	<object id="Sauce" type="SauceBéarnaise" />
	<object id="Meat" type="Steak" />
</objects>

Это позволяет нам разрешать каждый из объектов по его имени:

var meat = context.GetObject("Meat");
var sauce = context.GetObject("Sauce");

Это не спасает нас от использования метода GetObjectsOfType, поэтому все предыдущие примеры также применимы.

Принимая как должное то, что мы всегда должны разрешать сервисы в единственном Composition Root, мы не должны ожидать возникновения неопределенности на этом уровне. Но для осуществления выбора между составными альтернативами при конфигурировании зависимостей этого потребителя мы можем использовать именованные объекты.

Конфигурирование именованных зависимостей

Интеграция объектов с именованными объектами – центральная возможность Spring.NET, несмотря на то, что возможности интеграции в Spring.NET ограничены. Даже если по возможности мы должны выбирать автоматическую интеграцию, существуют ситуации, когда нам необходимо обращаться к неопределенному API. В качестве примера рассмотрим приведенный ниже констуктор:

public ThreeCourseMeal(ICourse entrée,
	ICourse mainCourse, ICourse dessert)

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

Листинг 12-1: Интеграция списка зависимостей
<object id="Entrée" type="Rillettes" />
<object id="MainCourse" type="CordonBleu" />
<object id="Dessert" type="MousseAuChocolat" />
<object id="Meal" type="ThreeCourseMeal">
	<constructor-arg ref="Entrée" />
	<constructor-arg ref="MainCourse" />
	<constructor-arg ref="Dessert" />
</object>

Три реализации ICourse конфигурируются в виде именованных объектов. При конфигурировании объекта ThreeCourseMeal мы можем ссылаться на имена, когда интегрируем аргументы конструктора. Элемент constructor-arg также принимает необязательные атрибуты name или index, которые мы можем использовать, чтобы точно указать, на какой параметр мы ссылаемся. Но в этом примере мы перечисляем их все в соответствующем порядке.

Явное преобразование аргументов конструктора в именованные объекты – повсеместно применяемое решение. Сделать это мы можем, даже если конфигурируем именованные объекты в одном XML ресурсе, а конструктор – в совершенно другом ресурсе, поскольку единственное средство идентификации, которое связывает именованный объект с аргументом, – это имя. Это всегда выполнимо, но может стать хрупким решением, если нам придется управлять большим количеством имен.

Когда основной причиной, побуждающей нас отказаться от автоматической интеграции, является неопределенность, наилучшее решение – сконструировать API, помогающее нам избавиться от этой неопределенности. Все это также приводит к лучшей конструкции в целом.

В следующем разделе вы увидите, как можно использовать менее неопределенный и более гибкий подход, при котором разрешается использовать в обеде несколько блюд. Теперь вы должны изучить то, как Spring.NET работает со списками и последовательностями.

Интеграция последовательностей

В разделе 10.3.2 "Разработка пользовательского стиля существования" мы обсуждали, как выполнить рефакторинг явного класса ThreeCourseMeal к более универсальному классу Meal, который обладает приведенным ниже конструктором:

public Meal(IEnumerable<ICourse> courses)

Вы можете сконфигурировать Spring.NET так, чтобы интегрировать экземпляры Meal с соответствующими зависимостями ICourse, и я покажу вам, как это сделать. После рассмотрения этого вопроса вы должны будете приобрести хорошее понимание тех возможностей, которые доступны в случае необходимости конфигурирования экземпляров, имеющих последовательности зависимостей.

Автоматическая интеграция последовательностей

Spring.NET хорошо разбирается в массивах, но не в других типах последовательностей. Если нам необходим массив конкретных абстракций, и мы собираемся использовать все эти сконфигурированные абстракции, то в этом случае применяется автоматическая интеграция. К примеру, представьте себе, что класс Meal предлагает следующую перегрузку конструктора:

public Meal(params ICourse[] courses)

Если мы хотим, чтобы все сконфигурированные объекты ICourse были внедрены в Meal, то мы можем предоставить следующую конфигурацию:

<object id="Entrée" type="Rillettes" />
<object id="MainCourse" type="CordonBleu" />
<object id="Dessert" type="MousseAuChocolat" />
<object id="Meal" type="Meal" autowire="autodetect" />

Объект Meal конфигурируется для автоматической интеграции, и, поскольку Spring.NET по существу понимает массивы, он находит все объекты, которые реализуют интерфейс ICourse, и снабжает их конструктором Meal. Автоматически интегрировать массивы зависимостей довольно легко.

А теперь представьте себе, что перегрузки конструктора, которая принимает в качестве параметра массив ICourse, не существует. У вас имеется только конструктор, который принимает в качестве параметра IEnumerable<ICourse>. Хотя в этом случае автоматическая интеграция и не работает, вы можете воспользоваться преимуществами встроенного понимания массивов, определив простой Decorator, экземпляры которого должны создаваться с помощью массива. Приведенный ниже листинг демонстрирует generic-реализацию. Не забывайте о том, что принятие IEnumerable<ICourse> в качестве параметра конструктора указывает на статически типизированный запрос данной конкретной зависимости. Все, что вам необходимо сделать, является столь же простым, как и преобразование этого запроса в запрос массива того же типа.

Листинг 12-2: Преобразование запросов последовательностей в запросы массивов
public class ArrayEnumerable<T> : IEnumerable<T>
{
	private readonly IEnumerable<T> sequence;
	public ArrayEnumerable(params T[] items)
	{
		if (items == null)
		{
			throw new ArgumentNullException("items");
		}
		this.sequence = items;
	}
	public IEnumerator<T> GetEnumerator()
	{
		return this.sequence.GetEnumerator();
	}
}

Строка 1: Определение последовательности

Строка 4: Необходим массив

ArrayEnumerable<T> реализует IEnumerable<T>, таким образом, он подходит для любого конструктора, которому необходима такая последовательность. С другой стороны, для него нужен массив такого же типа. Поскольку Spring.NET в сущности знает, как работать с массивами, ему может подойти закрытый ArrayEnumerable, снабженный всеми объектами, которые совпадают с типом элемента T.

Для того чтобы соответствующим образом интегрировать класс Meal со всеми объектами ICourse, вы можете сконфигурировать контекст следующим образом:

<object id="Entrée" type="Rillettes" />
<object id="MainCourse" type="CordonBleu" />
<object id="Dessert" type="MousseAuChocolat" />
<object id="Courses"
				type="ArrayEnumerable<ICourse>"
				autowire="autodetect" />
<object id="Meal" type="Meal" autowire="autodetect" />

Вы задаете Courses в виде ArrayEnumerable<ICourse> с включенной возможностью автоматической интеграции. Поскольку только для его конструктора необходим массив ICourse'ов, Spring.NET автоматически интегрирует его со всеми реализациями ICourse, которые только может найти: Rillettes, CordonBleu и MousseAuChocolat.

Для класса Meal нужен IEnumerable<ICourse>, и, кроме того, он конфигурируется таким образом, чтобы иметь возможность автоматически интегрироваться. Когда вы отправите Spring.NET запрос на разрешение объекта Meal, он будет искать сконфигурированный объект, который реализует IEnumerable<ICourse>, и найдет объект Courses. Все три объекта ICourse будут внедрены в объект Meal посредством объекта Courses.

Класс ArrayEnumerable<T> – это небольшая забава, которая заполняет маленький пробел в Spring.NET. Это чисто инфраструктурный компонент, который можно упаковать в повторно используемую библиотеку.

Spring.NET автоматически обрабатывает массивы и, благодаря небольшой помощи ArrayEnumerable<T>, также обрабатывает и другие запросы последовательностей путем разрешения их в последовательности объектов, реализующих запрашиваемый тип. Единственное, что вам нужно сделать, – сконфигурировать ArrayEnumerable соответствующего типа элемента. Только в случае, когда вам необходимо явно отобрать несколько компонентов из большого набора, вам нужно выполнить больше действий. Это возможно благодаря более явной конфигурации.

Отбор нескольких объектов из большого набора

Когда мы используем возможность Spring.NET разрешать массивы все объекты внедряются в потребителей. Чаще всего это корректное поведение. Однако, как показано на рисунке 12-4, могут возникать ситуации, когда нам необходимо отобрать несколько компонентов из большого набора всех зарегистрированных компонентов.

Рисунок 12-4: В ситуации, продемонстрированной слева, мы хотим явным образом отобрать определенные зависимости из большого списка всех сконфигурированных объектов. Это отличается от ситуации, приведенной справа, когда мы отбираем все без разбора.

Когда мы ранее позволяли Spring.NET автоматически интегрировать все сконфигурированные объекты, это соответствовало ситуации, продемонстрированной в правой части рисунка 12-4. Если нам нужно сконфигурировать экземпляр так, как это показано в левой части рисунка, мы должны явным образом определить, какие объекты необходимо использовать.

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

Листинг 12-3: Внедрение именованных объектов в последовательность
<object id="Entrée" type="Rillettes" />
<object id="Entrée1" type="LobsterBisque" />
<object id="MainCourse" type="CordonBleu" />
<object id="Dessert" type="MousseAuChocolat" />
<object id="Meal" type="Meal">
	<constructor-arg>
		<list element-type="ICourse">
			<ref object="Entrée" />
			<ref object="MainCourse" />
			<ref object="Dessert" />
		</list>
	</constructor-arg>
</object>

Строка 7: Указывает на список

Строка 8-10: Именованные объекты

Элемент list можно использовать для указания на то, что следующие элементы являются элементами списка. Когда Spring.NET интегрирует список, он создает массив, тип которого задан атрибутом element-type. Элемент list может содержать множество различных дочерних элементов. Элемент ref используется для обращения к другим именованным объектам.

При разрешении объекта Meal вы получите экземпляр Meal с Rillettes, CordonBleu и MousseAuChocolat в качестве содержащихся в нем блюд, при этом LobsterBisque не используется.

Еще раз вы видите, что Spring.NET по существу работает с массивами. Несмотря на отсутствие поддержки других типов последовательностей вы можете обойти это ограничение посредством заворачивания последовательностей в класс наподобие ArrayEnumerable<T>.

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

Интеграция Decorator'ов

В разделе 9.1.2 "Паттерны и принципы механизма перехвата" мы обсуждали то, насколько паттерн проектирования Decorator полезен при реализации сквозных сущностей. По определению Decorator'ы представляют собой составные типы одной и той же абстракции. У нас есть, по крайней мере, две реализации абстракции: сам Decorator и вложенный в него тип. Если бы мы помещали Decorator'ы в стек, то у нас было бы еще больше реализаций.

Это еще один пример составных объектов, которые реализуют одну и ту же абстракцию. В отличие от предыдущих разделов эти объекты не являются концептуально равносильными, а зависят друг от друга. Я продемонстрирую вам два разных способа конфигурирования Spring.NET, используемых для работы с этим паттерном.

Создание обертки с помощью именованных объектов

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

Класс Breading – это Decorator для IIngredient. Для получения экземпляра, который необходимо в него вложить, он использует Constructor Injection:

public Breading(IIngredient ingredient)

Для того чтобы получить Cotoletta, вам хотелось бы вложить VealCutlet (еще один IIngredient) в класс Breading. Поскольку вы уже знаете, как соединять именованные объекты с аргументами конструктора, вам будет привычно выполнять действия, аналогичные следующим:

<object id="Breading" type="Breading">
	<constructor-arg ref="Cutlet" />
</object>
<object id="Cutlet" type="VealCutlet" />

Строка 2: Ссылка на именованный объект

На данный момент этот подход должен быть вам знаком. Для интеграции объекта Breading с объектом Cutlet вы используете ссылку на именованный объект. Поскольку Spring.NET явным образом не работает с преобразованиями абстракций в конкретные типы, каждый из этих двух элементов является таким же самым объектом, как и остальные элементы object. То, что они оба реализуют интерфейс IIngredient, никак не влияет на способ их конфигурирования.

При разрешении имени Breading вы получаете экземпляр Breading, в который вложен VealCutlet.

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

Создание обертки с помощью вложенных объектов

Если вам никогда не потребуется разрешать вложенные компоненты напрямую, вы можете воспользоваться более неявным способом создания их обертки. Представьте себе, что вам никогда не нужно будет разрешать VealCutlet непосредственно как IIngredient. Когда вам нужен IIngredient, вам всегда нужен Cotoletta.

В таких случаях нет нужды явным образом конфигурировать VealCutlet в виде независимого объекта. Вместо этого вы можете воспользоваться преимуществами синтаксиса вложенных объектов Spring.NET:

<object id="Breading" type="Breading">
	<constructor-arg>
		<object type="VealCutlet" />
	</constructor-arg>
</object>

Spring.NET позволяет вам задавать объекты в виде вложенных элементов. Вместо того чтобы ссылаться на именованный объект, элемент constructor-arg может содержать конфигурации всего объекта. Поскольку предполагается, что кроме как из конфигурации ссылаться на объект VealCutlet откуда-то еще вам не нужно будет, вы можете предоставить неименованный элемент object с корректным атрибутом type. Будучи вложенным в элемент constructor-arg, тип VealCutlet будет разрешаться в виде первого аргумента конструктора класса Breading.

Существует несколько доступных вариантов конфигурирования Decorator'ов. В отличие от Castle Windsor Spring.NET явно не понимает Decorator'ы, что может показаться слегка удивительным, поскольку, как и Windsor, он предполагает максимальную поддержку паттерна Decorator: механизм перехвата.

Создание перехватчиков

В разделе 9.3.3 "Пример: перехват с помощью Windsor" вы видели пример того, как добавить в WCF-приложение обработчик ошибок и Circuit Breaker с помощью возможности динамического перехвата, предлагаемой Castle Windsor. Чтобы продемонстрировать возможности перехвата в Spring.NET и сравнить их с Castle Windsor и Unity, я разберу точно такой же пример, но реализованный с помощью Spring.NET.

Как показано на рисунке 12-5, добавление аспекта в Spring.NET – довольно простой процесс.

Рисунок 12-5: Простой процесс добавления аспекта в Spring.NET.

Основная часть работы заключается в разработке самого перехватчика, но после создания перехватчика необходимо добавить его в контейнер. Это, как и все другое, делается в XML конфигурации.

Однако мы не можем конфигурировать перехватчики, пока не реализуем их. Поэтому первый шаг – написать некоторый код для обработчика ошибок и перехватчиков Circuit Breaker. После создания обработчика ошибок и перехватчика Circuit Breaker мы можем с помощью них сконфигурировать контейнер.

Реализация перехватчика обработчика исключений

Реализация перехватчика в Spring.NET требует от нас реализации интерфейса IMethodInterceptor. В следующем листинге демонстрируется, как реализовать стратегию обработки исключений из главы 9 "Механизм перехвата". Эта конкретная реализация, приведенная для Spring.NET, соответствует листингу 9-8, приведенному для Castle Windsor и листингу 14-13, приведенному для Unity.

Листинг 12-4: Реализация обработчика исключений IMethodInterceptor
public class ErrorHandlingInterceptor : IMethodInterceptor
{
	public object Invoke(IMethodInvocation invocation)
	{
		try
		{
			return invocation.Proceed();
		}
		catch (CommunicationException e)
		{
			this.AlertUser(e.Message);
		}
		catch (InvalidOperationException e)
		{
			this.AlertUser(e.Message);
		}
		return null;
	}
	private void AlertUser(string message)
	{
		var sb = new StringBuilder();
		sb.AppendLine("An error occurred.");
		sb.AppendLine("Your work is likely lost.");
		sb.AppendLine("Please try again later.");
		sb.AppendLine();
		sb.AppendLine(message);
		MessageBox.Show(sb.ToString(), "Error",
			MessageBoxButton.OK, MessageBoxImage.Error);
	}
}

Строка 3: Реализация логики перехвата

Строка 5-8: Попытка вернуть результат

Строка 9-16: Обработка исключений

Класс ErrorHandlingInterceptor реализует интерфейс IMethodInterceptor, который определяет только единственный метод Invoke. Именно здесь вы должны определить логику перехвата. Единственный аргумент метода – экземпляр интерфейса IMethodInvocation. Благодаря его методу Proceed вы пытаетесь вызвать вложенный метод и вернуть результат. Однако, поскольку цель перехватчика заключается в обработке известных исключений, вызов метода Proceed вставляется в блок try.

Если метод Proceed (или лучше вложенный метод, который вызывает Proceed) выдает одно из известных исключений, перехватчик ловит его и предупреждает пользователя об ошибке. В данном примере совокупность известных исключений жестко закодирована в самом перехватчике, но более общепринятая реализация могла бы вместо такого поведения подавить исключения или выдать их повторно в соответствии с внедренной спецификацией.

Поскольку метод Invoke должен возвращать объект, он возвращает null, когда выдается исключение и завершает свое выполнение. Это корректное значение для тех случаев, когда вложенный метод возвращает значение типа void, но когда метод возвращает реальные значения, это может стать проблемой, поскольку все это легко может привести к NullReferenceExceptions. Тем не менее, мы можем создать другой перехватчик, который устанавливает соответствующие значения по умолчанию для различных типов возвращаемого результата. Это было бы более корректно, нежели попытка предугадать корректное значение по умолчанию в рамках ErrorHandlingInterceptor, являющегося универсальным перехватчиком, который можно использовать для перехвата любого интерфейса. Кроме того, это соответствовало бы принципу единичной ответственности.

ErrorHandlingInterceptor заботится об обработке конкретных исключений, полученных для вложенного компонента. Данный компонент сам по себе может являться другим перехватчиком в виде Circuit Breaker.

Реализация Circuit Breaker перехватчика

Circuit Breaker перехватчик слегка более сложен, поскольку для него нужна зависимость ICircuitBreaker, но, как показывает следующий листинг, мы обращаемся к нему путем применения стандартного паттерна Constructor Injection. Когда дело доходит до компоновки класса, Spring.NET поступает с ним так же, как и с любым другим объектом, поэтому, пока он может разрешать зависимость, все идет хорошо.

Листинг 12-5: Реализация Circuit Breaker перехватчика IMethodInterceptor
public class CircuitBreakerInterceptor :
	IMethodInterceptor
{
	private readonly ICircuitBreaker breaker;
	public CircuitBreakerInterceptor(
		ICircuitBreaker breaker)
	{
		if (breaker == null)
		{
			throw new ArgumentNullException("breaker");
		}
		this.breaker = breaker;
	}
	public object Invoke(IMethodInvocation invocation)
	{
		this.breaker.Guard();
		try
		{
			var result = invocation.Proceed();
			this.breaker.Succeed();
			return result;
		}
		catch (Exception e)
		{
			this.breaker.Trip(e);
			throw;
		}
	}
}

Строка 1-2: Реализация IMethodInterceptor

Строка 4-13: Constructor Injection

Строка 19: Получения результата вложенного метода

Строка 20: Запись успешного результата

Строка 25: Отключение прерывателя

CircuitBreakerInterceptor необходимо делегировать его реальную реализацию экземпляру ICircuitBreaker. Поскольку Spring.NET, как и любой другой объект, может автоматически интегрировать перехватчик, для внедрения ICircuitBreaker вы можете воспользоваться стандартным паттерном Constructor Injection.

В методе Invoke вам необходимо реализовать идиоматическое выражение Guard-Succeed/Trip, которое вы уже видели в листингах 9-4 и 9-9. Как и в листинге 12-4, вы вызываете вложенный метод путем вызова метода Proceed, но вместо того, чтобы сразу же вернуть значение, вам необходимо присвоить его локальной переменной result, чтобы вы могли идентифицировать успешный результат для Circuit Breaker. Не забывайте, что это может привести к закрытию другого открытого прерывателя.

Любое исключение, которое может выдаваться вложенным методом, выдается заново неизменнным с помощью метода Proceed, поэтому вы можете поймать его и отключить прерыватель, как вы обычно и делаете.

После того как и ErrorHandlingInterceptor, и CircuitBreakerInterceptor реализованы, приходит время конфигурирования контейнера таким образом, чтобы он создавал обертку для объекта IProductManagementAgent.

Конфигурирование механизма перехвата

Все, что нам фактически необходимо сделать, – перехватить объект IProductManagementAgent вместе с Circuit Breaker и обработчиком ошибок таким образом, чтобы в тех случаях, когда при взаимодействии с веб-сервисом возникает исключение, открывался Circuit Breaker и обрабатывалось исключение, что давало бы приложению возможность восстановиться, как только веб-сервис или сеть восстановят свою работу.

Конфигурировать механизм перехвата в Spring.NET довольно легко. Все, что вам нужно сделать, – выполнить конфигурацию самих перехватчиков:

<objects xmlns="http://www.springframework.net"
				default-autowire="constructor">
	<object id="ErrorHandlingInterceptor"
					type="ErrorHandlingInterceptor" />
	<object id="CircuitBreakerInterceptor"
					type="CircuitBreakerInterceptor" />
</objects>

Не обладая особой фантазией, я присвоил объектам id, совпадающие с их type. Обратите внимание на то, что Constructor Injection, основанный на автоматической интеграции, включен по умолчанию. В то время как ErrorHandlingInterceptor обладает конструктором по умолчанию, CircuitBreakerInterceptor для запрашивания ICircuitBreaker использует Constructor Injection. Механизм автоматической интеграции работает как для CircuitBreakerInterceptor, так и для ErrorHandlingInterceptor, а также для большинства других объектов конфигурации, поэтому включение его по умолчанию является самым простым способом.

После того как вы разместили перехватчики, осталось только сконфигурировать объект IProductManagementAgent с необходимыми перехватчиками. На рисунке 12-6 продемонстрирована нужная вам конфигурация.

Рисунок 12-6: IProductManagementAgent должен быть вложен в Circuit Breaker перехватчик таким образом, чтобы при выдаче агентом исключения цепь на некоторое время открывалась Поскольку Circuit Breaker лишь регистрирует исключения, а не обрабатывает их, за это несет отвественность перехватчик обработчика ошибок, который должен, по крайней мере, уметь обрабатывать исключения, возникающие в обоих агентах, а также в Circuit Breaker.

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

Листинг 12-6: Конфигурирование перехватчиков
<object type="WcfProductManagementAgent" />
<object id="AgentPointCut"
				type="RegularExpressionMethodPointcut">
	<property name="patterns">
		<list>
			<value>.*WcfProductManagementAgent.*</value>
		</list>
	</property>
</object>
<aop:config>
	<aop:advisor pointcut-ref="AgentPointCut"
								advice-ref="ErrorHandlingInterceptor"
								order="1" />
	<aop:advisor pointcut-ref="AgentPointCut"
								advice-ref="CircuitBreakerInterceptor"
								order="2" />
</aop:config>

Строка 1: Объект для перехвата

Строка 2-9: Указание того, в каком месте выполнять перехват

Строка 10-17: Привязка перехватчиков к спецификации

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

Чтобы указать, какие классы или методы нужно перехватить, вы должны задать так называемое Pointcut, придуманное название для правила, определяющего, что нужно перехватить. Если вы вспомните первоначальное введение в механизм перехвата, приведенное в главе 9, то поймете, что Pointcut соответствует IModelInterceptorsSelector, используемому в Castle Windsor и реализованному в листинге 9-10. Как и Castle Windsor, Spring.NET позволяет писать императивный код, определяющий Pointcut, но помимо этого он предоставляет некоторые декларативные Pointcut'ы. Одним из таких статических Pointcut'ов является RegularExpressionMethodPointcut, который можно использовать для задания соответствующего правила с регулярным выражением. При каждом вызове метода он будет пытаться сопоставить полное имя метода с регулярным выражением. В этом конкретном случае вы планируете сопоставить только члены класса WcfProductManagementAgent.

Наконец, вам нужно связать Pointcut с перехватчиками, которые вы уже зарегистрировали ранее. Делается это с помощью последовательности элементов advisor, которые объявляют перехватчики и порядок их компоновки. Заметьте, что поскольку вы сначала указываете ErrorHandlingInterceptor, он становится крайним перехватчиком, перехватывающим CircuitBreakerInterceptor.

Последнее, что вам нужно сделать для того, чтобы сконфигурировать приложение, в котором используется сильное управление внешним взаимодействием, – убедиться, что все зависимости могут удовлетворять требуемым условиям. Поскольку для CircuitBreakerInterceptor нужен ICircuitBreaker, вы также должны сконфигурировать и этот объект:

<object type="CircuitBreaker" autowire="no">
	<constructor-arg value="00:01:00" />
</object>

Для конструктора CircuitBreaker нужна задержка в виде экземпляра TimeSpan, и вы будете задавать это элементарное значение как вложенное. Для этого вы должны отключить используемые по умолчанию настройки автоматического интегрирования с тем, чтобы явно установить задержку, равной одной минуте.

Для большей эффективности важно наличие только одного экземпляра Circuit Breaker (по крайней мере, для одного внешнего ресурса), но, поскольку по умолчанию используется область применения Singleton, вам не нужно явным образом выражать данное требование.

Этот пример продемонстрировал, как реализовать механизм динамического перехвата в Spring.NET. По-моему, я получил сложность, сравнимую с поддержкой контейнерами Castle Windsor и Unity механизма перехвата. Будучи не таким уже незначительным, полученное преимущество становится довольно значимым.

Механизм перехвата – динамическая реализация паттерна Decorator, а сам паттерн Decorator является сложным применением составных объектов одного и того же типа. Spring.NET дает нам возможность работать с составными компонентами несколькими различными способами. Мы можем конфигурировать их в виде альтернатив друг другу, в виде пиров, которые разрешаются в виде последовательностей, или в виде иерархических Decorator'ов, или даже в виде перехватчиков. Когда дело дойдет до массивов, Spring.NET поймет, что делать, но при этом мы часто можем преобразовывать в массивы и другие типы последовательностей, используя такой адаптер, как класс ArrayEnumerable<T>. Кроме того, все это позволяет нам явным образом задавать, как компонуются сервисы в случае, если нам необходим более явный контроль.

Помимо этого такие ситуации могут возникать, когда нам приходится иметь дело с API, отклоняющимися от Constructor Injection. До настоящего момента вы наблюдали за тем, как конфигурировать объекты, включая то, как определять области применения, и как работать с составными компонентами. Но до этого момента мы позволяли контейнеру подключать зависимости, явным образом предполагая, что все компоненты используют Constructor Injection. Поскольку это не всегда происходит именно так, в следующем разделе мы сделаем краткий обзор того, как работать с классами, экземпляры которых должны создаваться особым образом.