← Назад к вопросам

Как работает Bean при обнаружении зависимостей на другие Bean?

1.7 Middle🔥 71 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Внедрение зависимостей (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 такого типа в контексте.

Алгоритм поиска:

  1. По типу (type) — ищем bean типа UserRepository
  2. Если несколько — по названию (name) — ищем bean с названием userRepository
  3. Если несколько — по аннотации @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 — это мощный паттерн который делает код гибче, тестируемее и поддерживаемее.