0Pricing
Java Academy · Урок

Внедрение через конструктор и циклические зависимости

Отдавайте предпочтение внедрению через конструктор для удобства тестирования, обнаруживайте циклические зависимости и устраняйте их с помощью @Lazy.

«Внедрение через конструктор и циклические зависимости» — бесплатный урок Java Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Три типа внедрения зависимостей

Spring поддерживает внедрение через конструктор (рекомендуется), внедрение в поле (@Autowired над полем) и внедрение через сеттер. Внедрение через конструктор предпочтительно благодаря удобству тестирования и неизменяемости.

Внедрение через конструктор

Объявляйте зависимости как параметры конструктора. Spring внедряет их автоматически. Если есть только один конструктор, @Autowired необязательна (Spring Boot 2.1+).

@Service
public class OrderService {
    private final OrderRepository repo;
    private final EmailService email;
    // @Autowired is optional when there is only one constructor
    public OrderService(OrderRepository repo, EmailService email) {
        this.repo  = repo;
        this.email = email;
    }
}

Почему предпочтительно внедрение через конструктор

Внедрение через конструктор: (1) зависимости неизменяемы, (2) класс всегда полностью инициализирован, (3) в модульных тестах нет зависимости от Spring — просто вызовите new OrderService(mockRepo, mockEmail).

// Unit test — no Spring context needed:
OrderRepository mockRepo  = mock(OrderRepository.class);
EmailService    mockEmail = mock(EmailService.class);
OrderService    service   = new OrderService(mockRepo, mockEmail);
// Test service logic directly

Внедрение в поле (не рекомендуется)

Внедрение в поле с помощью @Autowired кратко записывается, но имеет проблемы: поля нельзя сделать final, после создания компонент не полностью инициализирован, а для модульного тестирования нужны Spring или вспомогательные средства рефлексии Mockito.

// Avoid in production code:
@Service
public class UserService {
    @Autowired  // hidden dependency
    private UserRepository repo; // cannot be final
}

Внедрение через сеттер для необязательных зависимостей

Используйте внедрение через сеттер для необязательных зависимостей, у которых есть разумное значение по умолчанию, — пометьте сеттер аннотацией @Autowired(required = false).

@Service
public class NotificationService {
    private SmsSender smsSender; // optional
    @Autowired(required = false)
    public void setSmsSender(SmsSender s) { this.smsSender = s; }
}

Что такое циклическая зависимость

Циклическая зависимость возникает, когда A требует B, а B требует A (непосредственно или через цепочку зависимостей). Spring обнаруживает это при запуске и выбрасывает BeanCurrentlyInCreationException.

// CIRCULAR:
@Service class A { A(B b) {} }
@Service class B { B(A a) {} }
// Spring throws: BeanCurrentlyInCreationException: Is there an unresolvable circular reference?

Разрыв циклической зависимости: рефакторинг с выделением третьего сервиса

Самое чистое решение — вынести общую логику в третий сервис, от которого ни A, ни B не зависят циклически.

// Before: A <-> B
// After: A -> C, B -> C (no cycle)
@Service class SharedLogic { ... }
@Service class A { A(SharedLogic s) {} }
@Service class B { B(SharedLogic s) {} }

Разрыв циклической зависимости: @Lazy

Добавьте @Lazy в одну из точек инъекции. Spring немедленно создаст прокси для B, а настоящий B будет создан только при первом вызове A его метода.

@Service
public class A {
    private final B b;
    public A(@Lazy B b) { this.b = b; } // breaks the cycle
}

Разрыв циклической зависимости: получение через ApplicationContext

Внедрите ApplicationContext в один компонент и получите другой компонент с задержкой через context.getBean() — но это снижает удобство тестирования и ясность кода.

@Service
public class A implements ApplicationContextAware {
    private ApplicationContext ctx;
    public void setApplicationContext(ApplicationContext c) { this.ctx = c; }
    public void doWork() { ctx.getBean(B.class).help(); }
}

Внедрение через сеттер для разрыва циклической зависимости

Компоненты, внедряемые через сеттер, сначала создаются (конструирование завершается успешно), а затем зависимости устанавливаются. Это позволяет использовать циклические зависимости ценой изменяемости.

@Service
public class A {
    private B b;
    @Autowired public void setB(B b) { this.b = b; } // Spring sets after construction
}

spring.main.allow-circular-references (Spring Boot 2.6+)

Spring Boot 2.6+ по умолчанию запрещает циклические зависимости. Включайте их явно только временно, пока выполняется рефакторинг: spring.main.allow-circular-references=true.

# application.properties (temporary workaround only):
spring.main.allow-circular-references=true

Быстрая проверка

Почему внедрение через конструктор предпочтительнее внедрения в поле?

Итоги

Предпочитайте внедрение через конструктор: оно обеспечивает неизменяемость, удобство тестирования и явное описание зависимостей. Избегайте внедрения в поле. Используйте @Lazy или выделяйте общую логику в отдельный сервис, чтобы разрывать циклические зависимости. Spring Boot 2.6+ сообщает о циклических ссылках при запуске — расценивайте их как архитектурные предупреждения.

Часто задаваемые вопросы

Урок «Внедрение через конструктор и циклические зависимости» бесплатный?

Да — полный текст урока «Внедрение через конструктор и циклические зависимости» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «Внедрение через конструктор и циклические зависимости»?

Отдавайте предпочтение внедрению через конструктор для удобства тестирования, обнаруживайте циклические зависимости и устраняйте их с помощью @Lazy. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Внедрение через конструктор и циклические зависимости»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Автонастройка и стартеры Spring Boot
  2. Свойства приложения и профили
  3. Связывание компонентов: @Component, @Service, @Repository
  4. Внедрение через конструктор и циклические зависимости
← Назад к Java Academy