Как стать автором
Обновить

Комментарии 42

Пардон, проблема-то вроде понятна, только при чем тут спринг? Если у вас есть в классе обязательная зависимость, ее можно задать через конструктор, или через сеттер. Понятно что через сеттер ее можно и не задать. Это точно так же актуально, если у вас вообще спринга в проекте нет.

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


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

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

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

Могу сказать, что все это из личного опыта. Я видел код проектов, где до сих пор используется di через поля, даже не через сеттеры. Они даже крутятся в проде.

Это понятно, куча легаси и все такое, но тема не нова, видишь в коде — перепиши.
С ломбоком решается private final и RequareArgsConstructor. Хотя и ломбок многие считают злом

Во-первых, на такой класс невозможно написать хороший unit-тест.

У вас бин создается вручную в тесте. Зачем? Чтобы нормально протестировать это дело, надо добавить к тестовому контексту конфиг с данным бином ( в данном случае конфиг, который содержит @Bean Parent parent(){...} ). Если там же рядом будет лежать и @Bean Child child, то в тест вы спокойно через @Autowire инжектите бины и тестируете их спокойно. Если вдруг в тесте вам нужна другая реализация Child ( например, замоканное значение), то вы с таким же успехом просто переопределяете бин в тестовом конфиге и получаете профит.

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

Как отказ от DI или вынос задания зависимостей в конструктор влияет на тип тестирования?

Если у вас зависимости инжектятся через поля, вы автоматически лишаете себя возможности написать unit-тест на данный компонент. Так как в таком случае просто нет способа, чтобы передать значение в объект без рефлексии.


Вы все ещё можете писать интеграционные тесты, поднимая Spring context. Но интеграционные должны расширять существующую тестовую базу, а не заменять собой модульные. К тому же, исходя из моего опыта, интеграционные тесты писать дольше и сложнее. И вряд ли кто-то будет обеспечивать покрытие в 80 процентов, если unit-тестов нет. Скорее всего, протестируют самый важный функционал, а на остальное закроют глаза.


Возможно, что у вас не так. Но это именно то, что я видел в жизни.

Ваш пример с инъекцией через Reflection — это тоже интеграционный тест.

Пример с инъекцией через Reflection в тесте – это то, как делать не надо :)

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

То есть, поля не инкапсулированы, а сеттеры инкапсулированы? Да что вы говорите? Что мне мешает вызвать сеттер вместо присваивания?

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

Про тестирование вообще улыбнуло — автор явно не знает как тестируются спринг бины.

«Spring использует Reflection API» — ну это вообще-то не всегда.
То есть, поля не инкапсулированы, а сеттеры инкапсулированы? Да что вы говорите? Что мне мешает вызвать сеттер вместо присваивания? На самом деле поля можно (и наверно нужно) сделать приватными и они будут скрыты.

В приведенных примерах все поля private, так что я не понимаю, о каком присваивании без сеттеров вы говорите.


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

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


Да и проблема не только в тестировании. Как я уже писал в статье, в этом случае вы жестко привязаны к аннотации @Component/@Service. Что, если потребуется инициализация через BeanFactory? Как передавать зависимости? Или вдруг класс вообще отвяжется от Spring, станет обычным типом. Все равно придется добавлять сеттеры/конструкторы. Так зачем полагаться на магию, если, просто используя стандартные подходы в Java, код становится чище и прозрачнее?
Опять же, DI через поля и сеттеры открывает дорогу к циклическим зависимостям.


«Spring использует Reflection API» — ну это вообще-то не всегда.

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

В приведенных примерах все поля private, так что я не понимаю, о каком присваивании без сеттеров вы говорите.


Вы писали, про то что поля, нарушают принципы инкапсуляции. Так вот: сеттеры точно также нарушают принципы инкапсуляции, но при этом ещё превращают код в месиво.

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


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

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

Насколько я знаю, для DI Spring всегда использует Reflection API.

Не обязательно.

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

И это тоже необязательно
В исходном примере есть циклическая зависимость между child и parent.
В последнем варианте — через конструктор — такой зависимости нет. Если её вернуть обратно, то невозможно будет создать бины.

Именно для того, чтобы решить эту проблему и было разделено создание бина через конструктор по умолчанию, injections, и инициализация через @PostConstruct.

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

Согласен, но это не имеет никакого отношения к заголовку статьи.

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

Spring DI был создан как альтернатива Service Locator Pattern. Service Locator Pattern, в свою очередь, решал проблему модульной разработки, когда сервис и его клиенты разрабатываются разными проектами/командами/компаниями, в разное время. В такой ситуации нет практического способа «не допустить».
Можно и по другому сказать: спринг контекст выступает в роли сервис локатора
// циклическая зависимость является проблемой самой по себ

ну хоть кто-то это сказал )

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

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

Странный пример у вас. Проблема с циклической зависимостью "решилась" убиранием поля Parent из Child. При этом ничего больше не поменялось в коде. Получается, это поле там было и не нужно изначально?


В жизни всё намного сложнее, к сожалению. Пример: сервису обработки RPC через MQ, обеспечивающий вызов удаленных процедур между микросервисами, требуется зависимость — сервис проверки ролей (RBAC) чтобы убедиться что пользователь, от имени которого прилетела команда RPC, имеет право запускать эту логику. При этом сервис RBAC также зависит от сервиса MQ, поэтому что если роли пользователя изменились, то нужно отправить broadcast сообщение всем репликам чтобы они сбросили локальные кеши ролей для этого юзера. Конечно, зависимость не означает что оба класса имеют поля-ссылки друг на друга, но зависимость есть и эта проблема должна как-то решаться. И решения есть.


И да, я использую Autowired через приватные поля для всех проектов, в том числе новых на спринг буте 2.3, и считаю это самым удобным и элегантным способом, который обеспечивает чистоту кода. Для тестов с поднятием контекста и простых Mockito используются @MockBean, либо для юнит Setter с пакетным уровнем доступа, а в редких экзотических случаях стандартный спринговый ReflectionTestUtils, чтобы не городить огород как сделано в вашем примере.

В жизни всё намного сложнее, к сожалению. Пример: сервису обработки RPC через MQ, обеспечивающий вызов удаленных процедур между микросервисами, требуется зависимость — сервис проверки ролей (RBAC) чтобы убедиться что пользователь, от имени которого прилетела команда RPC, имеет право запускать эту логику. При этом сервис RBAC также зависит от сервиса MQ, поэтому что если роли пользователя изменились, то нужно отправить broadcast сообщение всем репликам чтобы они сбросили локальные кеши ролей для этого юзера.

Вы просто сами себе проблему придумали, а затем преодолели. Должно быть так


  • Low level RPC — без всяких проверок
  • RBAC компонент, зависит от Low level RPC
  • RPC с проверками ролей, которым большая часть сервисов пользуется.
Спасибо вам за совет, но мне на самом деле не пришлось предолевать никакие проблемы. Я просто привёл здесь сильно упрощенный пример взаимной зависимости компонентов, чтобы показать, что в жизни такое случатся сплошь и рядом, и это совершенно не означает, что дизайн приложения дерьмовый. Это просто требования бизнеса. Функциональную зависимость компонентов совсем не обязательно решать при помощи композиции.

Раз уж вы дали совет, опираясь на домыслы, то я раскрою немного деталей реализации. RBAC Service и MQRPC Service (назовём их так) не связаны напрямую. Они вообще друг о друге ничего не знают. Все ядровые сервисы вроде них имплементированы в shared library и внедряются во все микрсервисы и настраиваются при помощи «магии» автоконфигураций Spring Boot. Проверка ролей выполняется при помощи Java аннотаций, в том числе и в MQRPC Svc, потому что та же библиотека имплементирует кастомный SecurityExpressionRoot и т.д. А упомянутое мной broadcas оповещение отсылается как реакция на внутреннее событие, тоже annotation-driven (EventListener + ApplicationEventPublisher), а не путём прямого вызова метода. Как видите, никаких зависимостей в коде нет вообще. За это я и люблю Spring Boot. Я делаю изменение в общей библиотеке, привношу что-то совершенно новое или меняю существующее, мерджу коммит, и CI pipeline мне пересобирает докер образы и деплоит все сервисы с уже новой функциональностью, хотя я их даже не касался.

А разве при большом количестве юнит-тестов без контекста подход с инъекцией в приватные поля не превращается в тыкву? Приходится усложнять код на пустом месте и добавлять лишние сеттеры специально для тестов, или же использовать ReflectionTestUtils, что весьма сомнительное удовольствие с точки зрения дальнейшей поддержки кода.


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

Нет, не превращается. Вы ведь используете Mockito чтоб обеспечить изоляцию тестов? Вам в любом случае нужно как-то создать все эти mock-объекты и внедрить в ваш класс. И Mockito вам в этом помогает. Он делает DI точно так же как и Spring при поднятии контекста: по типам (интерфейсам) проаннотированных полей, а не по именам.


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

Прошу прощения, у какого Роберта Мартина вы прочитали такой вывод, что чистый код = меньше кода?) Тот факт, что мы создаем какие-то абстракции, интерфейсы, фабрики, разделяем приложение на разные слои, априори увеличивает количество кода.


По такой логике любой код на каком-нибудь Python автоматически становится более чистым, чем на Java.


Кстати, вы можете использовать lombok и @RequiredArgsConstructor вместо @Autowired над каждым полем. Тогда кода станет еще меньше, так как все @Autowired можно будет убрать)

В свое время я посмотрел много выступлений дяди Боба, раз уж вы его вспомнили. И первый из его принципов всегда был такой: "Smaller code is much easier to understand". Он рекомендует следующее:


Long methods mix high-level abstractions with unimportant details. They overwhelm you.
How do you fix it? The first rule: functions should be small.
How small should they be? 1? 5? 10 lines? Fit into the screen? There is a better rule: a function should do one thing.
What is “one thing”? A function does one thing if you cannot meaningfully extract another function from it.
You should extract methods until you can’t extract anymore.


Кроме того, есть принцип KISS. Keep it simple.


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

Вы все говорите правильно. Только давайте разберемся со "Smaller code is much easier to understand" и KISS. В моем понимании это означает, что класс/метод должны быть небольшими и делать одну конкретную работу. С этим я совершенно согласен. Но я не согласен с тем, что наличие конструктора делает мой код "громоздким".


Первое правило чистого кода — чем его меньше, тем лучше. Я не хочу городить конструктор и вызывать его вручную в тестах. Я предпочитаю стройный ряд объявлений зависимостей в самом начале моего класса.

Это неотъемлемая часть языка. Если они вас так не устраивают, можете использовать lombok или перейти на Kotlin. Там объявление главного конструктора объединено с объявлением полей. А если мне надо объявить дто? Тоже нельзя добавлять конструктор? Ведь это увеличит количество кода.


Хорошо, допустим я не использую конструктор в бинах Spring и полностью полагаюсь на DI через поля. Сейчас даже забудем про юнит-тесты и невозможность реализации BeanFactory. Пусть у меня появился другой класс. Не бин, а обычный Java-класс. Ему мне придется добавлять конструкторы/сеттеры, ведь Spring про него ничего не знает. То есть, получается, что у меня есть обычные классы и есть какие-то особенные классы, у которых заметно отличается декларация и вообще принцип работы с ними. Я не думаю, что это можно классифицировать как KISS. Как short, возможно, но не как simple.


В моем понимании KISS означает, что весь мой код должен быть написан в едином стиле.


Кстати, раз уж вы упомянули, что конструкторы – это дополнительный код. На мой взгляд, наличие большого конструктора – это сигнал того, что с вашим классом что-то не так. А если вы инжектите все через поля, не пишите юнит тестов, в которых необходимо создавать инстанс через new, то может возникнуть ощущение, что с классом все в порядке. В конце концов, можно в него добавить хоть 20 зависимостей. Это займет всего лишь 20 строчек кода, не так уж много. Справедливости ради, такое же может произойти и при использовании @RequiredArgsConstructor/@AllArgsConstructor

Lombok это моя любовь, но даже с его волшебными аннотациями мне хочется большего. Потому я создаю свои аннотации, использую AOP, автоконфигурации и прочую черную магию. Я хочу дакларативного программирования. Очень сложный ядровой код, который спрятан внутри библиотек и инфраструктурных хранимых процедур БД, который правят синьёры-помидоры, и очень простой бизнес код, который с лёгкостью правят новички, следуя простым правилам. Желающие разобраться как всё работает тоже со временем становятся синьёрами.


Вы, конечно же, правы. Я и не спорю с вами, на самом деле. Мне всего лишь не понравились категоричные заявления в стиле Чеховского соседа "этого не может быть, потому что этого не может быть никогда", и я решил высказать альтернативную точку зрения, хотя я знал заранее что в меня полетят помидоры. Мне просто было интересно побеседовать на эту тему.


Я знаю современные официальные рекоменадции, и всю аргументацию, за ними стоящую, и я регулярно смотрю выступления Juergen Hoeller, слушаю Bootiful Podcast by Josh Long и прочих мейнтейнеров этого замечательного фреймворка. Но всё же предпочитаю работать так, как я описал, и этот подход полностью поддерживается и оправдывает себя с годами. И тот же Джош Лонг в своих видео не парится и делает ровно то же самое. На все ваши аргументы я могу привести контраргументы и наоборот.


Мне, кажется, особо нечего добавить к тому, что я написал вам и другим участникам дискусии. Спасибо разработчикам Spring и экосистемы вокруг него за то, что мы, такие разные, можем использовать разные подходы. И спасибо вам за интересную беседу :-)

Если чтение boilerplate кода в классе вызывает сложности, скорее всего стоит подумать над рефакторингом. Но даже если оставить этот момент за кадром — с точки размера класса, как тут уже упоминали, аннотация @AllArgsConstructor сведет весь код конструктора к одной строке и позволит избавиться лишних спринговых Autowired в коде. Тесты при этом можно создавать через @InjectMocks как и раньше, плюс появится опциональная возможность вызывать конструктор явно и проверять все пропущенные моки во время компиляции, вместо того чтобы получать NPE во время выполнения.

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


Для обычных классов (не бинов) я использую конструкторы. Для инфраструктурных бинов, которые создаются автоконгурациями Spring Boot я тоже делаю конструктор. Это совсем другое дело. Таких бинов мало и они очень сложные.


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


Вас не беспокоит что у вас нет конструктора для JpaRepository? Ну вот точно также мне пофигу и на конструкторы для прочих "рядовых" бинов. Мне так нравится, и не вызывает никаких неудобств.

Спасибо за объяснение. Я более чистым вариантом считаю максимальное разделение абстракций фреймворка и кода, но это отдельная дискуссия и если исходить из противоположной точки зрения — возможно Ваш подход более правильный. В моей практике был один случай, когда заказчику неявные абстракции Spring показались слишком неявными, но там проблема была во всем механизме ComponentScan и в итоге от нас потребовали все классы создавать через Java Config (хотя опять же через конструкторы).


По поводу JpaRepository — я всегда его воспринимал скорее как конфигурацию, тем более там внутри и кода-то обычно нет.

И ниже я также привёл примеры невозможных внедрений зависимости через конструктор: @PersistenceContext и self-injection.

Вызов метода в конструкторе (да и вообще любой код, кроме присваивания полей) – тоже антипаттерн (смотри, например, Мишко Хевери), потому что это нарушает тестируемость кода. Вы не сможете вызвать конструктор, и не вызвать метод init. Уж лучше в таком случае оставить @PostConstruct. А еще лучше сделать фабрику, которая в нужном порядке проинициализирует нужные вам объекты, и в ней не будет никакой магии вроде @PostConstruct, и уже эту фабрику вызвать в @Configuration-файле.

отказываемся от метода в конструкторе — получаем дополнительный бойлеплейт при использовании объекта (когда отдельно надо вызвать конструктора, а затем его проинициализировать).

Вообще в Философии Java одного известного автора четко описано, почему возникла потребность в конструкторах — потому что огромное количество багов происходило (и происходит) при инициализации, то есть подготовке объекта к работе.

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

Антипаттерном эти методы становятся только в том случае, если вы их делаете публичными.

Я кстати не в курсе, Мишко Хевери в списке Java-гуру сейчас котируется выше автора «Философии Java»? Если да, то конечно, надо его слушать.

На самом деле нет — надо решать самому. У каждого инженерного выбора есть плюсы и минусы. Как писал Мартин Фоулер, невозможно создать абсолютно чистую архитектуру, она всегда будет грязной, и в каком-то месте грязь будет чудовищной. Но инженер может и должен минимизировать её расползание по системе )

Спасибо за статью! При прочтении пару раз возникал вопрос о том, зачем еще раз вспоминать про field injection, если даже официальная документация говорит, что "DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection" и про поля упоминает по большей части при тестировании, но после прочтения комментов вопрос отпал сам собой :)

Я думал, что больше всего споров возникнет по поводу сеттеров. Как оказалось, field di до сих пор применяют. Хотя даже idea (новая версия, по крайней) предупреждает, что Autowired над полями использовать не рекомендуется

Кстати, к вашему сведению, Spring использует reflection для всех трёх типов DI (конструктор, сеттер, поле). Можете убедиться сами в режиме отладки, если не верите :) Или просто найдите BeanPostProcessor ответственный за обработку аннотации Autowired и почитайте код. Других способов узнать что именно вы хотели просто не существует. Так что это скорее дело вкуса и style guide принятого на проекте или в организации. И многие известные тренеры и спикеры, типа того же Жени Борисова, разделяют эту точку зрения. Часть Spring Boot, отвечающая за поддержку тестирования, также полностью исключает любые проблемы типа необходимости внедрять моки вручную, по стрковым именам полей.

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


Здесь вопрос скорее концептуальный. Насколько сильно мы хотим связывать наш объект с экосистемой Spring. Если взять все тот же доклад «Spring-потрошитель», то там Евгений использует аннотацию @InjectRandomNumber. Кастомный BeanPostProcessor находит ее и внедряет в поле случайное число. Мне лично такой подход не очень нравится, так как здесь получается слишком сильный coupling со Spring. Я бы вынес генерацию случайного числа в отдельный сервис и инжектил бы при необходимости. Также это сильно затрудняет понимания кода, особенно для новых членов команды. К тому же, здесь опять же упираемся в проблему юнит-тестирования.


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


Да и проблема не только в тестировании. Как я уже писал в статье, в этом случае вы жестко привязаны к аннотации Component/@Service. Что, если потребуется инициализация через BeanFactory? Как передавать зависимости? Или вдруг класс вообще отвяжется от Spring, станет обычным типом. Все равно придется добавлять сеттеры/конструкторы. Так зачем полагаться на магию, если, просто используя стандартные подходы в Java, код становится чище и прозрачнее?

Да, уважаемый коллега, я хочу связать свой класс с экосистемой Spring по максимуму, чтобы использовать все возможности этой экосистемы на полную катушку. И на самом деле код класса будет различаться для разных фреймворков. Коней на переправе не меняют, не так ли? Вы можете представить себе смену фреймворка для живого проекта? Это означает фактически переписать всё с нуля, если это не простейший микросервис. А если вы имеете в виду просто выдрать класс из проекта и использовать его в vanilla Java проекте — ну так добавить конструктор впоследствии ничего не стоит, IDE вам сгенерит его автоматически.


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


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


Вы спрашиваете: "Что, если потребуется инициализация через BeanFactory? Как передавать зависимости?"
Очевидно, фабрика для синглтонов не требуется, или же этот одноразовый код будет размещен в @Configuration. Речь идёт о прототипах, которые выполняют какую-то короткоживущую бизнес-логику, скорее всего однопоточную. И им, как правило, для выполнения этой работы не требуется много зависимостей или сложная настройка. Прототипы просты. Поэтому мне всегда хватало стандартной саринговой фабрики (ObjectFactory, Provider, Supplier и т.п.). Например так:


@Autowired
private ObjectFactory<MyPrototype> myPrototypeFactory;

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


А вот скажите лучше, как вы собираетесь с помощью конструктора передавать в синглтон @PersistenceContext? Это required dependency, но объект будет каждый раз новый для нового потока, запускающего метод, т.к. он представляет собой обёртку вокруг коннекта к БД. Его нельзя передать извне, только создавать внутри ручками через фабрику. Мне больше нравится декларативный стиль. Аннотации вполне достаточно.


Или вот ещё пример. Очень часто нужно вызывать транзакционные методы из других методов того же класса. Без self-injection тут не обойтись. Это тоже required dependency, то есть по логике нужно её передавать в конструкторе. Но на этапе конструктора у вас ещё не создан proxy с логикой обработки транзакций.

Если вы используете Mockito, то @InjectMocks проставит вам моки прямо в поля, что (почти) решает проблему с созданием класса в юнит-тестах.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Публикации

Истории