В чем преимущества инъекции через конструктор над @Autowired
Что проверяют
Понимаете ли вы, что сравнение на самом деле идёт между внедрением через конструктор и через поле: @Autowired можно поставить и на конструктор. Следом обычно спрашивают про циклические зависимости и поведение Spring при их появлении.
Как отвечать
Сначала уточните термины, потом перечислите преимущества:
- обязательность видна в сигнатуре: все зависимости перечислены в конструкторе, объект нельзя создать без них. С полем зависимость спрятана, и объект может жить с
null; - неизменяемость: поля объявляются
final, их нельзя переприсвоить, а модель памяти Java гарантирует их видимость другим потокам после конструктора; - тесты без контекста:
new OrderService(repoMock, clockMock)в обычном юнит-тесте. Поле с@Autowiredбез Spring или@InjectMocksостанетсяnull, и тест упадёт с NPE; - сигнал о дизайне: конструктор на восемь параметров сразу показывает, что класс делает слишком много. С полями это незаметно;
- циклы видны сразу: цикл через конструкторы не даст контексту подняться, будет
BeanCurrentlyInCreationException. С Spring Boot 2.6 циклические ссылки запрещены по умолчанию и для полей, флагspring.main.allow-circular-referencesих разрешает, но это маскировка проблемы.
Практика: при единственном конструкторе аннотация не нужна, Spring с версии 4.3 внедряет через него сам. С Lombok пишут @RequiredArgsConstructor над классом с final-полями. Опциональные зависимости передают через ObjectProvider<T> или сеттер, а внедрение в поле оставляют тестовым классам, где конструкторы никто не пишет.
Где спотыкаются
Говорят, что без @Autowired конструктор не сработает. Для одного конструктора аннотация лишняя уже много лет.
Считают, что конструктор «решает» циклические зависимости. Он делает их ошибкой на старте, а лечится это пересмотром связей между бинами, а не @Lazy.
Путают проверку компилятором и проверку контейнером. Если нужного бина нет, приложение упадёт при старте контекста с UnsatisfiedDependencyException, компилятор здесь ничего не заметит.