Код-ревью Middle IoC и внедрение зависимостей обновлено

Код-ревью: сервис уведомлений

Вопрос

Найдите проблемы с внедрением зависимостей и устройством бина.

@Service
public class NotificationService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ReportService reportService;

    private EmailClient emailClient = new EmailClient("smtp.example.com", 25);

    private static NotificationService instance;

    @Autowired
    public void init() {
        instance = this;
    }

    public static NotificationService get() {
        return instance;
    }

    public void notifyAll(String text) {
        for (User u : userRepository.findAll()) {
            emailClient.send(u.getEmail(), text);
        }
        reportService.markNotified();
    }
}

Коротко

Главная мысль ревью: бин должен получать всё через конструктор, а внешние клиенты должны быть бинами со свойствами из конфигурации. Статический доступ к бину и захардкоженные адреса убивают тестируемость.

Что должен найти ревьюер

  1. Стильстрока 5
    Внедрение в поля: зависимости скрыты, поля не final, юнит-тест без Spring или рефлексии не написать. Лучше конструктор.
  2. Ошибкастрока 8
    Если ReportService тоже зависит от NotificationService, это цикл, который с Boot 2.6 роняет старт. Стоит проверить и развязать.
  3. Ошибкастрока 10
    EmailClient создаётся через new с захардкоженными хостом и портом: не управляется контейнером, не настраивается через свойства, не подменяется в тестах.
  4. Ошибкастрока 12
    Статическая ссылка на бин и метод get() это service locator в обход DI: ломает тесты и контексты (несколько контекстов перетирают instance).
  5. Производительностьстрока 24
    findAll() грузит всех пользователей в память. Для рассылки нужна постраничная выборка или стриминг, а саму рассылку лучше делать асинхронно.

Разбор

Главная мысль ревью: бин должен получать всё через конструктор, а внешние клиенты должны быть бинами со свойствами из конфигурации. Статический доступ к бину и захардкоженные адреса убивают тестируемость. Отдельно стоит проверить зависимость ReportService на цикл и заменить findAll постраничной обработкой.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.