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

Как работает getBean()?

1.0 Junior🔥 241 комментариев
#Spring Framework

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

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

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

# Как работает getBean() в Spring

Метод getBean() является фундаментальной операцией в Spring контейнере. Вот полный разбор его работы.

Что такое getBean()?

getBean() - это метод BeanFactory, который извлекает экземпляр бина из Spring контейнера по его имени или типу.

// Основные варианты
ApplicationContext context = SpringApplication.run(Application.class);

// По имени (тип String)
UserService service = (UserService) context.getBean("userService");

// По типу (type-safe)
UserService service = context.getBean(UserService.class);

// По имени и типу
UserService service = context.getBean("userService", UserService.class);

// С параметрами конструктора
UserService service = context.getBean(UserService.class, "param1", "param2");

Архитектура Spring Контейнера

┌────────────────────────────────────────┐
│      ApplicationContext                │
│  (extends BeanFactory)                 │
├────────────────────────────────────────┤
│                                        │
│  ┌─────────────────────────────────┐  │
│  │   DefaultListableBeanFactory    │  │
│  │  (реальная реализация)          │  │
│  │                                 │  │
│  │  - beanDefinitionMap            │  │
│  │  - singletonObjects (кэш)       │  │
│  │  - dependencyComparator          │  │
│  └─────────────────────────────────┘  │
│                                        │
└────────────────────────────────────────┘

Поэтапная работа getBean()

Шаг 1: Трансформация имени

public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}

// Если передано имя типа "&userService", убираем & (получение самого Factory)
String beanName = transformedBeanName(name);  // Удаляет & если есть

// Если это alias, получаем реальное имя
beanName = canonicalName(beanName);

Шаг 2: Проверка кэша синглтонов

Это самая критичная оптимизация:

private final Map<String, Object> singletonObjects = 
    new ConcurrentHashMap<>(256);  // Потокобезопасный кэш

public Object getBean(String name) {
    Object sharedInstance = getSingleton(name);
    
    if (sharedInstance != null) {
        return sharedInstance;  // ✅ Найдено в кэше (99% случаев)
    }
    
    // Если не в кэше, нужно создать
    return createBean(name);
}

Шаг 3: Проверка, находится ли создание бина в процессе

Для обнаружения циклических зависимостей:

private final Set<String> singletonsCurrentlyInCreation = 
    Collections.synchronizedSet(new HashSet<>(16));

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        
        if (singletonObject == null) {
            // Проверяем, не создаётся ли уже
            if (this.singletonsCurrentlyInCreation.contains(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }
            
            this.singletonsCurrentlyInCreation.add(beanName);
            try {
                singletonObject = singletonFactory.getObject();
            } finally {
                this.singletonsCurrentlyInCreation.remove(beanName);
            }
            
            this.singletonObjects.put(beanName, singletonObject);
        }
        
        return singletonObject;
    }
}

Шаг 4: Получение BeanDefinition

BeanDefinition beanDefinition = getBeanDefinition(beanName);

// BeanDefinition содержит всю информацию о бине:
// - Какой класс
// - Конструкторские параметры
// - Внедряемые зависимости
// - Scope (singleton, prototype, etc.)
// - Lifecycle callbacks

Шаг 5: Разрешение зависимостей

String[] dependsOn = beanDefinition.getDependsOn();

for (String dep : dependsOn) {
    // Рекурсивно создаём зависимости
    getBean(dep);  // ← Рекурсия!
}

// Пример: если UserService зависит от UserRepository
// Сначала создадим UserRepository, потом UserService

Шаг 6: Создание экземпляра (Instantiation)

Object instance = instantiateBean(beanDefinition, beanName);

// Три способа создания:

// 1. Через конструктор (самый частый)
Constructor<?> constructorToUse = findConstructor(beanDefinition);
instance = constructorToUse.newInstance(resolvedArgs);

// 2. Через static factory method
instanceof = beanClass.getMethod("getInstance").invoke(null);

// 3. Через factory bean
Factory factoryBean = getBean(factoryBeanName);
instance = factoryBean.createObject();

Шаг 7: Автоподключение зависимостей (Autowiring)

if (beanDefinition.getAutowireMode() == AUTOWIRE_BY_NAME) {
    // @Autowired поля и setters
    Field[] fields = beanClass.getDeclaredFields();
    
    for (Field field : fields) {
        if (field.isAnnotationPresent(Autowired.class)) {
            Object dependency = getBean(field.getName());
            field.setAccessible(true);
            field.set(instance, dependency);
        }
    }
}

Шаг 8: Инициализация (Initialization)

// 1. PostConstruct callback
if (bean.getClass().getMethod("init") != null) {
    bean.init();
}

// 2. InitializingBean interface
if (bean instanceof InitializingBean) {
    ((InitializingBean) bean).afterPropertiesSet();
}

// 3. init-method из конфигурации
if (beanDefinition.getInitMethodName() != null) {
    Method initMethod = beanClass.getMethod(beanDefinition.getInitMethodName());
    initMethod.invoke(bean);
}

Шаг 9: BeanPostProcessor callbacks

public Object applyBeanPostProcessors(Object bean, String beanName) {
    // Перед инициализацией
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        bean = processor.postProcessBeforeInitialization(bean, beanName);
    }
    
    invokeInitMethods(bean, beanName);
    
    // После инициализации
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        bean = processor.postProcessAfterInitialization(bean, beanName);
    }
    
    return bean;
}

// Пример: @Transactional обрабатывается в BeanPostProcessor
// который создаёт proxy

Шаг 10: Кэширование (для синглтонов)

if (beanDefinition.isSingleton()) {
    // Добавляем в кэш для будущих вызовов
    this.singletonObjects.put(beanName, bean);
}

// Следующий getBean() сразу вернёт из кэша на Шаге 2

Полная диаграмма потока выполнения

getBean("userService")
    ↓
[1] Трансформация имени → "userService"
    ↓
[2] Проверка singletonObjects кэша
    └─→ Если есть → return (быстро!)
    └─→ Если нет → продолжить
    ↓
[3] Проверка singletonsCurrentlyInCreation (циклические зависимости)
    ↓
[4] Получение BeanDefinition
    ↓
[5] Разрешение зависимостей (рекурсия)
    ↓
[6] Instantiation (создание объекта)
    ↓
[7] Autowiring (внедрение зависимостей)
    ↓
[8] Инициализация (@PostConstruct, afterPropertiesSet())
    ↓
[9] BeanPostProcessor callbacks (создание proxies для @Transactional и т.д.)
    ↓
[10] Кэширование в singletonObjects
    ↓
Возврат бина

Пример с кодом

@Service
public class UserService {
    private UserRepository userRepository;
    
    // Конструкторское внедрение - очень рекомендуется
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;  // [7] Autowiring
    }
    
    @PostConstruct
    public void init() {
        System.out.println("[8] UserService инициализирован");
        // Здесь можно инициализировать ресурсы
    }
    
    @Transactional  // [9] BeanPostProcessor создаст proxy
    public User getUser(Long id) {
        return userRepository.findById(id);
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("[10] UserService уничтожен");
    }
}

// Использование
public static void main(String[] args) {
    ApplicationContext context = SpringApplication.run(Application.class);
    
    // [2] Если первый раз - полный цикл выше
    UserService service1 = context.getBean(UserService.class);
    
    // [2] Второй раз - из кэша (очень быстро)
    UserService service2 = context.getBean(UserService.class);
    
    System.out.println(service1 == service2);  // true (один объект)
}

Оптимизации в getBean()

1. ConcurrentHashMap для кэша

// Thread-safe без блокировки всего кэша
Object bean = singletonObjects.get(beanName);
// O(1) амортизированное время

2. Ранние проверки

// Проверяем кэш ДО вычисления зависимостей
if (sharedInstance != null) return sharedInstance;  // Экономит CPU

3. Lazy initialization

// Бины создаются только когда их запрашивают (по умолчанию)
// Не при старте приложения

// Исключение: @Bean с @Lazy(false) или eager=true
@Bean(initMethod = "init")
public DataSource dataSource() { ... }  // Создаст сразу

Проблемы и решения

Проблема 1: Циклические зависимости

@Service
public class AService {
    @Autowired
    private BService bService;  // BService зависит от AService!
}

@Service
public class BService {
    @Autowired
    private AService aService;
}

// Решение: Spring использует proxy и setter injection

Проблема 2: getBean() при старте приложения

// ❌ Плохо - явно вызываем getBean()
@Component
public class MyInit {
    @Autowired
    private ApplicationContext context;
    
    public void init() {
        MyBean bean = context.getBean(MyBean.class);  // Лишний вызов
    }
}

// ✅ Хорошо - используем @Autowired
@Component
public class MyInit {
    @Autowired
    private MyBean bean;  // Spring сам вызовет getBean
}

Резюме

getBean() работает так:

  1. Проверка кэша - большинство вызовов возвращают cached singleton (очень быстро)
  2. BeanDefinition - получаем метаинформацию о бине
  3. Разрешение зависимостей - рекурсивно создаём нужные бины
  4. Instantiation - создание объекта через конструктор или factory
  5. Autowiring - внедрение зависимостей в поля/setters
  6. Инициализация - вызов lifecycle callbacks
  7. Post-processing - применение BeanPostProcessors (proxies, etc.)
  8. Кэширование - добавляем в singletonObjects

Вся эта сложность скрыта от разработчика. Благодаря этому Spring обеспечивает гибкость, безопасность и производительность управления объектами в Java приложениях.