Внедрение через конструктор и циклические зависимости
Отдавайте предпочтение внедрению через конструктор для удобства тестирования, обнаруживайте циклические зависимости и устраняйте их с помощью @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 — локальная установка не требуется.
Все уроки этого курса
- Автонастройка и стартеры Spring Boot
- Свойства приложения и профили
- Связывание компонентов: @Component, @Service, @Repository
- Внедрение через конструктор и циклические зависимости