В нынешней enterprise-разработке, где слово "Java" стало чуть ли не синонимом слову "Spring", многие даже не задумываются, что волшебная аннотация
@Autowired в Spring'е, которая обеспечивает автомагическое появление одних бинов в других, — это лишь одна из имплементаций паттерна Dependency Injection (внедрение зависимостей), и уж тем более не допускают мысли, что есть и другие имплементации. Ибо зачем?.. 🤔В одной из недавних задач мне нужно было выделить из нашей платформы специфичный для одного заказчика функционал в отдельный плагин. Но если с выбором фреймворка для самого плагина всё было понятно (у нас используется PF4J и вполне устраивает), то для управления зависимостями внутри плагина нужно было что-то подыскать, так как решение из платформы в плагине работало плохо 👎
Конечно, можно было взять модуль
spring-core, ведь в него входит Spring IoC Container, который как раз этим всем и занимается. Но это показалось мне избыточным, так как кроме DI туда входит и немало других, совершенно не нужных мне фич:It adds:
• Easier integration with Spring’s AOP features
• Message resource handling (for use in internationalization)
• Event publication
• Application-layer specific contexts such as the WebApplicationContext for use in web applications.
Кроме того, поскольку плагин будет поддерживаться партнёром, для которого Java — отнюдь не основной стек, очень желательно, чтобы в решении было как можно меньше "магии", а если что-то пойдёт не так, то ошибки были максимально понятными 💎
Вот поэтому я вспомнил про Guice — фреймворк внедрения зависимостей, разработанный в Google почти одновременно со Spring (в 2006) и до сих пор используемый у них в production. В отличие от всемогущего Spring'а, целеустремлённый Guice решает только одну задачу, но делает это с очень сильным акцентом на ясность и предсказуемость 🎯
Это проявляется, в основном, в том, что в Guice нет никакого classpath scanning, т.е. конфигурации зависимостей должны быть прописаны явно. Для удобства предусмотрено исключение в виде т.н. Just-In-Time Bindings (привязок, создаваемых на лету), но их логика максимально проста, а для особо строгих случаев их можно вообще отключить. В остальном фреймворк, по сути, просто заставляет разработчика отделять
Еще из интересных отличий Guice от Spring:
— объекты по умолчанию в IoC контейнере не создаются синглтонами, а инстанциируются на каждое обращение;
— описывать конфигурацию зависимостей можно не только аннотациями, но и на специальном DSL;
— ошибки разрешения зависимостей представляют из себя целые отчёты, зачастую с советами по исправлению 🔖
Отдельными плюсами для меня стали простая возможность внедрять зависимости в уже существующие объекты (создаваемые PF4J), а также возможность заинжектить инжектор (🤪) — это позволяет в особо упоротых местах перейти от dependency injection к service locator. Такое можно вытворять и в Spring, но по-хорошему лучше избегать с любым фреймворком 🫠
Мой прошлый опыт работы с Guice, где его внедрили до меня, трудно считать на 100% успешным, но ещё тогда мне показалось, что если этот фреймворк применять как задумано его создателями и в более полном объёме, то он может раскрыться во всей красе и позволить работать с ним "благодаря", а не "вопреки". Кажется, моя недавняя задача подтвердила эту гипотезу ✅
К слову, у Google есть и другой DI-фреймворк — Dagger, обретший популярность в Android разработке. Его основная фишка в том, что он работает в compile-time. Такой подход открывает массу полезных возможностей и ограничений, но это уже совсем другая история... 📖