что такое dependency injection и зачем оно нужно

Dependency injection

От переводчика

Представляемый вашему вниманию перевод открывает серию статей от Jakob Jenkov, посвященных внедрению зависимостей, или DI. Примечательна серия тем, что в ней автор, анализируя понятия и практическое применение таких понятий как «зависимость», «внедрение зависимостей», «контейнер для внедрения зависимостей», сравнивая паттерны создания объектов, анализируя недостатки конкретных реализаций DI-контейнеров (например, Spring), рассказывает, как пришел к написанию собственного DI-контейнера. Таким образом, читателю предлагается познакомиться с довольно цельным взглядом на вопрос управления зависимостями в приложениях.

В данной статье сравнивается подход к настройке объектов изнутри и извне (DI). По смыслу настоящая статья продолжает статью Jakob Jenkov Understanding Dependencies, в которой дается определение самому понятию «зависимости» и их типам.

что такое dependency injection и зачем оно нужно. Смотреть фото что такое dependency injection и зачем оно нужно. Смотреть картинку что такое dependency injection и зачем оно нужно. Картинка про что такое dependency injection и зачем оно нужно. Фото что такое dependency injection и зачем оно нужно

Серия включает в себя следующие статьи

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

«Внедрение зависимостей» — это выражение, впервые использованное в статье Мартина Фаулера Inversion of Control Containers and the Dependency Injection Pattern. Это хорошая статья, но она упускает из виду некоторые преимущества контейнеров внедрения зависимостей. Также я не согласен с выводами статьи, но об этом — в следующих текстах.

Объяснение внедрения зависимостей

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

UPD: после обсуждения представленных автором фрагментов кода с flatscode и fogone, я принял решение скорректировать спорные моменты в коде. Изначальный замысел был в том, чтобы не трогать код и давать его таким, каков он написан автором. Оригинальный авторский код в спорных местах закомментирован с указанием «в оригинале», ниже дается его исправленная версия. Также оригинальный код можно найти по ссылке в начале статьи.

Этот DAO (Data Access Object), MyDao нуждается в экземпляре javax.sql.DataSource для того, чтобы получить подключения к базе данных. Подключения к БД используются для чтения и записи в БД, например, объектов Person.

Заметьте, что класс MyDao создает экземпляр DataSourceImpl, так как нуждается в источнике данных. Тот факт, что MyDao нуждается в реализации DataSource, означает, что он зависит от него. Он не может выполнить свою работу без реализации DataSource. Следовательно, MyDao имеет «зависимость» от интерфейса DataSource и от какой-то его реализации.

Класс MyDao создает экземпляр DataSourceImpl как реализацию DataSource. Следовательно, класс MyDao сам «разрешает свои зависимости». Когда класс разрешает собственные зависимости, он автоматически также зависит от классов, для которых он разрешает зависимости. В данном случае MyDao завсист также от DataSourceImpl и от четырех жестко заданных строковых значений, передаваемых в конструктор DataSourceImpl. Вы не можете ни использовать другие значения для этих четырех строк, ни использовать другую реализацию интерфейса DataSource без изменения кода.

Как вы можете видеть, в том случае, когда класс разрешает собственные зависимости, он становится негибким в отношении к этим зависимостям. Это плохо. Это значит, что если вам нужно поменять зависимости, вам нужно поменять код. В данном примере это означает, что если вам нужно использовать другую базу данных, вам потребуется поменять класс MyDao. Если у вас много DAO-классов, реализованных таким образом, вам придется изменять их все. В добавок, вы не можете провести юнит-тестирование MyDao, замокав реализацию DataSource. Вы можете использовать только DataSourceImpl. Не требуется много ума, чтобы понять, что это плохая идея.

Давайте немного поменяем дизайн:

Заметьте, что создание экземпляра DataSourceImpl перемещено в конструктор. Конструктор принимает четыре параметра, это — четыре значения, необходимые для DataSourceImpl. Хотя класс MyDao все еще зависит от этих четырех значений, он больше не разрешает зависимости сам. Они предоставляются классом, создающим экземпляр MyDao. Зависимости «внедряются» в конструктор MyDao. Отсюда и термин «внедрение (прим. перев.: или иначе — инъекция) зависимостей». Теперь возможно сменить драйвер БД, URL, имя пользователя или пароль, используемый классом MyDao без его изменения.

Внедрение зависимостей не ограничено конструкторами. Можно внедрять зависимости также используя методы-сеттеры, либо прямо через публичные поля (прим. перев.: по поводу полей переводчик не согласен, это нарушает защиту данных класса).

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

Теперь класс MyDao больше не зависит от класса DataSourceImpl или от четырех строк, необходимых конструктору DataSourceImpl. Теперь можно использовать любую реализацию DataSource в конструкторе MyDao.

Цепное внедрение зависимостей

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

Как вы можете видеть, теперь MyBizComponent зависит от класса DataSourceImpl и четырех строк, необходимых его конструктору. Это еще хуже, чем зависимость MyDao от них, потому что MyBizComponent теперь зависит от классов и от информации, которую он сам даже не использует. Более того, реализация DataSourceImpl и параметры конструктора принадлежат к разным слоям абстракции. Слой ниже MyBizComponent — это слой DAO.

Решение — продолжить внедрение зависимости по всем слоям. MyBizComponent должен зависеть только от экземпляра MyDao. Вот как это выглядит:

Снова зависимость, MyDao, предоставляется через конструктор. Теперь MyBizComponent зависит только от класса MyDao. Если бы MyDao был интерфейсом, можно было бы менять реализацию без ведома MyBizComponent.

Такой паттерн внедрения зависимости должен продолжается через все слои приложения, с самого нижнего слоя (слоя доступа к данным) до пользовательского интерфейса (если он есть).

Источник

Основы внедрения зависимостей

что такое dependency injection и зачем оно нужно. Смотреть фото что такое dependency injection и зачем оно нужно. Смотреть картинку что такое dependency injection и зачем оно нужно. Картинка про что такое dependency injection и зачем оно нужно. Фото что такое dependency injection и зачем оно нужно

В этой статье я расскажу об основах внедрения зависимостей (англ. Dependency Injection, DI) простым языком, а также расскажу о причинах использования этого подхода. Эта статья предназначена для тех, кто не знает, что такое внедрение зависимостей, или сомневается в необходимости использования этого приёма. Итак, начнём.

Что такое зависимость?

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

Как работать с зависимостями?

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

Первый способ: создавать зависимости в зависимом классе

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

Это очень просто! Мы создаем класс, когда нам это необходимо.

Преимущества

Недостатки

Каждый класс должен выполнять лишь свою работу.

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

Второй способ: внедрять зависимости через пользовательский класс

Итак, понимая, что внедрение зависимостей внутри зависимого класса — не самая лучшая идея, давайте изучим альтернативный способ. Здесь зависимый класс определяет все необходимые ему зависимости внутри конструктора и позволяет пользовательскому классу предоставлять их. Является ли такой способ решением нашей проблемы? Узнаем немного позже.

Посмотрите на пример кода ниже:

Преимущества

Недостатки

Второй способ очевидно работает лучше первого, но у него всё ещё есть свои недостатки. Возможно ли найти более подходящее решение? Прежде чем рассмотреть третий способ, давайте сначала поговорим о самом понятии внедрения зависимостей.

Что такое внедрение зависимостей?

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

Исходя из этого определения, наше первое решение явно не использует идею внедрения зависимостей, а второй способ заключается в том, что зависимый класс ничего не делает для предоставления зависимостей. Но мы все ещё считаем второе решение плохим. ПОЧЕМУ?!

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

Как же сделать лучше? Давайте рассмотрим третий способ обработки зависимостей.

Третий способ: пусть кто-нибудь ещё обрабатывает зависимости вместо нас

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

«Чистая» реализация внедрения зависимостей (по моему личному мнению)

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

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

Любой фреймворк внедрения зависимостей имеет две неотъемлемые характеристики. Вам могут быть доступны и другие дополнительные функции, но эти две функции будут присутствовать всегда:

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

Преимущества

Обратите внимание, никакой код внутри приложения не меняется, только метод провайдера. Кажется, что ничего не может быть ещё проще и гибче.

Недостатки

Заключение

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

Источник

Внедрение зависимостей – проще, чем кажется?

что такое dependency injection и зачем оно нужно. Смотреть фото что такое dependency injection и зачем оно нужно. Смотреть картинку что такое dependency injection и зачем оно нужно. Картинка про что такое dependency injection и зачем оно нужно. Фото что такое dependency injection и зачем оно нужно

Внедрение зависимостей — это самодостаточный подход, который можно использовать сам по себе. С другой стороны, этот подход можно применять и вместе с интерфейсами, и с контейнерами для внедрения зависимостей/инверсии управления (DI/IoC). Применяя внедрение зависимостей в таком контексте, можно столкнуться с некоторой путаницей, которую поначалу испытывал и я.

На протяжении всей карьеры (я специализируюсь на разработке в Net/C#), я привык использовать внедрение зависимостей в его чистейшей форме. При этом я реализовывал DI, вообще не прибегая ни к контейнерам, ни к инверсии управления. Все изменилось совсем недавно, когда мне поставили задачу, в которой без использования контейнеров было не обойтись. Тогда я крепко усомнился во всем, что знал ранее.

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

(Здесь важно отметить: интерфейсы и контейнеры используются только в контексте внедрения зависимостей. Внедрение зависимостей можно реализовать и без интерфейсов/контейнеров, но, в сущности, единственное назначение интерфейсов или контейнеров – облегчить внедрение зависимостей).

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

Подготовка

Чтобы лучше понять внедрение зависимостей в их чистейшей форме, давайте разберем пример приложения, написанного на C#.

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

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

Приложение

Рассмотрим следующий код. Он написан для простого приложения-калькулятора, принимающего два числа, оператор и выводящего результат. (Это простое рабочее приложение для командной строки, поэтому вам не составит труда воспроизвести его как C# Console Application в Visual Studio и вставить туда код, если вы хотите следить за развитием примера. Все должно работать без проблем.)

Логирование

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

Но, возможен вопрос: а в самом ли деле уместно, чтобы класс Calculator отвечал за запись в текстовый файл?

Класс FileLogger

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

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

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

Очевидно, ответ на последний вопрос отрицательный!

Именно здесь, уважаемый читатель, в дело вступает внедрение зависимости. Давайте изменим конструктор нашего класса Calculator :

Вот и все. Больше в классе ничего не меняется.

Внедрение зависимостей – это элемент более крупной темы под названием «Инверсия управления», но ее подробное рассмотрение выходит за рамки этой статьи.

Итак, чья же это ответственность?

Чтобы это продемонстрировать, изменим метод Main в нашем классе Program.cs следующим образом:

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

Расширение возможностей: сделаем другой логгер

Несмотря на вышесказанное, у интерфейсов есть свое место, и по-настоящему они раскрываются именно в связке с Внедрением Зависимостей.

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

Вот здесь нам и пригодятся интерфейсы.

Это единственное изменение, которое мы внесем в этот файл. Все остальное будет как прежде.
Итак, отношение мы определили – что нам теперь с ним делать?

Поскольку все, что бы вы ни получили, реализует интерфейс ILogger (и, следовательно, имеет метод WriteLine ), с практическим использованием проблем не возникает.

Нам потребуется изменить только лишь метод Main в нашем файле Program.cs, чтобы передать в него иную реализацию. Давайте этим и займемся, чтобы метод Main принял следующий вид:

Небольшая оговорка об интерфейсах

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

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

Контейнеры для внедрения зависимостей

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

Знакомьтесь с контейнером для внедрения зависимостей. Он упрощает вам жизнь, но принцип работы такого контейнера может показаться весьма запутанным, особенно, когда вы только начинаете его осваивать. На первый взгляд эта возможность может отдавать некоторой магией.
В данном примере мы воспользуемся контейнером от Unity, но на выбор есть и много других, назову лишь наиболее популярные: Castle Windsor, Ninject. С функциональной точки зрения эти контейнеры практически не отличаются. Разница может быть заметна на уровне синтаксиса и стиля, но, в конечном итоге, все сводится к вашим персональным предпочтениям и опыту разработки (а также к тому, что предписывается в вашей компании!).

Давайте подробно разберем пример с использованием Unity: я постараюсь объяснить, что здесь происходит.

Первым делом вам потребуется добавить ссылку на Unity. К счастью, для этого существует пакет Nuget, поэтому щелкните правой кнопкой мыши по вашему проекту в Visual Studio и выберите Manage Nuget Packages:

что такое dependency injection и зачем оно нужно. Смотреть фото что такое dependency injection и зачем оно нужно. Смотреть картинку что такое dependency injection и зачем оно нужно. Картинка про что такое dependency injection и зачем оно нужно. Фото что такое dependency injection и зачем оно нужно

Найдите и установите пакет Unity, ориентируйтесь на проект Unity Container:

что такое dependency injection и зачем оно нужно. Смотреть фото что такое dependency injection и зачем оно нужно. Смотреть картинку что такое dependency injection и зачем оно нужно. Картинка про что такое dependency injection и зачем оно нужно. Фото что такое dependency injection и зачем оно нужно

Итак, мы готовы. Измените метод Main файла Program.cs вот так:

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

что такое dependency injection и зачем оно нужно. Смотреть фото что такое dependency injection и зачем оно нужно. Смотреть картинку что такое dependency injection и зачем оно нужно. Картинка про что такое dependency injection и зачем оно нужно. Фото что такое dependency injection и зачем оно нужно

что такое dependency injection и зачем оно нужно. Смотреть фото что такое dependency injection и зачем оно нужно. Смотреть картинку что такое dependency injection и зачем оно нужно. Картинка про что такое dependency injection и зачем оно нужно. Фото что такое dependency injection и зачем оно нужно

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

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

Вот и все. Ничего таинственного и особо мистического.

Другие возможности

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

Источник

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

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

При использовании шаблона внедрения зависимостей рабочая служба имеет следующие характеристики:

Реализацию интерфейса IMessageWriter можно улучшить с помощью встроенного API ведения журнала:

Обновленный метод ConfigureServices регистрирует новую реализацию IMessageWriter :

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

В терминологии внедрения зависимостей — служба:

Несколько правил обнаружения конструктора

Если тип определяет более одного конструктора, поставщик служб включает логику для определения используемого конструктора. Выбирается тот конструктор, который имеет больше всего параметров, в которых типы могут разрешаться с внедрением зависимостей. Рассмотрим следующий пример службы на C#:

Если при определении конструктора возникает неоднозначность, выдается исключение. Рассмотрим следующий пример службы на C#:

Код ExampleService с неоднозначными параметрами типов, которые могут разрешаться с внедрением зависимостей, выдаст исключение. Не делайте этого, это всего лишь демонстрация того, что подразумевается под «неоднозначными типами, разрешаемыми с внедрением зависимостей».

Регистрация групп служб с помощью методов расширения

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

Платформенные службы

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

Тип службыВремя существования
Microsoft.Extensions.DependencyInjection.IServiceScopeFactoryОдноэлементный
IHostApplicationLifetimeОдноэлементный
Microsoft.Extensions.Logging.ILoggerОдноэлементный
Microsoft.Extensions.Logging.ILoggerFactoryОдноэлементный
Microsoft.Extensions.ObjectPool.ObjectPoolProviderОдноэлементный
Microsoft.Extensions.Options.IConfigureOptionsВременный
Microsoft.Extensions.Options.IOptionsОдноэлементный
System.Diagnostics.DiagnosticListenerОдноэлементный
System.Diagnostics.DiagnosticSourceОдноэлементный

Время существования служб

Службы можно зарегистрировать с одним из следующих вариантов времени существования:

Они описываются в следующих разделах. Для каждой зарегистрированной службы выбирайте подходящее время существования.

Временный

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

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

Область действия

Для веб-приложений время существования, привязанное к области, означает, что службы создаются один раз для каждого запроса (подключения) клиента. Регистрируйте службы с заданной областью с помощью AddScoped.

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

При использовании Entity Framework Core метод расширения AddDbContext по умолчанию регистрирует типы DbContext с заданной областью времени существования.

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

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

Одноэлементный

Одноэлементные службы времени существования создаются в следующих случаях.

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

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

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

Методы регистрации службы

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

МетодАвтоматически
object
удаление
Несколько
реализации
Передача аргументов
Add ()

services.AddSingleton ();

ДаДаНет
Add (sp => new )

services.AddSingleton (sp => new MyDep());
services.AddSingleton (sp => new MyDep(99));

ДаДаДа
Add ()

services.AddSingleton ();

ДаНетНет
AddSingleton (new )

services.AddSingleton (new MyDep());
services.AddSingleton (new MyDep(99));

НетДаДа
AddSingleton(new )

services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(99));

НетНетДа

Дополнительные сведения об удалении типа см. в разделе Удаление служб.

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

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

Параметр TryAddSingleton не применяется, так как он уже был добавлен, поэтому выполнение «try» завершится ошибкой. В ExampleService будут следующие утверждения:

Дополнительные сведения см. в разделе:

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

IServiceCollection является коллекцией объектов ServiceDescriptor. В следующем примере показано, как зарегистрировать службу, создав и добавив ServiceDescriptor :

Встроенные методы Add используют аналогичный подход. Например, см. исходный код для AddScoped.

Поведение внедрения через конструктор

Службы можно разрешать с помощью:

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

Проверка области

Когда приложение выполняется в среде Development и вызывает CreateDefaultBuilder для создания узла, поставщик службы по умолчанию проверяет следующее:

Корневой поставщик службы создается при вызове BuildServiceProvider. Время существования корневого поставщика службы соответствует времени существования приложения — поставщик запускается с приложением и удаляется, когда приложение завершает работу.

Сценарии применения области

Интерфейс IServiceScopeFactory всегда регистрируется как отдельный (singleton), но IServiceProvider зависит от времени существования содержащего класса. Например, если при разрешении служб из области какая-то из служб принимает интерфейс IServiceProvider, это будет экземпляр с заданной областью.

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

В приведенном выше коде во время выполнения приложения фоновая служба:

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *