Главная страница   /   12.1. Знакомство с Spring.NET (Внедрение зависимостей в .NET

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

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

Марк Симан

12.1. Знакомство с Spring.NET

Из этого раздела вы узнаете, где можно взять Spring.NET, что вы при этом получите и как начать его использовать. Кроме того, мы рассмотрим универсальные варианты конфигурирования. Таблица 12-1 предоставляет основополагающую информацию, которая, скорее всего, понадобится вам для того, чтобы приступить к работе со Spring.NET.

Таблица 12-1: Краткая информация о Spring.NET
Вопрос Ответ
Откуда мне его получить?

Перейти на страницу www.springframework.net/download.html и загрузить последний релиз.

Из Visual Studio 2010 можно получить его посредством NuGet. Имя пакета – Spring.Core. Но если вам нужны возможности механизма перехвата, то в этом случае вам нужен пакет Spring.Aop.

Что находится в загруженном файле? Zip-файл, который содержит все вам необходимое: скомпилированные бинарные файлы, исходный код и документацию.
Какие платформы поддерживаются? Поддерживаются все версии ASP.NET, начиная с .NET 1.1, хотя последующие версии будут поддерживать только версии .NET 2.0 и выше.
Сколько он стоит? Нисколько. Это программное обеспечение с открытым исходным кодом.
Откуда мне получить помощь?

Коммерческую поддержку можно получить от SpringSource – организация, которая занимается разработкой Spring.NET.

Невзирая на коммерческую поддержку, Spring.NET все еще остается программным обеспечением с открытым исходным кодом, которое обладает процветающей экосистемой, поэтому, скорее всего (но не гарантированно), помощь можно получить на официальном форуме http://forum.springframework.net.

На какой версии Spring.NET основана данная глава? 1.3.1

Использование DI-контейнера Spring.NET предполагает выполнение трех шагов, продемонстрированных на рисунке 12-2.

Рисунок 12-2: Полноценный паттерн применения Spring.NET включает в себя три шага: сначала мы задаем то, как объекты конфигурируются и компонуются в XML-файле. Затем мы загружаем XML-конфигурацию в экземпляр контейнера. На последнем и финальном шаге мы можем разрешать объекты из экземпляра контейнера.

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

Разрешение объектов

Основная услуга, предоставляемая любым DI-контейнером – компоновка диаграмм объектов, и Spring.NET не является исключением. Поскольку это основная возможность контейнера, именно сейчас и стоит приступить к знакомству с соответствующим API, что я и буду делать в этом разделе.

В предыдущих главах, посвященных Castle Windsor и StructureMap, вы видели, как эти DI-контейнеры по-разному подходят к тому, нужно ли конфигурировать компоненты до того, как можно будет их разрешать. Castle Windsor требует, чтобы мы явно конфигурировали каждый отдельный компонент, а StructureMap может работать с конкретными типами без предварительной конфигурации. Но оба эти DI-контейнера, как и все DI-контейнеры, рассматриваемые в следующих главах, оперируют типами: мы отправляем в контейнер запрос на разрешение соответствующего типа.

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

Конфигурирование Spring.NET выполняется в XML, поэтому даже самый простой сценарий включает в себя фрагмент XML, а также некоторый .NET код. К примеру, для того чтобы разрешить конкретный класс SauceBéarnaise, вы должны сначала задать объект в XML-конфигурации:

<objects xmlns="http://www.springframework.net">
	<object id="Sauce"
					type="Ploeh.Samples.MenuModel.SauceBéarnaise,
					➥Ploeh.Samples.MenuModel" />
</objects>

В Spring.NET каждый сконфигурированный объект должен отображаться в элементе object. Данный элемент может иметь атрибут id, который присваивает имя объекту, а также атрибут type, который определяет .NET тип объекта. Имя используется в тех случаях, когда вы собираетесь разрешить объект.

Для разрешения экземпляра SauceBéarnaise вы должны загрузить XML-конфигурацию в экземпляр контейнера. При помощи XmlApplicationContext вы можете загрузить XML из нескольких различных источников, включая вложенные ресурсы и конфигурационный файл приложения. Но в примере ниже используется самостоятельный XML-файл под названием sauce.xml:

var context = new XmlApplicationContext("sauce.xml");
SauceBéarnaise sauce = (SauceBéarnaise)context.GetObject("Sauce");

Для того чтобы разрешить экземпляр SauceBéarnaise вы вызываете метод GetObject c ID, равным Sauce, который вы задали для объекта в XML-конфигурации. ID может быть любой строкой, но Spring.NET рекомендует использовать Pascal нотацию в качестве соглашения по именованию.

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

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

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

Метод GetObject является одним из нескольких методов, которые можно использовать для разрешения объектов. Тем не менее, поскольку все они слабо типизированы, мы должны всегда явным образом приводить возвращаемое значение к типу, с которым мы собираемся работать. С помощью метода GetObject мы можем запрашивать объекты только по имени, а не по типу, поэтому, каким образом мы должны выходить из ситуаций, когда все, что у нас есть – это тип?

Разрешение запросов типа

Иногда у нас имеется не имя, а экземпляр Type, который мы должны разрешить в экземпляр этого типа. Пример этого вы видели в разделе 7.2 "Построение ASP.NET MVC приложений", где мы обсуждали ASP.NET MVC класс DefaultControllerFactory. Соответствующий метод приведен ниже:

protected internal virtual IController GetControllerInstance(
	RequestContext requestContext, Type controllerType);

При наличии типа вместо имени мы могли бы соблазниться заданием и сопровождением явного преобразования типов в имена, но это было бы излишним. Наиболее подходящим вариантом было бы использование соглашения по именованию, которое позволяло бы нам детерминированно наследовать имя от экземпляра Type. Но интерфейс IListableObjectFactory, который наследуется напрямую от IObjectFactory, задает метод под названием GetObjectsOfType, который можно использовать для получения всех объектов, соответствующих данному типу. Полагая, что запрашиваемый controllerType – уникален в конфигурации Spring.NET, вы можете реализовать метод GetControllerInstance следующим образом:

IDictionary controllers =
	this.context.GetObjectsOfType(controllerType);
return controllers.Values.OfType<IController>().Single();

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

Хотя Spring.NET не предоставляет никакого generic API, вы можете легко инкапсулировать предыдущий запрос в метод расширения:

public static T Resolve<T>(this IListableObjectFactory factory)
{
	return factory.GetObjectsOfType(typeof(T))
		.Values.OfType<T>().Single();
}

Это позволит вам разрешать тип следующим образом:

SauceBéarnaise sauce = context.Resolve<SauceBéarnaise>();

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

IIngredient ingredient = context.Resolve<IIngredient>();

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

Конфигурирование контейнера

Как мы уже обсуждали в разделе 3.2 "Конфигурирование DI-контейнеров", существует несколько концептуально разных способа конфигурирования DI-контейнера. На рисунке 12-3 представлен обзор возможных вариантов и то, какие варианты подходят для Spring.NET.

Рисунок 12-3: Spring.NET из трех возможных вариантов, перечисленных в главе 3, поддерживает, главным образом, XML-конфигурацию. Технология использования кода в качестве конфигурации поддерживается в минимальной степени, а автоматическая регистрация вообще недоступна. Поэтому данные варианты отмечены серым цветом.

Как и другие, имеющие длительную историю DI-контейнеры, Spring.NET сначала использовал XML в качестве основного источника конфигурации. Но в отличие от Castle Windsor и StructureMap контейнер Spring.NET продолжает концентрироваться на XML, возможно, из-за его сильных связей с фреймворком Java Spring.

Технология конфигурирования в коде в Spring.NET

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

К несчастью, это случилось слишком поздно, и у меня не оставалось времени на то, чтобы переписать эту главу.

В данной главе мы будем рассматривать только XML-конфигурацию.

Работа с .NET типами в XML

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

В разделе 12.1.1 "Разрешение объектов" вы уже видели простой пример XML-конфигурации Spring.NET:

<objects xmlns="http://www.springframework.net">
	<object id="Sauce"
				type="Ploeh.Samples.MenuModel.SauceBéarnaise,
				➥Ploeh.Samples.MenuModel" />
</objects>

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

На читабельность оказывается влияние, поскольку соответствующая часть имени типа (SauceBéarnaise) размещена между пространством имен и именем сборки. Удобство сопровождения подвергается влиянию, поскольку становится сложнее переименовывать пространства имен и сборки. Всякий раз, когда мы что-либо переименовываем, нам приходится редактировать потенциально большой набор определений типов.

Допустим, эти проблемы применяются ко всем фреймворкам, в которых типы должны задаваться в виде XML, но тогда это еще одна причина того, почему все остальные DI-контейнеры используют для конфигурирования контейнера другие варианты. По этой причине я также чаще всего не рекомендую использовать XML-конфигурацию до тех пор, пока она не будет предписана сценарием применения. Тем не менее, когда дело касается Spring.NET, XML – самый распространенный вариант конфигурирования.

Для облегчения работы с .NET типами в XML SpringSource предоставляет такие средства, как XML-схемы и дополнение для Visual Studio с завершением типов и свойств. Сам фреймворк также позволяет нам задавать набор псевдонимов типов, являющихся сокращенными именами, которые можно использовать для определения типов. Это также выполняется в XML. Псевдоним типа для класса SauceBéarnaise может выглядеть следующим образом:

<alias name="SauceBéarnaise"
				type="Ploeh.Samples.MenuModel.SauceBéarnaise,
				➥Ploeh.Samples.MenuModel" />

Имя может быть любым, но мне кажется, чтобы оно было наиболее понятно и его можно было бы легко запоминать, для имени типа следует использовать его псевдоним.

Указанный псевдоним типа позволяет нам переписать предыдущий пример следующим образом:

<object id="Sauce" type="SauceBéarnaise" />

Данная возможность может быть полезна, когда нам нужно несколько раз обратиться к одному и тому же типу в одном и том же XML-файле. В любом случае это делает конфигурацию более читабельной. В оставшейся части данной главы я буду использовать псевдонимы типов. Благодаря такому обозначению элемент object начинает походить на то, чем он является: преобразование имени в конкретный тип.

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

Явное конфигурирование зависимостей

Создать класс SauceBéarnaise довольно легко, поскольку он имеет конструктор по умолчанию. Ни одному DI-контейнеру не нужно никакой особой помощи для создания таких типов. Все меняется, когда конструктор по умолчанию отсутствует. К примеру, рассмотрим конструктор Mayonnaise:

public Mayonnaise(EggYolk eggYolk, OliveOil oil)

Несмотря на то, что рецепт майонеза слегка упрощен, и EggYolk, и OliveOil – это конкретные классы, имеющие конструкторы по умолчанию. Однако, поскольку класс Mayonnaise не имеет конструктора по умолчанию, вы должны сообщить Spring.NET, как его разрешить. Один из вариантов – явным образом одновременно интегрировать типы:

<object id="EggYolk" type="EggYolk" />
<object id="OliveOil" type="OliveOil" />
<object id="Mayonnaise" type="Mayonnaise">
	<constructor-arg ref="EggYolk" />
	<constructor-arg ref="OliveOil" />
</object>

Типы EggYolk и OliveOil конфигурируются таким же образом, как это делалось ранее, но элемент Mayonnaise теперь содержит два элемента constructor-arg. Каждый из этих элементов ссылается на именованный объект для того, чтобы определить параметры конструктора Mayonnaise. Атрибут ref идентифицирует еще один сконфигурированный объект по имени, таким образом, EggYolk ссылается на имя EggYolk, а не явно на тип EggYolk.

В предыдущем примере важен порядок элементов constructor-arg, поскольку вы не обращаетесь явным образом к именам параметров, что также возможно.

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

Автоматическая интеграция зависимостей

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

<object id="EggYolk" type="EggYolk" />
<object id="OliveOil" type="OliveOil" />
<object id="Mayonnaise" type="Mayonnaise"
				autowire="autodetect" />

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

Если мы планируем использовать автоматическую интеграцию для всех объектов, то мы можем разрешить ее для целого блока сконфигурированных объектов вместо того, чтобы приписывать атрибут autowire для каждого элемента object:

<objects xmlns="http://www.springframework.net"
				default-autowire="autodetect">
	<object id="EggYolk" type="EggYolk" />
	<object id="OliveOil" type="OliveOil" />
	<object id="Mayonnaise" type="Mayonnaise" />
</objects>

Атрибут default-autowire задает стратегию автоматической интеграции по умолчанию для всех объектов в рамках элемента objects. Это самый простой способ включения автоматической интеграции сразу для всех объектов, но вы должны иметь ввиду, что этот механизм работает не всегда.

Поддержка автоматической интеграции в Spring.NET основывается на однозначности. Spring.NET исследует конструктор класса Mayonnaise и определяет, что ему нужны экземпляры EggYolk и OliveOil. Для выделения зависимости EggYolk он выполняет поиск среди всех остальных сконфигурированных элементов с целью обнаружения элемента, который может удовлетворять данному условию (то же самое он делает и для зависимости OliveOil).

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

Примечание

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

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

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

Загрузка XML

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

В рамках Spring.NET мы можем упаковать конфигурацию в отдельные XML элементы, которые определены в разных ресурсах. В таблице 12-2 перечисляются поддерживаемые типы ресурсов. Их легко использовать, но я вкратце расскажу о каждом из них, чтобы у вас осталось это в памяти.

Таблица 12-2: Типы ресурсов XML
Тип ресурса Синтаксис URI Описание
FileSystemResource

file://<filename>

Моникер file:// moniker не обязателен.

XML-конфигурация задается в файлах.
ConfigSectionResource config://<path to section> XML-конфигурация задается в конфигурационном файле приложения.
UriResource Поддерживается стандартный синтаксис .NET URI. XML-конфигурация читается из таких стандартных протоколов System.Uri, как HTTP и HTTPS.
AssemblyResource assembly://<AssemblyName>/<NameSpace>/<ResourceName> XML-конфигурация вложена в сборку.
InputStreamResource Не поддерживается XML-конфигурация читается из System.IO.Stream.

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

Использование XML-файлов

До настоящего момента вы видели примеры загрузки XML-конфигурации только из одного файла. В примере ниже загрузка всей конфигурации выполняется из файла sauce.xml:

var context = new XmlApplicationContext("sauce.xml");

Поскольку о пути не предоставлено никакой явной информации, предполагается, что файл sauce.xml располагается в рабочей папке запущенного процесса. Кроме того, можно использовать и полный путь.

В этом примере моникер не использовался, поэтому Spring.NET принимает в качестве значения по умолчанию FileSystemResource. В противном случае вы могли бы явно использовать моникер file://, как это продемонстрировано ниже:

var context = new XmlApplicationContext("file://sauce.xml");

Этот пример аналогичен предыдущему примеру. Чаще всего работа с XML в виде файлов интуитивно понятна, поэтому в большинстве случаев имеет смысл обойтись без моникера file:// и вместо этого явно прописывать путь к файлу.

Помимо определения XML-конфигурации в текстовых файлах можно также интегрировать ее в стандартный конфигурационный файл приложения.

Использование конфигурационных файлов приложения

Если мы предпочитаем интегрировать конфигурацию Spring.NET с остальной частью конфигурации приложения, то можем использовать стандартный .config файл .NET приложения.

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

<configSections>
	<sectionGroup name="spring">
		<section name="objects"
						type="Spring.Context.Support.DefaultSectionHandler,
						➥Spring.Core" />
	</sectionGroup>
</configSections>

Это позволяет определять объекты напрямую в файле .config, как вы это делали ранее в автономных XML-файлах:

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

Используя моникер config://, теперь можно загрузить конфигурацию Spring.NET из файла .config в экземпляр XmlApplicationContext следующим образом:

var context = new XmlApplicationContext("config://spring/objects");

Теперь экземпляр context может безопасно разрешать имя Sauce.

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

Загрузка XML из URI

При загрузке XML из файлов мы используем моникер в качестве разграничителя URI схемы. Spring.NET может загружать XML-файлы не из файлов, а из других URI, например, HTTP, HTTPS и FTP. Это столь же просто, как и приведенный ниже код:

var context = new XmlApplicationContext("http://localhost/sauce.xml");

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

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

Использование вложенных ресурсов

В .NET мы можем скомпилировать ресурсы в сборки. Если мы внедрим XML-конфигурацию в сборку, то Spring.NET сможет ее загрузить.

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

При внедрении файла sauce.xml в сборку можно загрузить его в XmlApplicationContext следующим образом:

var context = new XmlApplicationContext(
		"assembly://Ploeh.Samples.Menu.SpringNet/
		➥Ploeh.Samples.Menu.SpringNet/sauce.xml");

Для того чтобы выполнить загрузку из вложенного ресурса, мы можем сконструировать строку ресурса из моникера assembly://, за которым следует название сборки, пространство имен и название самого вложенного ресурса. В таблице 12-2 показан требуемый формат, используемый для обращения к вложенному ресурсу.

AssemblyResources позволяет нам загружать конфигурацию Spring.NET не только из внешне заданных XML-файлов и URI, но и из вложенных ресурсов. Если мы храним XML-конфигурацию в других местах, то нам может понадобиться такая возможность, как чтение из потоков.

Использование потоков

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

Поскольку поток не является статическим ресурсом, Spring.NET не поддерживает возможность идентификации его по строке. У нас нет возможности использовать класс XmlApplicationContext, и вместо него мы должны прибегнуть к одному из многочисленных контекстных классов Spring.NET:

var resource = new InputStreamResource(stream, "");
var context = new XmlObjectFactory(resource);

InputStreamResource выступает в роли адаптера для объекта System.IO.Stream. Объект stream содержит XML-конфигурацию, которую вы собираетесь загрузить. Мы можем загрузить XML в поток из множества различных источников, включая строку, или посредством построения XML модели с помощью технологии LINQ to XML. Пустая строка, используемая в конструкторе InputStreamResource – это описание. Мы можем передать соответствующее описание, но это необязательно.

Благодары resource (или любой реализации IResource), мы теперь можем создать экземпляр XmlObjectFactory. Этот класс предлагает функциональность, аналогичную XmlApplicationContext, но загружает конфигурацию напрямую из экземпляра IResource вместо того, чтобы загружать ее из строк, представляющих собой статические ресурсы.

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

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

Комбинирование XML ресурсов

Для большого приложения потребуется большое количество кода XML-конфигурации. Чтобы обеспечить наилучшее сопровождение конфигурации, мы, возможно, захотим разделить конфигурацию на несколько небольших документов. Может быть, мы даже захотим хранить их в отдельных местах: некоторые в XML-файлах, некоторые в файле .config, а некоторые – в виде вложенных ресурсов.

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

Ниже приведена сигнатура конструктора, который вы использовали на протяжении всего этого времени:

public XmlApplicationContext(params string[] configurationLocations)

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

var context = new XmlApplicationContext("sauce.xml");

Однако вы можете использовать произвольное количество строк в конструкторе для комбинирования в одном контексте нескольких ресурсов:

var context = new XmlApplicationContext(
		"config://spring/objects",
		"meat.xml",
		"file://course.xml");

В этом примере комбинируются три разных ресурса, каждый из которых определяет фрагмент единого целого. Одна часть конфигурации задается и загружается из конфигурационного файла приложения, в то время как две остальные части загружаются из XML-файлов: одна – посредством использования для имен файлов неявного синтаксиса, а другая – с помощью явного использования моникера file://. Вместе эти части формируют полноценную систему конфигурации, которая определяет XmlApplicationContext.

Еще один способ комбинирования составных ресурсов – посредством элемента import XML-файла:

<objects xmlns="http://www.springframework.net">
	<import resource="config://spring/objects" />
	<import resource="meat.xml" />
	<import resource="file://course.xml" />
</objects>

Эта конфигурация аналогична предыдущему примеру.

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

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

Этот раздел познакомил нас с DI-контейнером Spring.NET и продемонстрировал фундаментальные принципы: как конфигурировать контейнер с помощью XML и впоследствии использовать его для разрешения объектов. Разрешение объектов выполняется посредством единичного вызова метода GetObject, поэтому вся сложность заключается в конфигурировании контейнера. До настоящего момента мы рассматривали только самое основное API. Но есть и более продвинутые области, которые мы еще не рассмотрели. Один из самых важных вопросов – как управлять жизненным циклом компонентов.