Почему инъекцию зависимостей в Spring через конструктор (а не через @Autowired над полем) считают лучшей практикой?

«Почему инъекцию зависимостей в Spring через конструктор (а не через @Autowired над полем) считают лучшей практикой?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Использование инъекции через конструктор предпочтительнее полевой инъекции (@Autowired над полем) по следующим причинам:

  1. Неизменяемость (Immutability): Поля зависимостей можно объявить как final. Это гарантирует их обязательную инициализацию при создании бина и защищает от случайного изменения после инъекции.
  2. Явное объявление зависимостей: Все зависимости видны в конструкторе, что делает контракт класса понятным и упрощает обнаружение проблем с циклическими зависимостями на этапе компиляции.
  3. Упрощение тестирования: Класс можно легко протестировать без Spring Context, просто создав экземпляр через new и передав моки зависимостей в конструктор.
  4. Отказоустойчивость: Приложение не запустится, если какая-либо зависимость, требуемая в конструкторе, не найдена в контексте. Полевая инъекция может привести к NullPointerException во время выполнения.

Пример лучшей практики:

@Service
public class OrderService {
    // Поле final - зависимость обязательна и неизменна
    private final OrderRepository orderRepository;
    private final NotificationService notificationService;

    // Один конструктор: @Autowired опционален в Spring 4.3+
    public OrderService(OrderRepository orderRepository, 
                        NotificationService notificationService) {
        this.orderRepository = orderRepository;
        this.notificationService = notificationService;
    }

    public void placeOrder(Order order) {
        orderRepository.save(order);
        notificationService.sendConfirmation(order);
    }
}

Для теста не нужен Spring:

@Test
void placeOrder_ShouldSaveAndNotify() {
    // Arrange
    OrderRepository repoMock = mock(OrderRepository.class);
    NotificationService notifMock = mock(NotificationService.class);
    OrderService service = new OrderService(repoMock, notifMock); // Просто new
    Order testOrder = new Order();

    // Act & Assert
    service.placeOrder(testOrder);
    verify(repoMock).save(testOrder);
    verify(notifMock).sendConfirmation(testOrder);
}

Когда полевая инъекция допустима? В основном в конфигурационных классах или простых компонентах без логики (например, @Controller в Spring MVC), но даже там конструктор предпочтительнее.