Как работает Bean при обнаружении зависимостей на другие Bean?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Внедрение зависимостей (Dependency Injection) в Spring
Это сердце Spring Framework. Объясню как работает механизм обнаружения и инъекции зависимостей.
Основная идея
Вместо того чтобы класс сам создавал свои зависимости:
// Плохо: сильная связность
public class UserService {
private UserRepository userRepository = new UserRepository(); // Сам создал
private EmailService emailService = new EmailService(); // Сам создал
}
Spring создаёт их и передаёт:
// Хорошо: слабая связность
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
@Autowired
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
Как Spring обнаруживает зависимости
Шаг 1: Component Scanning
При старте приложение Spring сканирует классы с аннотациями:
@Configuration
@ComponentScan("com.example")
public class AppConfig {
}
// Или в application.properties
spring.components.scan.packages=com.example
Spring находит все классы с @Component, @Service, @Repository, @Controller.
Шаг 2: Создание Bean Definition
Для каждого найденного класса Spring создаёт описание (BeanDefinition):
public class BeanDefinition {
private Class<?> beanClass = UserService.class;
private String scope = "singleton";
private List<DependencyDescriptor> dependencies = [
new DependencyDescriptor(UserRepository.class),
new DependencyDescriptor(EmailService.class)
];
private Constructor<?> constructor; // Конструктор для инъекции
}
Шаг 3: Разрешение зависимостей (Dependency Resolution)
Вот магия. Spring смотрит на конструктор UserService:
public UserService(UserRepository userRepository, EmailService emailService) { }
Он видит два параметра типа UserRepository и EmailService. Ищет beans такого типа в контексте.
Алгоритм поиска:
- По типу (type) — ищем bean типа UserRepository
- Если несколько — по названию (name) — ищем bean с названием userRepository
- Если несколько — по аннотации
@Qualifier
@Service
public class UserService {
@Autowired
@Qualifier("postgresUserRepository")
private UserRepository userRepository;
// Вместо стандартного UserRepository используется postgresUserRepository
}
Шаг 4: Создание экземпляра
// Примерный код того как работает Spring
public class BeanFactory {
private Map<String, Object> singletons = new HashMap<>();
public Object getBean(String beanName) {
if (singletons.containsKey(beanName)) {
return singletons.get(beanName); // Возвращаем существующий
}
BeanDefinition definition = getBeanDefinition(beanName);
// 1. Найти зависимости
List<Object> dependencies = new ArrayList<>();
for (DependencyDescriptor dep : definition.getDependencies()) {
// Рекурсивно разрешаем зависимости
Object dependency = getBean(dep.getType().getSimpleName());
dependencies.add(dependency);
}
// 2. Вызвать конструктор с зависимостями
Object bean = definition.getConstructor()
.newInstance(dependencies.toArray());
// 3. Сохранить как синглтон
singletons.put(beanName, bean);
return bean;
}
}
Варианты инъекции
1. Constructor Injection (рекомендуется)
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// Spring вызовет этот конструктор
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
Плюсы: зависимости обязательны, immutable, легко тестировать Минусы: нужно писать конструктор
2. Setter Injection
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Плюсы: гибко, можно переопределить позже Минусы: зависимости не обязательны, mutable, сложнее тестировать
3. Field Injection
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
Плюсы: просто писать Минусы: невозможно использовать в тестах без reflection, не работает с final, сложно понять зависимости
Spring team не рекомендует этот подход!
Сценарий: Circular Dependency
@Service
public class UserService {
@Autowired
private OrderService orderService; // Зависит от OrderService
}
@Service
public class OrderService {
@Autowired
private UserService userService; // А OrderService зависит от UserService!
}
Проблема: A → B → A (циклическая зависимость).
Spring обычно разрешает этот случай через setter injection (создаёт оба, потом инжектирует).
Решение:
// Вариант 1: Используй ObjectProvider
@Service
public class UserService {
private final ObjectProvider<OrderService> orderService;
public UserService(ObjectProvider<OrderService> orderService) {
this.orderService = orderService; // Lazy loading
}
}
// Вариант 2: Разьедини циклы
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Жизненный цикл Bean
1. Создание Bean Definition (scanning)
2. Выполнение BeanFactoryPostProcessors
3. Создание экземпляра
4. Установка свойств (setter injection)
5. Выполнение BeanPostProcessor.postProcessBeforeInitialization()
6. Вызов методов инициализации (@PostConstruct или InitializingBean.afterPropertiesSet())
7. Выполнение BeanPostProcessor.postProcessAfterInitialization()
8. Bean готов к использованию
...
9. Вызов методов destroy (@PreDestroy или DisposableBean.destroy())
@Component
public class MyBean {
@PostConstruct
public void init() {
System.out.println("Bean инициализирован");
}
@PreDestroy
public void cleanup() {
System.out.println("Bean закрывается");
}
}
Наиболее частые ошибки
1. Забыли @Autowired
// Неправильно
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) { // Spring не инжектит!
this.userRepository = userRepository;
}
}
// Правильно (если несколько конструкторов)
public class UserService {
@Autowired // Обязательно указать какой конструктор
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
2. No qualifying bean of type
Это означает что Spring не нашёл нужный bean.
@Service
public class UserService {
@Autowired
private NonExistentRepository repo; // Этот bean не создан!
}
Добавь @Repository или @Bean:
@Repository
public class NonExistentRepository { }
3. Несколько beans одного типа
@Repository
public class PostgresUserRepository implements UserRepository { }
@Repository
public class MongoUserRepository implements UserRepository { }
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // Ошибка! Два beans!
}
Решение:
@Service
public class UserService {
@Autowired
@Qualifier("postgresUserRepository")
private UserRepository userRepository;
}
Тестирование с зависимостями
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testCreateUser() {
User user = new User("John");
when(userRepository.save(user)).thenReturn(user);
User result = userService.createUser("John");
assertThat(result.getName()).isEqualTo("John");
verify(userRepository).save(user);
}
}
Spring's Dependency Injection — это мощный паттерн который делает код гибче, тестируемее и поддерживаемее.