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

Как бы написал часть Spring Framework, который бы создавал объекты из полей с @Bean

2.0 Middle🔥 251 комментариев
#Spring Boot и Spring Data#Spring Framework

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

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

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

Реализация @Bean-аннотации Spring

Этот вопрос демонстрирует глубокое понимание Spring. Я напишу упрощенную версию механизма, который сканирует классы, находит методы с @Bean и регистрирует их как бины в контексте.

Архитектура решения

1. Сканирование классов -> Find @Configuration classes
2. Анализ методов -> Find @Bean methods
3. Создание прокси -> Create Bean definitions
4. Управление зависимостями -> Resolve dependencies
5. Инстанцирование объектов -> Create instances

Шаг 1: Определение аннотаций

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Configuration {
    String value() default "";
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
    String name() default "";
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

Шаг 2: Определение Bean Definition

import java.lang.reflect.Method;
import java.util.List;

public class BeanDefinition {
    private String beanName;
    private Class<?> beanClass;
    private Method factoryMethod; // Метод с @Bean
    private Object configInstance; // Экземпляр класса с @Configuration
    private List<String> dependencies; // Имена зависимостей
    private Object instance; // Кэшированный экземпляр (singleton)
    
    public BeanDefinition(
            String beanName,
            Class<?> beanClass,
            Method factoryMethod,
            Object configInstance) {
        this.beanName = beanName;
        this.beanClass = beanClass;
        this.factoryMethod = factoryMethod;
        this.configInstance = configInstance;
        this.dependencies = new ArrayList<>();
    }
    
    public String getBeanName() { return beanName; }
    public Class<?> getBeanClass() { return beanClass; }
    public Method getFactoryMethod() { return factoryMethod; }
    public Object getConfigInstance() { return configInstance; }
    public List<String> getDependencies() { return dependencies; }
    public void setInstance(Object instance) { this.instance = instance; }
    public Object getInstance() { return instance; }
    public boolean isSingleton() { return instance != null; }
}

Шаг 3: Spring Context (Контейнер бинов)

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

public class SimpleApplicationContext {
    private Map<String, BeanDefinition> beanDefinitions = new HashMap<>();
    private Map<String, Object> singletons = new HashMap<>();
    private List<Object> configurationInstances = new ArrayList<>();
    
    /**
     * Инициализируем контекст: сканируем конфигурации и регистрируем бины
     */
    public SimpleApplicationContext(Class<?>... configClasses) {
        // Шаг 1: Регистрируем конфигурационные классы
        for (Class<?> configClass : configClasses) {
            registerConfigurationClass(configClass);
        }
        
        // Шаг 2: Создаем все singleton бины
        initializeSingletons();
    }
    
    /**
     * Регистрируем конфигурационный класс и его @Bean методы
     */
    private void registerConfigurationClass(Class<?> configClass) {
        // Проверяем, что класс помечен @Configuration
        if (!configClass.isAnnotationPresent(Configuration.class)) {
            throw new IllegalArgumentException(
                configClass.getName() + " не помечен @Configuration");
        }
        
        try {
            // Создаем экземпляр конфигурационного класса
            Object configInstance = configClass.getDeclaredConstructor().newInstance();
            configurationInstances.add(configInstance);
            
            // Обрабатываем все методы класса
            Method[] methods = configClass.getDeclaredMethods();
            for (Method method : methods) {
                // Ищем методы помеченные @Bean
                if (method.isAnnotationPresent(Bean.class)) {
                    registerBeanMethod(method, configInstance);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(
                "Ошибка создания конфигурации: " + configClass.getName(), e);
        }
    }
    
    /**
     * Регистрируем одну @Bean переменную
     */
    private void registerBeanMethod(Method method, Object configInstance) {
        Bean beanAnnotation = method.getAnnotation(Bean.class);
        
        // Определяем имя бина (или используем имя метода)
        String beanName = !beanAnnotation.name().isEmpty() 
            ? beanAnnotation.name() 
            : method.getName();
        
        // Возвращаемый тип метода — это тип бина
        Class<?> beanClass = method.getReturnType();
        
        // Создаем определение бина
        BeanDefinition definition = new BeanDefinition(
            beanName,
            beanClass,
            method,
            configInstance
        );
        
        // Анализируем зависимости метода (его параметры)
        Parameter[] parameters = method.getParameters();
        for (Parameter param : parameters) {
            // Параметры метода — это зависимости
            // Их имена совпадают с именами других бинов
            definition.getDependencies().add(param.getName());
        }
        
        // Регистрируем определение
        beanDefinitions.put(beanName, definition);
        
        System.out.println("Зарегистрирован бин: " + beanName + 
                         " (" + beanClass.getSimpleName() + ")");
    }
    
    /**
     * Инициализируем все singleton бины
     */
    private void initializeSingletons() {
        for (BeanDefinition definition : beanDefinitions.values()) {
            Object instance = createBean(definition);
            singletons.put(definition.getBeanName(), instance);
            definition.setInstance(instance);
        }
    }
    
    /**
     * Создаем экземпляр бина, разрешая его зависимости
     */
    private Object createBean(BeanDefinition definition) {
        try {
            Method factoryMethod = definition.getFactoryMethod();
            
            // Получаем параметры метода
            Parameter[] parameters = factoryMethod.getParameters();
            Object[] args = new Object[parameters.length];
            
            // Разрешаем зависимости
            for (int i = 0; i < parameters.length; i++) {
                Parameter param = parameters[i];
                String paramName = param.getName();
                Class<?> paramType = param.getType();
                
                // Ищем бин с таким именем или типом
                Object dependency = getBean(paramName, paramType);
                
                if (dependency == null) {
                    throw new IllegalArgumentException(
                        "Не найдена зависимость: " + paramName + 
                        " для бина: " + definition.getBeanName());
                }
                
                args[i] = dependency;
            }
            
            // Вызываем фабричный метод с разрешенными зависимостями
            factoryMethod.setAccessible(true);
            Object instance = factoryMethod.invoke(
                definition.getConfigInstance(), args);
            
            System.out.println("Создан бин: " + definition.getBeanName());
            return instance;
            
        } catch (Exception e) {
            throw new RuntimeException(
                "Ошибка создания бина: " + definition.getBeanName(), e);
        }
    }
    
    /**
     * Получаем бин по имени или типу
     */
    public Object getBean(String name) {
        return singletons.get(name);
    }
    
    public <T> T getBean(Class<T> type) {
        for (Object bean : singletons.values()) {
            if (type.isInstance(bean)) {
                return (T) bean;
            }
        }
        return null;
    }
    
    /**
     * Вспомогательный метод для разрешения зависимостей
     */
    private Object getBean(String name, Class<?> type) {
        // Сначала ищем по имени
        if (singletons.containsKey(name)) {
            return singletons.get(name);
        }
        
        // Затем ищем по типу
        for (Object bean : singletons.values()) {
            if (type.isInstance(bean)) {
                return bean;
            }
        }
        
        return null;
    }
    
    /**
     * Закрываем контекст и освобождаем ресурсы
     */
    public void close() {
        singletons.clear();
        beanDefinitions.clear();
        configurationInstances.clear();
    }
}

Шаг 4: Пример использования

// Определяем конфигурацию
@Configuration
public class AppConfig {
    
    @Bean
    public UserRepository userRepository() {
        return new UserRepository();
    }
    
    @Bean
    public UserService userService(UserRepository userRepository) {
        // Spring найдет userRepository в контексте и передаст сюда
        return new UserService(userRepository);
    }
    
    @Bean(name = "primaryDataSource")
    public DataSource dataSource() {
        return new PostgresDataSource();
    }
}

// Классы для примера
class UserRepository {
    public User findById(Long id) {
        return new User(id, "John");
    }
}

class UserService {
    private UserRepository repository;
    
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    public User getUser(Long id) {
        return repository.findById(id);
    }
}

class User {
    Long id;
    String name;
    
    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

class DataSource {}
class PostgresDataSource extends DataSource {}

// Использование контекста
public class Main {
    public static void main(String[] args) {
        // Создаем контекст и регистрируем конфигурацию
        SimpleApplicationContext context = 
            new SimpleApplicationContext(AppConfig.class);
        
        // Получаем бин
        UserService userService = context.getBean("userService", UserService.class);
        User user = userService.getUser(1L);
        System.out.println("User: " + user.name);
        
        // Закрываем контекст
        context.close();
    }
}

// Вывод:
// Зарегистрирован бин: userRepository (UserRepository)
// Зарегистрирован бин: userService (UserService)
// Зарегистрирован бин: primaryDataSource (PostgresDataSource)
// Создан бин: userRepository
// Создан бин: userService
// Создан бин: primaryDataSource
// User: John

Шаг 5: Дополнительные возможности

Поддержка Lazy инициализации

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {
}

// В контексте:
private Object createBean(BeanDefinition definition) {
    if (definition.getFactoryMethod().isAnnotationPresent(Lazy.class)) {
        // Создаем бин только при первом запросе
        return new LazyBeanProxy(definition);
    }
    // ... обычная инициализация
}

Поддержка Prototype бинов

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    String value();
    // SINGLETON, PROTOTYPE, etc.
}

Обработка зависимостей через поля

private void injectFieldDependencies(Object instance) {
    Class<?> clazz = instance.getClass();
    Field[] fields = clazz.getDeclaredFields();
    
    for (Field field : fields) {
        if (field.isAnnotationPresent(Autowired.class)) {
            Object dependency = getBean(field.getType());
            field.setAccessible(true);
            field.set(instance, dependency);
        }
    }
}

Ключевые принципы реализации

  1. Reflection API: Используем для анализа аннотаций и вызова методов
  2. Dependency Injection: Разрешаем зависимости перед инстанцированием
  3. Singleton Pattern: Кэшируем уже созданные бины
  4. Inversion of Control: Контекст управляет жизненным циклом объектов
  5. Lazy Initialization: Бины создаются при необходимости

Различия с реальным Spring

Реальный Spring значительно сложнее:

  • Поддержка PostConstruct / PreDestroy callbacks
  • Bean lifecycle hooks (InitializingBean, DisposableBean)
  • Automatic type-based wiring
  • Circular dependency detection
  • Aspect-Oriented Programming (AOP)
  • Conditional beans (@ConditionalOnProperty)
  • Property placeholder resolution
  • Event publishing

Но базовый механизм остается тем же: сканирование аннотаций -> создание определений -> разрешение зависимостей -> инстанцирование объектов.

Эта реализация демонстрирует фундаментальные концепции, на которых построен Spring Framework.