Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
Скачать 5.66 Mb.
|
XML- конфигурация задается в файлах. ConfigSectionReso urce config:// XML- конфигурация задается в конфигурацион ном файле приложения. UriResource Поддерживается стандартный синтаксис .NET URI. XML- конфигурация читается из таких стандартных протоколов System.Uri , как HTTP и HT TPS. AssemblyResource assembly:// XML- конфигурация вложена в сборку. InputStreamResour ce Не поддерживается XML- конфигурация читается из System.IO.Stre am Некоторые типы ресурсов поддерживают синтаксис URI, в котором можно использовать моникер для обозначения типа ресурса в качестве составляющей части адреса, закодированного строкой. Если моникер не применяется, то предполагается, что ресурс является файлом. 465 И спользование XML-файлов До настоящего момента вы видели примеры загрузки XML-конфигурации только из одного файла. В примере ниже загрузка всей конфигурации выполняется из файла sauce.xm l: var context = new XmlApplicationContext("sauce.xml"); Поскольку о пути не предоставлено никакой явной информации, предполагается, что файл sauce.xm l располагается в рабочей папке запущенного процесса. Кроме того, можно использовать и полный путь. В этом примере моникер не использовался, поэтому 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: < configS ect ions> < /config Sec tions> Это позволяет определять объекты напрямую в файле .config, как вы это делали ранее в автономных XML-файлах: < spring> < /spring > 466 Используя моникер 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 и FT P. Это столь же просто, как и приведенный ниже код: var context = new XmlApplicationContext("http://localhost/sauce.xml"); В этом примере файл sauce.xm l размещен на веб-сервере локального компьютера, но использовать можно любой публично доступный ресурс. Во всех предыду щих случаях мы могли изменять конфигурацию, заново не компилируя приложение. Это невозможно для следующей опции. И спользование вложенных ресурсов В .NET мы можем скомпилировать ресурсы в сборки. Если мы внедрим XML- конфигурацию в сборку, то Spring.NET сможет ее загрузить. Преимущество вложенных XML-файлов заключается в том, что операторы не могут случайно изменить конфигурационные значения, когда они компилируются в сборку. Т олько файлы, имеющие параметры, значения которых, возможно, должны устанавливаться операторами, должны передаваться вне сборки в файловую систему, где их можно редактировать. При внедрении файла sauce.xml в сборку можно загрузить его в XmlApplicationContext следующим образом: var context = new XmlApplicationContext( "assembly://Ploeh.Samples.Menu.SpringNet/ ➥Ploeh.Samples.Menu.SpringNet/sauce.xml"); Для того чтобы выполнить загрузку из вложенного ресурса, мы можем сконструировать строку ресурса из моникера assem bly://, за которым следует название сборки, пространство имен и название самого вложенного ресурса. В таблице 12-2 показан требуемый формат, используемый для обращения к вложенному ресурсу. AssemblyResources позволяет нам загружать конфигурацию Spring.NET не только из внешне заданных XML-файлов и URI, но и из вложенных ресурсов. Если мы храним 467 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 позволяет комбинировать несколько разных ресурсов, поскольку он получает каждую строку ресурса как часть массива параметров. 468 Ниже приведена сигнатура конструктора, который вы использовали на протяжении всего этого времени: 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-файла: Эта конфигурация аналогична предыдущему примеру. Как вы уже видели, существует несколько различных способов, с помощью которых можно загрузить XML-конфигурацию в Spring.NET . Поскольку мы можем загрузить и комбинировать конфигурацию из более чем одного ресурса, это дает нам некоторую степень модульности, которая очень нужна нам для обеспечения сопровождаемости. Но мы не должны забывать о том, что в целом XML-конфигурация не самый лучший способ конфигурирования DI-контейнера. Это очень хрупкий и слишком подробный способ, и к тому же он сложен для разрешения. Этот раздел познакомил нас с DI-контейнером Spring.NET и продемонстрировал фундаментальные принципы: как конфигурировать контейнер с помощью XML и впоследствии использовать его для разрешения объектов. Разрешение объектов выполняется посредством единичного вызова метода GetObject , поэтому вся сложность заключается в конфигурировании контейнера. До настоящего момента мы рассматривали только самое основное API. Но есть и более продвинутые области, которые мы еще не 469 рассмотрели. Один из самых важных вопросов – как управлять жизненным циклом компонентов. 470 12.2. Управление жизненным циклом В главе 8 мы обсуждали процесс управления жизненным циклом, в том числе такие самые универсальные концептуальные стили существования, как Singleton и T ransient . Spring.NET поддерживает несколько других стилей существования и позволяет нам конфигурировать жизненные циклы всех объектов. Стили существования, продемонстрированные в таблице 12-3 доступны в виде составляющей пакета. П римечание В документации к Spring.NET стили существования называются област ями применения объектов. Реализации Singleton и Transient в Spring.NET аналогичны основным стилям существования, описанным в главе 8, поэтому в данной главе я не буду уделять им много внимания. Таблица 12-3: Стили существования Spring.NET Название Комментарии Singleton Стиль существования по умолчанию. Prototype Название стиля существования Transient, используемое в рамках Spring.NET. Экземпляр ы контейнером не отслеживаются. Request Название стиля существования Web Request Cont ext, используемое в рамках Spring.NET . Корректно только в контексте IApplicationContext Session Для одной HT TP-сессии создается один экземпляр. Используйте с осторожностью. Корректно только в контексте IApplicationContext Application Расширяет определение одного объекта до жизненного цикла веб-приложения. Корректно только в контексте IApplicationContext П римечание По умолчанию в Spring.NET используется стиль существования Singleton. Этим он отличается от большинства других контейнеров. Как уже обсуждалось в главе 8, Singleton – это самый эффективный, хотя и не всегда безопасный, из всех стилей существования объектов. В Spring.NET эффективность приоритетнее безопасности. Т ри веб стиля (Request, Session и Application) тесно связаны между собой соотвествующими IApplicationContexts и не работают с XmlApplicationContext или с XmlObjectFactory , которые мы рассматривали до настоящего момента. Доступные на данный момент реализации настолько сильно связаны с ASP.NET W eb Forms, что заставить их работать с ASP.NET MVC довольно трудно. Откровенно говоря, эти стили довольно запутаны и не используются, поэтому в этой главе мы не будем их рассматривать. Предполагается, что в последующих версиях Spring.NET этот вопрос будет модернизирован. В этой главе я продемонстрирую вам, как конфигурировать области применения объектов на примере стилей Singleton и Transient. Поскольку Spring.NET не поддерживает 471 пользовательские стили существования, эта глава будет краткой. После прочтения этой главы вы сможете использовать стили существования объектов в Spring.NET. Конфигурирование областей применения Области применения конфигурируются в качестве составляющей части конфигурации объектов в XML. Для того чтобы сконфигурировать объект как T ransient , необходимо установить атрибуту singleton значение false : Изменив значение атрибута на true , мы сконфигурируем объект как Singleton: Атрибут singleton является необязательным, поскольку стиль Singleton используется по умолчанию, за исключением тех случаев, когда он неявным образом конфигурируется как Singleton. Именно это вы и будете делать далее в этой главе. П редотвращение утечек памяти Как и любой другой DI-контейнер Spring.NET создает диаграммы объектов. Однако он не выполняет за нас отслеживание созданных объектов. Он может отслеживать созданные объекты для своих собственных целей, но все это зависит от стиля существования объекта. Например, для реализации области применения Singleton Spring.NET должен хранить ссылку на созданный экземпляр. С другой стороны, область применения T ransient не отслеживает объекты, созданные Spring.NET . Как вы уже видели в листингах 8-7 и 8-8, экземпляр ы объектов создаются и возвращаются без внутреннего сопровождения. Все это имеет некоторые достоинства и преимущества. Поскольку Spring.NET не держится за экземпляр ы, риск случайных утечек памяти не велик. При использовании таких контейнеров, как Castle Windsor, утечки памяти гарантированы, если вы забыли вызвать метод Release для всех разрешенных диаграмм объектов. В Spring.NET все не так, поскольку объекты будут автоматически уничтожаться "сборщиком мусора", как только они будут выходить за рамки области применения. Недостаток заключается в том, что устраняемые объекты нельзя детерминированно уничтожать. Поскольку мы не можем явно высвобождать диаграмму объектов, мы не можем уничтожать любые устранямые объекты. Это означает, что наибольшую значимость приобретает заворачивание устраняемых API в неустраняемые сервисы, что обсуждалось в разделе 6.2.1. Короче говоря, Spring.NET ведет себя подобающим образом и позволяет объектам уничтожаться "сборщиком мусора", когда они выходят за рамки области применения вашего кода. Но при этом необходимо, чтобы ваши собственные классы также вели себя подобающим образом. Поскольку в вопросе уничтожения любого сервиса мы не можем полагаться на контейнер или код, выполняющий вызов, мы должны продолжать использовать устраняемые объекты в рамках единичных методов. 472 Эта глава предоставила удивительно краткий обзор областей применения объектов, применяемых в Spring.NET . По этому вопросу особо и нечего сказать. Единственные повсеместно доступные области применения – это Singleton и T ransient, тогда как пара остальных областей применения полагается на конкретные реализации IApplicationContext . При конфигурировании объектов мы можем конфигурировать некоторые из них как Singleton, а некоторые – как Transient, и это справедливо даже, когда мы конфигурируем составные реализации одной и той же абстракции. Мы уже рассматривали то, как работать с составными компонентами, поэтому давайте переключим наше внимание в этом направлении. 473 12.3. Работа с составными компонентами DI-контейнеры процветают благодаря их индивидуа льности, но их неопределенность порождает ряд трудностей. При использовании Constructor Injection единичный конструктор предпочтительнее перегружаемых конструкторов, поскольку в этом случае ясно, какой конструктор использовать в ситуации, когда у вас нет выбора. То же самое касается и преобразования абстракций к конкретным типам. Если мы пытаемся преобразовать конкретные составные типы к одной и той же абстракции, это приводит к неопределенности. Несмотря на столь нежелательну ю особенность как неопределенность, нам часто приходится работать с составными реализациями единичного интерфейса. Это может происходить в следующих ситуациях: Для разных потребителей должны использоваться разные специфичные типы Зависимости являются последовательнос тями Используются Decorator'ы Мы рассмотрим каждую из этих ситуаций и увидим, как Spring.NET поочереди справляется с каждой из них. После прочтения раздела вы должны будете уметь регистрировать и разрешать компоненты даже тогда, когда в дело вступают составные реализации одной и той же абстракции. Как вы уже видели в разделе 12.1.2 "Конфигурирование контейнера", по сравнению с большинством других DI-контейнеров автоматическая интеграция не является поведением по умолчанию в Spring.NET . Более разветвленно е управление интеграцией столь же проверенная опция и может использоваться для осуществления выбора между составными кандидатами. В ыбор из составных кандидатов В Spring.NET можно использовать механизм автоматической интеграции, но для этого необходимо, чтобы сервисы были индивидуа льными. Пока у нас есть только один единственный объект, соответствующий конкретной абстракции, никаких проблем нет, но как только мы введем больше реализаций одного и того же интерфейса, возникает неопределенность. Для того чтобы побороть эту неопределеннос ть, мы можем использовать явную интеграцию зависимостей, которую вы уже видели в некоторых примерах. Конфигурирова ние составных реализаций одной и той же абстракции До настоящего момента вы конфигурировали именованные объекты, но у объектов необязательно должны быть имена. Вы можете конфигурировать составные объекты, не присваивая им имена: 474 Классы 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 .FirstOrDefault(); Вы запрашиваете тип Steak в методе GetObjectsOfType . Spring.NET найдет все сконфигурированные объекты, соответствующие запрашиваемому типу (независимо от того, именованные они или нет), и вернет их в виде словаря. Ключевыми словами данного словаря являются имена объектов, но поскольку вы не знаете имен, вас интересуют только значения. Свойство Values – это экземпляр не generic-интерфейса ICollection , поэтому для того чтобы использоват ь LINQ, мы должны каким-либо образом привести его к generic- последовательности. Один и вариантов – использовать метод Cast , но более безопасно – использовать фильтр OfType . Несмотря на то, что метод Cast мог бы выдать исключение в случае наличия элемента, который нельзя привести к желаемому типу, метод OfType фильтрует последовательно сть. Наконец, мы получаем из последовательност и объект. В данном случае мы использовали FirstOrDefault , но более строгое ограничение вводится с помощью метода расширения Single Оба класса SauceBéarnaise и Steak реализуют интерфейс IIngredient . При конфигурировании объектов Spring.NET не накладывает никаких ограничений на то, сколько объектов данного интерфейса мы можем сконфигурировать, но он все равно позволяет нам разрешать их с помощью метода GetObjectsOfType : var ingredients = context.GetObjectsOfType(typeof(IIngredient)); При наличии предыдущей конфигурации возвращаемый словарь ingredients будет содержать экземпляры как SauceBéarnaise , так и Steak , и для извлечения конкретных интересующих нас элементов мы можем использовать LINQ-запросы, как делали это в предыдущем примере. Несмотря на то, что мы можем сконфигурировать несколько объектов IIngredient без имен, при необходимости мы можем присвоить им имена: 475 Это позволяет нам разрешать каждый из объектов по его имени: 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: Интеграция списка зависимостей Т ри реализации ICourse конфигурируются в виде именованных объектов. При конфигурировании объекта ThreeCourseMeal мы можем ссылаться на имена, когда интегрируем аргументы конструктора. Элемент constructor-arg также принимает необязательные атрибуты name или index , которые мы можем использоват ь, чтобы точно указать, на какой параметр мы ссылаемся. Но в этом примере мы перечисляем их все в соответствующем порядке. Явное преобразование аргументов конструктора в именованные объекты – повсеместно применяемое решение. Сделать это мы можем, даже если конфигурируем именованные объекты в одном XML ресурсе, а конструктор – в совершенно другом ресурсе, поскольку единственное средство идентификации, которое связывает именованный объект с 476 аргументом, – это имя. Это всегда выполнимо, но может стать хрупким решением, если нам придется управлять большим количеством имен. Когда основной причиной, побуждающей нас отказаться от автоматической интеграции, является неопределенно сть, наилучшее решение – сконструировать API, помогающее нам избавиться от этой неопределенно сти. Все это также приводит к лучшей конструкции в целом. В следующем разделе вы увидите, как можно использовать менее неопределенный и более гибкий подход, при котором разрешается использовать в обеде несколько блюд. Т еперь вы должны изучить то, как Spring.NET работает со списками и последовательностя ми. Интеграция последовательностей В разделе 10.3.2 "Разработка пользовательского стиля существования" мы обсуждали, как выполнить рефакторинг явного класса ThreeCourseMeal к более универсальному классу Meal , который обладает приведенным ниже конструктором: public Meal(IEnumerable Вы можете сконфигурировать Spring.NET так, чтобы интегрировать экземпляр ы Meal с соответствующими зависимостями ICourse , и я покажу вам, как это сделать. После рассмотрения этого вопроса вы должны будете приобрести хорошее понимание тех возможностей, которые доступны в случае необходимости конфигурирования экземпляров, имеющих последовательности зависимостей. Автоматическая инте грация последовательностей Spring.NET хорошо разбирается в массивах, но не в других типах последовательностей. Если нам необходим массив конкретных абстракций, и мы собираемся использовать все эти сконфигурированные абстракции, то в этом случае применяется автоматическая интеграция. К примеру, представьте себе, что класс Meal предлагает следующу ю перегрузку конструктора: public Meal(params ICourse[] courses) Если мы хотим, чтобы все сконфигурированные объекты ICourse были внедрены в Meal , то мы можем предоставить следующу ю конфигурацию: Объект Meal конфигурируется для автоматической интеграции, и, поскольку Spring.NET по существу понимает массивы, он находит все объекты, которые реализуют интерфейс ICourse , и снабжает их конструктором Meal . Автоматически интегрировать массивы зависимостей довольно легко. А теперь представьте себе, что перегрузки конструктора, которая принимает в качестве параметра массив ICourse , не существует. У вас имеется только конструктор, который принимает в качестве параметра IEnumerable . Хотя в этом случае 477 автоматическая интеграция и не работает, вы можете воспользоваться преимуществами встроенного понимания массивов, определив простой Decorat or, экземпляры которого должны создаваться с помощью массива. Приведенный ниже листинг демонстрирует generic-реализацию. Не забывайте о том, что принятие IEnumerable в качестве параметра конструктора указывает на статически типизированный запрос данной конкретной зависимости. Все, что вам необходимо сделать, является столь же простым, как и преобразование этого запроса в запрос массива того же типа. Листинг 12-2: Преобразование запросов последовательностей в запросы массивов 1. public class ArrayEnumerable 2. { 3. private readonly IEnumerable 4. public ArrayEnumerable(params T[] items) 5. { 6. if (items == null) 7. { 8. throw new ArgumentNullException("items"); 9. } 10. this.sequence = items; 11. } 12. public IEnumerator 13. { 14. return this.sequence.GetEnumerator(); 15. } 16. } Строка 1: Определение последовательности Строка 4: Необходим массив ArrayEnumerable реализует IEnumerable , таким образом, он подходит для любого конструктора, которому необходима такая последовательность. С другой стороны, для него нужен массив такого же типа. Поскольку Spring.NET в сущности знает, как работать с массивами, ему может подойти закрытый ArrayEnumerable , снабженный всеми объектами, которые совпадают с типом элемента T Для того чтобы соответствующим образом интегрировать класс Meal со всеми объектами ICourse , вы можете сконфигурировать контекст следующим образом: Вы задаете Courses в виде ArrayEnumerable с включенной возможностью автоматической интеграции. Поскольку только для его конструктора необходим массив ICourse 'ов, Spring.NET автоматически интегрирует его со всеми реализациями ICourse , которые только может найти: Rillettes , CordonBleu и MousseAuChocolat Для класса Meal нужен IEnumerable , и, кроме того, он конфигурируется таким образом, чтобы иметь возможность автоматически интегрироваться. Когда вы отправите Spring.NET запрос на разрешение объекта Meal , он будет искать сконфигурированный 478 объект, который реализует IEnumerable , и найдет объект Courses . Все три объекта ICourse будут внедрены в объект Meal посредством объекта Courses Класс ArrayEnumerable – это небольшая забава, которая заполняет маленький пробел в Spring.NET. Это чисто инфраструктурный компонент, который можно упаковать в повторно используемую библиотеку. Spring.NET автоматически обрабатывает массивы и, благодаря небольшой помощи ArrayEnumerable , также обрабатывает и другие запросы последовательно стей путем разрешения их в последовательности объектов, реализующих запрашиваемый тип. Единственное, что вам нужно сделать, – сконфигурировать ArrayEnumerable соответствующего типа элемента. Т олько в случае, когда вам необходимо явно отобрать несколько компонентов из большого набора, вам нужно выполнить больше действий. Это возможно благодаря более явной конфигурации. О тбор нескольких объектов из большого набора Когда мы используем возможность Spring.NET разрешать массивы все объекты внедряются в потребителей. Чаще всего это корректное поведение. Однако, как показано на рисунке 12-4, могут возникать ситуации, когда нам необходимо отобрать несколько компонентов из большого набора всех зарегистрированных компонентов. Рисунок 12-4: В ситуации, продемонстрированной слева, мы хотим явным образом отобрать определенные зависимости из большого списка всех сконфигурированных объектов. Это отличается от ситуации, приведенной справа, когда мы отбираем все без разбора. Когда мы ранее позволяли Spring.NET автоматически интегрировать все сконфигурированные объекты, это соответствовало ситуации, продемонстрированной в правой части рисунка 12-4. Если нам нужно сконфигурировать экземпляр так, как это 479 показано в левой части рисунка, мы должны явным образом определить, какие объекты необходимо использовать. Это легко сделать с помощью именованных объектов, поскольку это более или менее идиоматичный способ конфигурирования Spring.NET, который позволяет использовать отдельный XML элемент list для обращения к конкретному сценарию. В следующем листинге продемонстрирован соответствующий пример. Листинг 12-3: Внедрение именованных объектов в последовательнос ть 1. 2. 3. 4. 5. Строка 7: Указывает на список Строка 8-10: Именованные объекты Элемент list можно использовать для указания на то, что следующие элементы являются элементами списка. Когда Spring.NET интегрирует список, он создает массив, тип которого задан атрибутом element-type . Элемент list может содержать множество различных дочерних элементов. Элемент ref используется для обращения к другим именованным объектам. При разрешении объекта Meal вы получите экземпляр Meal с Rillettes , CordonBleu и MousseAuChocolat в качестве содержащихся в нем блюд, при этом LobsterBisque не используется. Еще раз вы видите, что Spring.NET по существу работает с массивами. Несмотря на отсутствие поддержки других типов последовательностей вы можете обойти это ограничение посредством заворачивания последовательнос тей в класс наподобие ArrayEnumerable Потребители, которые полагаются на последовательности зависимостей, могут выступать в роли самого интуитивного использования составных экземпляров одной и той же абстракции. Но до того как мы перестанем заниматься этим вопросом, нам необходимо рассмотреть еще один случай, когда в дело вступают составные экземпляры. Интеграция Decorator'ов В разделе 9.1.2 "Паттерны и принципы механизма перехвата" мы обсуждали то, насколько паттерн проектирования Decorator полезен при реализации сквозных сущностей. По определению Decorator'ы представляют собой составные типы одной и той же абстракции. У нас есть, по крайней мере, две реализации абстракции: сам Decorat or и вложенный в 480 него тип. Если бы мы помещали Decorator'ы в стек, то у нас было бы еще больше реализаций. Это еще один пример составных объектов, которые реализуют одну и ту же абстракцию. В отличие от предыдущих разделов эти объекты не являются концептуально равносильными, а зависят друг от друга. Я продемонстрирую вам два разных способа конфигурирования Spring.NET , используемых для работы с этим паттерном. С оздание обертки с помощью именованны х объектов На протяжении всей этой главы вы видели множество примеров того, как обращаться к именованным объектам как к аргументам конструктора. Кроме того, вы можете использовать этот идиоматичный подход для конфигурирования Decorat or'ов. Класс Breading – это Decorator для IIngredient . Для получения экземпляра, который необходимо в него вложить, он использует Constructor Injection: public Breading(IIngredient ingredient) Для того чтобы получить Cotoletta , вам хотелось бы вложить VealCutlet (еще один IIngredient ) в класс Breading . Поскольку вы уже знаете, как соединять именованные объекты с аргументами конструктора, вам будет привычно выполнять действия, аналогичные следующим: Строка 2: Ссылка на именованный объект На данный момент этот подход должен быть вам знаком. Для интеграции объекта Breading с объектом Cutlet вы используете ссылку на именованный объект. Поскольку Spring.NET явным образом не работает с преобразованиями абстракций в конкретные типы, каждый из этих двух элементов является таким же самым объектом, как и остальные элементы object . Т о, что они оба реализуют интерфейс IIngredient , никак не влияет на способ их конфигурирования. При разрешении имени Breading вы получаете экземпляр Breading , в который вложен VealCutlet Это общепринятый способ создания обертки для компонента, но в тех случаях, когда вас не интересует вложенный экземпляр, вы можете воспользоваться более неявным способом. С оздание обертки с помощью вложенных объектов Если вам никогда не потребуется разрешать вложенные компоненты напрямую, вы можете воспользоваться более неявным способом создания их обертки. Представьте себе, что вам никогда не нужно будет разрешать VealCutlet непосредственно как IIngredient Когда вам нужен IIngredient , вам всегда нужен Cotoletta 481 В таких случаях нет нужды явным образом конфигурировать VealCutlet в виде независимого объекта. Вместо этого вы можете воспользоваться преимуществами синтаксиса вложенных объектов Spring.NET: Spring.NET позволяет вам задавать объекты в виде вложенных элементов. Вместо того чтобы ссылаться на именованный объект, элемент constructor-arg может содержать конфигурации всего объекта. Поскольку предполагается, что кроме как из конфигурации ссылаться на объект VealCutlet откуда-то еще вам не нужно будет, вы можете предоставить неименованный элемент object с корректным атрибутом type . Будучи вложенным в элемент constructor-arg , тип VealCutlet будет разрешаться в виде первого аргумента конструктора класса Breading Существует несколько доступных вариантов конфигурирования Decorator'ов. В отличие от Castle W indsor Spring.NET явно не понимает Decorator'ы, что может показаться слегка удивительным, поскольку, как и W indsor, он предполагает максимальную поддержку паттерна Decorator: механизм перехвата. Создание перехватчиков В разделе 9.3.3 "Пример: перехват с помощью W indsor" вы видели пример того, как добавить в WCF-приложение обработчик ошибок и Circuit Breaker с помощью возможности динамического перехвата, предлагаемой Castle W indsor. Чтобы продемонстрировать возможности перехвата в Spring.NET и сравнить их с Castle Windsor и Unity, я разберу точно такой же пример, но реализованный с помощью Spring.NET . Как показано на рисунке 12-5, добавление аспекта в Spring.NET – довольно простой процесс. Рисунок 12-5: Простой процесс добавления аспекта в Spring.NET. Основная часть работы заключается в разработке самого перехватчика, но после создания перехватчика необходимо добавить его в контейнер. Это, как и все другое, делается в XML конфигурации. Однако мы не можем конфигурировать перехватчики, пока не реализуем их. Поэтому первый шаг – написать некоторый код для обработчика ошибок и перехватчиков Circuit Breaker . После создания обработчика ошибок и перехватчика Circuit Breaker мы можем с помощью них сконфигурировать контейнер. Реализация перехватчика обработчика исключений Реализация перехватчика в Spring.NET требует от нас реализации интерфейса IMethodInterceptor . В следующем листинге демонстрируется, как реализовать 482 стратегию обработки исключений из главы 9 "Механизм перехвата". Эта конкретная реализация, приведенная для Spring.NET , соответствует листингу 9-8, приведенному для Castle W indsor и листингу 14-13, приведенному для Unity. Листинг 12-4: Реализация обработчика исключений IMethodInterceptor 1. public class ErrorHandlingInterceptor : IMethodInterceptor 2. { 3. public object Invoke(IMethodInvocation invocation) 4. { 5. try 6. { 7. return invocation.Proceed(); 8. } 9. catch (CommunicationException e) 10. { 11. this.AlertUser(e.Message); 12. } 13. catch (InvalidOperationException e) 14. { 15. this.AlertUser(e.Message); 16. } 17. return null; 18. } 19. private void AlertUser(string message) 20. { 21. var sb = new StringBuilder(); 22. sb.AppendLine("An error occurred."); 23. sb.AppendLine("Your work is likely lost."); 24. sb.AppendLine("Please try again later."); 25. sb.AppendLine(); 26. sb.AppendLine(message); 27. MessageBox.Show(sb.ToString(), "Error", 28. MessageBoxButton.OK, MessageBoxImage.Error); 29. } 30. } Строка 3: Реализация логики перехвата Строка 5-8: Попытка вернуть результат Строка 9-16: Обработка исключений Класс ErrorHandlingInterceptor реализует интерфейс IMethodInterceptor , который определяет только единственный метод Invoke . Именно здесь вы должны определить логику перехвата. Единственный аргумент метода – экземпляр интерфейса IMethodInvocation . Благодаря его методу Proceed вы пытаетесь вызвать вложенный метод и вернуть результат. Однако, поскольку цель перехватчика заключается в обработке известных исключений, вызов метода Proceed вставляется в блок try Если метод Proceed (или лучше вложенный метод, который вызывает Proceed ) выдает одно из известных исключений, перехватчик ловит его и предупреждает пользователя об ошибке. В данном примере совокупность известных исключений жестко закодирована в самом перехватчике, но более общепринятая реализация могла бы вместо такого поведения подавить исключения или выдать их повторно в соответствии с внедренной спецификацией. 483 Поскольку метод Invoke должен возвращать объект, он возвращает null , когда выдается исключение и завершает свое выполнение. Это корректное значение для тех случаев, когда вложенный метод возвращает значение типа void , но когда метод возвращает реальные значения, это может стать проблемой, поскольку все это легко может привести к NullReferenceExceptions . Т ем не менее, мы можем создать другой перехватчик, который устанавливает соответствующие значения по умолчанию для различных типов возвращаемого результата. Это было бы более корректно, нежели попытка предугадать корректное значение по умолчанию в рамках ErrorHandlingInterceptor , являющегос я универсальным перехватчиком, который можно использовать для перехвата любого интерфейса. Кроме того, это соответствовало бы принципу единичной ответственности. ErrorHandlingInterceptor заботится об обработке конкретных исключений, полученных для вложенного компонента. Данный компонент сам по себе может являться другим перехватчиком в виде Circuit Breaker Ре ализация Circuit Breaker перехватчика Circuit Breaker перехватчик слегка более сложен, поскольку для него нужна зависимость ICircuitBreaker , но, как показывает следующий листинг, мы обращаемся к нему путем применения стандартного паттерна Constructor Injection. Когда дело доходит до компоновки класса, Spring.NET поступает с ним так же, как и с любым другим объектом, поэтому, пока он может разрешать зависимость, все идет хорошо. Листинг 12-5: Реализация Circuit Breaker перехватчика IMethodInterceptor 1. public class CircuitBreakerInterceptor : 2. IMethodInterceptor 3. { 4. private readonly ICircuitBreaker breaker; 5. public CircuitBreakerInterceptor( 6. ICircuitBreaker breaker) 7. { 8. if (breaker == null) 9. { 10. throw new ArgumentNullException("breaker"); 11. } 12. this.breaker = breaker; 13. } 14. public object Invoke(IMethodInvocation invocation) 15. { 16. this.breaker.Guard(); 17. try 18. { 19. var result = invocation.Proceed(); 20. this.breaker.Succeed(); 21. return result; 22. } 23. catch (Exception e) 24. { 25. this.breaker.Trip(e); 26. throw; 27. } 28. } 29. } Строка 1-2: Реализация IMethodInterceptor 484 Строка 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 довольно легко. Все, что вам нужно сделать, – выполнить конфигурацию самих перехватчиков: Не обладая особой фантазией, я присвоил объектам id , совпадающие с их type . Обратите внимание на то, что Constructor Injection, основанный на автоматической интеграции, включен по умолчанию. В то время как ErrorHandlingInterceptor обладает конструктором по умолчанию, CircuitBreakerInterceptor для запрашивания 485 ICircuitBreaker использует Constructor Injection. М еханизм автоматической интеграции работает как для CircuitBreakerInterceptor , так и для ErrorHandlingInterceptor , а также для большинства других объектов конфигурации, поэтому включение его по умолчанию является самым простым способом. После того как вы разместили перехватчики, осталось только сконфигурировать объект IProductManagementAgent с необходимыми перехватчиками. На рисунке 12-6 продемонстрирована нужная вам конфигурация. Рисунок 12-6: IProductManagementAgent должен быть вложен в Circuit Breaker перехватчик таким образом, чтобы при выдаче агентом исключения цепь на некоторое время открывалась Поскольку Circuit Breaker лишь регистрирует исключения, а не обрабатывает их, за это несет отвественность перехватчик обработчика ошибок, который должен, по крайней мере, уметь обрабатывать исключения, возникающие в обоих агентах, а также в Circuit Breaker Как показывает следующий листинг, делается это при помощи синтаксиса конфигурации XML, а также специального пространства имен и классов, которые Spring.NET предоставляет для этих целей. Основная концепция конфигурирования механизма перехвата заключается в отделение того, что делать, от того, где это делать. Мы должны предоставить ответ на оба эти вопроса, но отвечаем на них по отдельности, а затем связываем ответы вместе. Листинг 12-6: Конфигурирование перехватчиков 1. 2. type="RegularExpressionMethodPointcut"> 4. 5. 6. 7. 8. 9. 10. 11. advice-ref="ErrorHandlingInterceptor" 13. order="1" /> 14. advice-ref="CircuitBreakerInterceptor" 16. order="2" /> 17. 486 Строка 1: Объект для перехвата Строка 2-9: Указание того, в каком месте выполнять перехват Строка 10-17: Привязка перехватчиков к спецификации В предыдущем коде вы регистрировали перехватчики, но помимо этого вам необходимо зарегистрировать классы, которые нужно будет перехватить. В этом примере один такой класс, но при желании вы можете перехватить множество различных классов с помощью одного и того же набора перехватчиков. Чтобы указать, какие классы или методы нужно перехватить, вы должны задать так называемое Pointcut, придуманное название для правила, определяющего, что нужно перехватить. Если вы вспомните первоначальное введение в механизм перехвата, приведенное в главе 9, то поймете, что Pointcut соответствует IModelInterceptorsSelector , используемому в Castle W indsor и реализованно му в листинге 9-10. Как и Castle W indsor, Spring.NET позволяет писать императивный код, определяющий Pointcut, но помимо этого он предоставляет некоторые декларативные Pointcut'ы. Одним из таких статических Pointcut'ов является RegularExpressionMethodPointcut , который можно использовать для задания соответствующего правила с регулярным выражением. При каждом вызове метода он будет пытаться сопоставить полное имя метода с регулярным выражением. В этом конкретном случае вы планируете сопоставить только члены класса WcfProductManagementAgent Наконец, вам нужно связать Point cut с перехватчиками, которые вы уже зарегистрировали ранее. Делается это с помощью последовательност и элементов advisor , которые объявляют перехватчики и порядок их компоновки. Заметьте, что поскольку вы сначала указываете ErrorHandlingInterceptor , он становится крайним перехватчиком, перехватывающим CircuitBreakerInterceptor Последнее, что вам нужно сделать для того, чтобы сконфигурировать приложение, в котором используется сильное управление внешним взаимодействием, – убедиться, что все зависимости могут удовлетворять требуемым условиям. Поскольку для CircuitBreakerInterceptor нужен ICircuitBreaker , вы также должны сконфигурировать и этот объект: Для конструктора CircuitBreaker нужна задержка в виде экземпляра TimeSpan , и вы будете задавать это элементарное значение как вложенное. Для этого вы должны отключить используемые по умолчанию настройки автоматического интегрирования с тем, чтобы явно установить задержку, равной одной минуте. Для большей эффективности важно наличие только одного экземпляра Circuit Breaker (по крайней мере, для одного внешнего ресурса), но, поскольку по умолчанию используется область применения Singleton, вам не нужно явным образом выражать данное требование. 487 Этот пример продемонстрировал, как реализовать механизм динамического перехвата в Spring.NET. По-моему, я получил сложность, сравнимую с поддержко й контейнерами Castle W indsor и Unity механизма перехвата. Будучи не таким уже незначительным, полученное преимущество становится довольно значимым. Механизм перехвата – динамическая реализация паттерна Decorator, а сам паттерн Decorator является сложным применением составных объектов одного и того же типа. Spring.NET дает нам возможность работать с составными компонентами несколькими различными способами. Мы можем конфигурировать их в виде альтернатив друг другу, в виде пиров, которые разрешаются в виде последовательно стей, или в виде иерархических Decorator'ов, или даже в виде перехватчиков. Когда дело дойдет до массивов, Spring.NET поймет, что делать, но при этом мы часто можем преобразовыват ь в массивы и другие типы последовательност ей, используя такой адаптер, как класс ArrayEnumerable Кроме того, все это позволяет нам явным образом задавать, как компонуются сервисы в случае, если нам необходим более явный контроль. Помимо этого такие ситуации могут возникать, когда нам приходится иметь дело с API, отклоняющимися от Constructor Injection. До настоящего момента вы наблюдали за тем, как конфигурировать объекты, включая то, как определять области применения, и как работать с составными компонентами. Но до этого момента мы позволяли контейнеру подключать зависимости, явным образом предполагая, что все компоненты используют Constructor Injection. Поскольку это не всегда происходит именно так, в следующем разделе мы сделаем краткий обзор того, как работать с классами, экземпляры которых должны создаваться особым образом. 488 12.4. Конфигурирование сложных API До настоящего момента мы рассматривали то, как можно конфигурировать компоненты, использующие Constructor Injection. Одним из главных преимуществ Constructor Injection является то, что DI-контейнеры, например, Spring.NET , могут с легкостью понимать, как компоновать и создавать все классы диаграммы зависимостей. Все становится менее понятным, когда API не столь хорошо функционируют. В этом разделе вы увидите, как работать с простейшими аргументами конструктора, статическими фабриками и Property Injection. Все это требует особого внимания. Давайте начнем с рассмотрения классов, которые принимают в качестве параметров простейшие типы, например, строки и целые числа. Конфигурирование простейших зависимостей Пока мы внедряем абстракции в потребителей, все в порядке. Но этот процесс усложняется, если конструктор зависит от простейшего типа, например, строкового, числового или перечисляемого. Наиболее часто это случается в реализациях доступа к данным, которые принимают в качестве параметра конструктора строку соединения. Но в то же время это является более общей проблемой, касающейся всех строковых и числовых типов. В сущности, регистрация строкового или числового типа в качестве компонента контейнера не имеет особого смысла, а в Spring.NET невозможно зарегистрировать значение простейшего типа в качестве объекта. Рассмотрим в качестве примера приведенный ниже конструктор: public ChiliConCarne(Spiciness spiciness) В этом примере Spiciness имеет перечисляемый тип: public enum Spiciness { Mild = 0, Medium, Hot } П редупреждение Согласно эмпирическому правилу перечисления являются code smell'ами и их нужно преобразовывать в полиморфные классы (имеющие разное состояние). Тем не менее, для этого примера они вполне нам подходят. Необходимо явным образом передать значение параметра конструктора spiciness в виде составляющей конфигурации объекта ChiliConCarne : 1. 2. 3. 489 Строка 2: Передача значения Spring.NET снабжен конвертерами нескольких типов, которые конвертируют текстовые элементы в экземпляры необходимых типов. Один из встроенных конвертеров преобразует текст в элемент перечисляемого типа, что позволяет нам сделать текст "Hot" значением элемента constructor-arg . Spring.NET смотрит на тип параметра конструктора класса ChiliConCarne , определяет, что это параметр перечисляемого типа и использует конвертер соответствующего типа для преобразования текста "Hot " в значение Spiciness.Hot Эта возможность Spring.NET предназначена для тех ситуаций, в которых нам необходимо передавать простейшие значения в виде аргументов. В примере аргумент ChiliConCarne был передан в конструктор, но иногда встречаются ситуации, когда класс не имеет открытого конструктора. Конфигурирование статических фабрик Экземпляры некоторых классов нельзя создать с помощью открытого конструктора. В этом случае для создания экземпляро в типа мы должны использовать некоторого рода фабрику. Это всегда проблематично для DI-контейнеров, поскольку по умолчанию им нужны открытые конструкторы. Рассмотрим приведенный ниже пример конструктора открытого класса JunkFood : internal JunkFood(string name) Даже если класс JunkFood является открытым, конструктор расположен внутри него. Очевидно, экземпляры JunkFood должны создаваться с помощью статического класса JunkFoodFactory : public static class JunkFoodFactory { public static IMeal Create(string name) { return new JunkFood(name); } } С точки зрения Spring.NET , это проблемное API, поскольку в нем отсутствуют точно выраженные и заданные соглашения касательно статических фабрик. Т ут требуется помощь – и мы можем предоставить ее при конфигурировании объекта: Как и всегда, конфигурация объекта выражается в элементе object , но вместо задания типа самого объекта, вы задаете тип фабрики. Помимо этого вы должны определить, что именем factory-method будет Create . Заметьте, что хотя метод Create по существу и не является конструктором, вы все равно используете элемент constructor-arg для того, чтобы задать значение аргумента name метода Create 490 Хотя атрибут type задается как тип фабрики, а не тип результата, Spring.NET достаточно умен, чтобы понять, что выходной тип метода Create – это JunkFood . Это означает, что вы можете не использовать атрибут id и создать неименованный объект, как вы это делали в разделе 12.3.1 "Выбор из составных кандидато в", и вы все равно сможете разрешить объект JunkFood с помощью метода GetObjectsOfType Последним рассматриваемым нами отклонением от паттерна Constructor Injection является Property Injection. Property Injection – это менее определенная форма механизма внедрения зависимостей, поскольку компилятор не принудает нас задавать значение свойства, доступного для записи. Все-таки, Spring.NET понимает Propert y Injection и работает с ним интуитивным образом. Если мы разрешим автоматическое интегрирование, то Propert y Injection будет работать, но, помимо этого, он будет работать и посредством прямого интегрирования. Рассмотрим класс CaesarSalad : public class CaesarSalad : ICourse { public IIngredient Extra { get; set; } } По всеобщему заблуждению в состав салата "Цезарь" входит курица, но это не правда. По существу "Цезарь" является салатом, но, поскольку с курицей он вкуснее, то ее часто предлагают использовать в нем в качестве дополнительного ингредиента. Класс CaesarSalad моделирует такую возможность посредством доступного для записи свойства под названием Extra Если вы конфигурируете только класс CaesarSalad , явно не обращаясь к свойству Extra , то этому свойству не будет присвоено значение. Вы все равно можете разрешать экземпляр, но свойство Extra будет иметь значение по умолчанию, которое ему присвоил конструктор (если только это имеет место). Явное интегрирова ние свойств Существует несколько способов, с помощью которых можно сконфигурировать CaesarSalad таким образом, чтобы свойство Extra заполнялос ь соответствующим образом. Один из таких способов – явным образом интегрировать свойство с именованным объектом: Элемент property указывает на то, что именем свойства является "Extra", и что в качестве его значения необходимо использовать объект Chicken . Вместо того чтобы использовать ссылку на именованный объект, вы можете использовать вложенный объект: 491 Вы всегда можете явным образом интегрировать свойства с элементом property , но поскольку свойство идентифицируется по имени, это довольно хрупкий подход. Если вы позднее переименуете свойство, большинство инструментов рефакторинга не смогут идентифицировать и изменять значение атрибута name в различных XML-файлах. Это может привести к ошибкам во время выполнения. Наилучшее решение – автоматически интегрировать объекты. Автоматическое инте грирование свойств Как вы помните из раздела 12.1.2 "Конфигурирование контейнера", вы должны явно разрешить автоматическое интегрирование в Spring.NET, но как только вы это сделаете, заработает Property Injection. Если зависимость не удовлетворяет условиям, то свойство игнорируется: В этом примере объект Course – единственный объект, сконфигурированный для контейнера. Несмотря на то, что он сконфигурирован с возможностью автоматической интеграции, свойству Extra никогда не будет присвоено значение, поскольку отсутствует доступный объект IIngredient . Выдается исключение, а свойство просто игнорируется. Все меняется, как только объект IIngredient становится доступным: Т еперь при разрешении объекта Course вы будете получать экземпляр CaesarSalad со свойством Extra , имеющим значение Chicken Использование автоматического интегрирования – более ясный способ, поскольку мы можем переименовывать свойства, не опасаясь, что конфигурация Spring.NET будет разрушена во время выполнения. Вы видели, как использовать Spring.NET для работы с более сложными API разработки. В общем случае вы всегда можете явным образом конфигурировать интегрирование с помощью XML-конфигурации, но вы также видели и то, что паттерн Property Injection можно сконфигурировать таким образом, что он будет поддерживать возможность автоматической интеграции. 492 12.5. Резюме Среди рассматриваемых в этой главе DI-контейнеров Spring.NET уникален тем, что это единственный контейнер, реализованный как порт из Java. И это более очевидный факт, чем то, что Spring.NET в большой степени полагается на XML-конфигурацию. Большинство из старейших DI-контейнеров в начале своего пути были сильно сфокусированы на XML. Но если другие DI-контейнеры продвинулись вперед, Spring.NET не сделал ни одного шага в эту сторону. Несмотря на то, что в последующих версиях Spring.NET возможно в большей степени будут поддерживат ься технологии использования кода в качестве конфигурации и даже конфигурация на основании соглашений, применение Spring.NET на сегоднящний день несет за собой большое количество XML-кода. Возможно, самым слабым местом Spring.NET является ограниченная поддержка механизма управления жизненным циклом. С одной стороны, Spring.NET, как и StructureMap, явным образом не поддерживает высвобождение диаграммы объектов (это можно рассматривать как отсутствие каких-либо возможностей). С другой стороны, Spring.NET не поддерживает пользовательские области применения объектов. Хотя возможности механизма управления жизненными циклами не столь примечательны, Spring.NET – один из нескольких DI-контейнеров, предлагающих встроенный механизм перехвата. Spring.NET все еще базируется на .NET 1.1, что, возможно, для некоторых является и преимуществом. Мы получаем полноценный пакет документации и возможность покупки коммерческого сопровождения. Поэтому, несмотря на то, что вцелом Spring.NET может показаться старомодным, он все равно является одним из профессиональных пакетов. Мы можем рассматривать Spring.NET как устаревший, но проверенный фреймворк. В противоположност ь этому в следующей главе рассматривается один из самых современных DI-контейнеров: Autofac. 493 13. Autofac Меню: Знакомство с Autofac Управление жизненным циклом Работа с составными компонентами Конфигурирование сложных API В предыдущих главах вы наблюдали за тем, как применять к конкретным DI-контейнерам принципы и паттерны, описанные в частях 1-3. В этой главе мы то же самое будем делать для DI-контейнера Autofac. Autofac – это пример так называемых "DI-контейнеров второго поколения". Примечание Примерами DI-контейнеров второго поколения являются DI-контейнеры Ninject и Unity. В основе DI-контейнеров второго поколения лежит .NET 3.5, поэтому их структуры чаще всего базируются напрямую на возможностях языка и платформы, которые были доступны в то время. Они не только отлично понимают дженерики, но и используют множество лямбда-выра жений в качестве центральных API элементов. Несмотря на то, что большинство развитых DI-контейнеров также поддерживают эти более современные конструкции языка, в основе их центральных движков обычно лежат более ранние версии .NET. Поскольку контейнеры второго поколения не обладают таким наследием, обычно в их основе заложены самые базовые составляющие этих возможностей. Autofac – это достаточно исчерпывающий DI-контейнер, имеющий тщательно спроектированный и логичный API. Он начал развиваться с конца 2007 года и, кажется, имеет достаточно обширную клиентскую базу. В этой главе мы рассмотрим то, как можно использовать Autofac для применения принципов и паттернов, описанных ранее в частях 1-3. На рисунке 13-1 продемонстрирована структура этой главы. Рисунок 13-1: Эта глава состоит из четырех разделов. Первый раздел знакомит нас с API Autofac и должен рассматриваться в качестве предпосылки следующих трех разделов. Каждый из четырех разделов может быть прочитан независимо от остальных разделов, несмотря на то, что в четвертом разделе используются методы, которые вводятся в третьем разделе. Тема четвертого раздела значительно отличается от темы третьего, поэтому читать их можно независимо друг от друга. Но, все-таки, вам может понадобиться более детальная информация о некоторых составляющих API. 494 Эта глава должна дать вам возможность приступить к работе, а также справиться с большинством общепринятых проблем, которые могут возникнуть при ежедневном использовании Autofac. Это не окончательная трактовка Autofac, поскольку всецелое его рассмотрение заняло бы несколько глав или, может быть, даже целую книгу. Но если вам хочется получить больше информации об Autofac, то лучше всего начать изучения с главной страницы сайта Autofac – ht tp://autofac.org Вы можете прочитать эту главу, не читая при этом остальные главы части 4, специально для того, чтобы познакомиться с Autofac, или можете прочитать ее совместно с остальными главами части 4, чтобы сравнить DI-контейнеры. Цель данной главы – показать, как Autofac связан с паттернами и принципами, описанными в частях 1-3, и как он их реализует. 13.1. Знакомство с Autofac 13.2. Управление жизненным циклом 13.3. Работа с составными компонентами 13.4. Регистрация сложных API 13.5. Резюме 495 13.1. Знакомство с Autofac Из этого раздела вы узнаете, где можно взять Autofac, что вы при этом получите и как начать его использовать. Кроме того, мы рассмотрим универсальные варианты конфигурирования, а также то, как пакетировать настройки конфигурации в повторно используемые компоненты. В таблице 13-1 содержится основополагающая информация, которая, скорее всего, понадобится вам для того, чтобы приступить к работе с Autofac Таблица 13-1: Краткая информация об Autofac Вопрос Ответ |