Ответ
Внедрение через конструктор рекомендуется по нескольким ключевым причинам, связанным с надежностью, тестируемостью и принципами иммутабельности.
Основные преимущества:
- Неизменяемость (Immutability): Поля, инициализированные через конструктор, можно объявить как
final. Это гарантирует, что зависимость будет установлена один раз при создании объекта и не может быть изменена позже, что делает объект потокобезопасным. - Гарантированная инициализация: Объект не может быть создан в неполном или невалидном состоянии. Все обязательные зависимости должны быть предоставлены немедленно.
- Упрощение тестирования: Класс легко протестировать в изоляции (юнит-тест), просто передав моки зависимостей через конструктор. Не требуется Spring-контекст или рефлексия для установки полей.
- Избегание циклических зависимостей: Конструкторное внедрение делает циклические зависимости явными (приводит к
BeanCurrentlyInCreationExceptionна этапе подъема контекста), что побуждает к улучшению дизайна. - Код без аннотаций: С Spring 4.3+ для единственного конструктора аннотация
@Autowiredне требуется, что делает код чище.
Пример:
@Service
public class OrderService {
// final поле - зависимость неизменна после инициализации
private final PaymentProcessor paymentProcessor;
private final NotificationService notificationService;
// Явный контракт: для создания OrderService нужны обе зависимости
public OrderService(PaymentProcessor pp, NotificationService ns) {
this.paymentProcessor = pp;
this.notificationService = ns;
}
public void processOrder(Order order) {
paymentProcessor.charge(order);
notificationService.sendConfirmation(order);
}
}
// Легкое тестирование
class OrderServiceTest {
@Test
void testProcessOrder() {
// Создаем моки
PaymentProcessor mockPP = mock(PaymentProcessor.class);
NotificationService mockNS = mock(NotificationService.class);
// Внедряем через конструктор
OrderService service = new OrderService(mockPP, mockNS);
// ... выполнение теста
}
}