Pull to refresh

Comments 12

У Autowired есть отличная возможность под капотом. Он может быть неинициализированным.

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

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

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

Я бы чуть иначе сказал. Вот скажем, автор пишет:

Если вы работаете с базами данных в Spring, скорее всего, используете Spring Data.


Это не глупость — это излишнее обобщение (я вот не использую, а проектов у меня вероятно было сильно больше чем у автора). А все почему? Потому что от проекта зависит. Рано или поздно такие выводы окажутся неверными, когда попадется проект другого типа.

Поэтому — лучше не верьте выводам. Думайте своей головой. Запоминайте достоинства и недостатки решений — а выводы делайте сами для своего проекта и своих условий.

Забавно, но я считаю, наоборот, самый приостым и универсальным решением инъекцию через поля. Конструктор всегда должне быть пустой. Плюсы следующие:
1. Наследование. В конструкторе унаследованного класса не надо перечислять поля родителя.
2. Возможность иметь циклические зависимости. Кто-то скажет, что это антипаттерн. Может быть. Но лучше иметь такую возможность.
3. Когда зависимостей много, конструктор со всеми зависимостями становится совершенно нечитаемым. Если разнести по полям, читабельность улучшается.
4. Возможность задать значения по-умолчанию с аннотацией @Autowired(optional=true)
5. Лучше безобразно но единообразно. Всегда пустой конструктор и инъекция через поля - не надо думать, какие поля вынести в конструктор (всегда никакие)

И, в довесок, поля должны быть package private. Это во-первых, меньше букв. Во-втрорых, при написании тестов такие поля очень просто инициализировать.

И как тогда гарантировать создание экземпляра такого класса?

Создание экземпляра - легко, а вот целостность - не очень

Когда зависимостей много, конструктор со всеми зависимостями становится совершенно нечитаемым

Может это сигнал, что что то пошло не так? Либо класс слишком много делает, либо зависимости слишком сильно разделены

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

А ещё можно получить объект невалидный при желании

В конструкторе унаследованного класса не надо перечислять поля родителя.

А почему это хорошо? Лучше явно перечислять, чтоб, опять же, увидеть проблему, если зависимостей слишком много

 Но лучше иметь такую возможность

Зачем? Лучше не создавать условий для плохого/запутанного кода

А в чём проблема для тестов передавать моки бинов через DI-контейнер?

Очень легко попасть в ситуацию когда тест тестирует только моки.

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

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

А если используются моки то по той же логике это тестирование интеграции с моками - только они сами не протестированы другими тестами и часто реализуют интерфейсы не полностью

Все потому, что Spring -- это не столько DI, сколько набор @рецептов и @заклинаний, построенных поверх DI. Если вам нужен только DI, возьмите Guice -- наиболее органичная реализация паттерна. И да, в Guice прямым текстом говорится, что наиболее приоритетный случае -- это инъекция через конструкторы. По двум причинам:

  • Объект должен быть гарантированно инициалицирован со всеми своими зависимостями.

  • Объект не должен иметь зависимость от DI фреймворка.

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

Лично я всегда использую инициализации через конструктор, а для сокращения писанины использую Lombok с аннотациями:

@FieldDefaults(makeFinal = true)
@RequiredArgsConstructor(onConstructor = @__({@Inject}))

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

Sign up to leave a comment.

Articles