Год назад я написал статью про DI в Spring/Java EE. Мой тезис звучал довольно категорично: "DI через конструкторы является единственно правильным. Все остальное –  от лукавого". Прошло время, я пообщался с разными разработчиками на эту тему, сменил проект, компанию, провел множество собеседований, отсмотрел большое количество строк на code-review и сейчас могу сказать, что не все так однозначно. Давайте наконец разберемся, как же все-таки инжектить правильно. Скажу сразу, что я по-прежнему считаю DI через конструкторы наиболее правильным с точки зрения поддерживаемости. Однако бывают ситуации, когда инжекты через поля оправдывают себя. Рассмотрим некоторые из них. Если вы работаете с базами данных в Spring, скорее всего, используете Spring Data. Это потрясающий инструмент, который упрощает взаимодействия с реляционными (и не только) СУБД на порядки. Тем не менее в сложных системах может потребоваться более низкоуровневое взаимодействие непосредственно с EntityManager. Как нам получить его инстанс? Что же, один из способов –  внедрение EntityManagerFactory. Здесь нам нужно беспокоиться о корректном открытии и закрытии EntityManager. Можно упростить себе жизнь. Аннотация @PersistenceContextвнедряет прокси, который выполняет открытие и закрытие EntityManager автоматически. Это вариант гораздо проще понимать и поддерживать. Spring – очень гибкий инструмент. Если вдруг обычного @Autowiredвам стало недостаточно, есть возможность дополнить DI механизм новыми модулями. Предположим, что нам нужна аннотация, которая будет внедрять случайное целое число. Чтобы реализовать функциональность, требуется написать bean post processor. Удобно, не правда ли? Более того, мы можем сделать несколько bean post proccessors, подчиняющихся разным профилям (dev, test, prod и так далее). Этот случай довольно простой. Но в докладе Евгения Борисова есть хороший пример, как кастомные инжекты могут помочь при работе с Apache Spark (таймкод 45:15). Я думаю, многие знают этот знаменитый вопрос с собеседований: "Если вызвать метод a, выполнится ли он транзакционно?" Краткий ответ – нет. Более подробный можно найти во множестве статей на том же Хабре. Но если все же требуется выполнить a транзакционно, но поставить @Transactional над методом мы по какой-то причине не можем, self-injection выручит нас. Так как заинжектен будет прокси, который реализует управление транзакциями, вызов myService.b() так же будет выполнен транзакционно. Хочу отметить, что это решение не является указанием к действиям. Self-inject – не есть хороший паттерн. Однако, если нам приходится поддерживать проект, который разрабатывался в течение многих лет, рефакторинг может быть чересчур дорогим. Поэтому в ряде случаев такой подход является наименьшим злом. Одним из доводов против использования DI через поля в моей предыдущей статье был пункт, который гласил, что на такие классы нельзя написать unit-тесты. Формально это так. Но никто не отменял тесты с поднятием Spring Context. Благо c приходом Spring Boot это стало совсем просто. Вот пример теста для вышенаписанного TestRepository. Как видите, никаких проблем с тестированием функциональности не возникает. Более того, я считаю, что стандартный подход с мокированием всего и вся зачастую не эффективен и довольно ограничен. Но эта тема для отдельной статьи. Как я упомянул в начале статьи, DI через конструктор для меня по-прежнему остается более приоритетным. И вот почему. И DI через поля, и DI через сеттеры не исключают шанса получить циклическую зависимость. Более того, когда это начнет мешать дальнейшей разработке, может быть слишком поздно что-то менять. При инжекте через конструкторы такая ситуация невозможна, ведь мы сразу получим ошибку в рантайме. При DI через конструктор соответствующие поля можно объявить как final. Это исключает возможные ошибки при неправильных переприсваиваниях. Если вы используете Lombok, то приятным бонусом будет то, что сгенерировать конструктор можно с помощью @RequiredArgsConstructor, так как, начиная со Spring 4.3, ставить @Autowiredнад конструктором не обязательно, если он единственный. Как я написал выше, всегда можно протестировать бин, подняв Spring Context. Однако в некоторых ситуациях это является излишним. Обычные unit-тесты с моками выполняются гораздо быстрее и не требуют никаких дополнительных конфигураций. DI через конструкторы и сеттеры оставляют пространство для такого решения. DI через поля – нет. На это все с инжектами. Пока. Если у вас есть вопросы или предложения, пожалуйста, оставляйте свои комментарии. Спасибо за чтение! Java Team Lead, спикер, лектор/автор в МТС Тета