Вопрос 5 · Раздел 5

Как создать Bean в Spring?

4. @Primary → если несколько бинов одного типа 5. Functional registration → GraalVM Native 6. @Configuration проксируется → вызовы методов = кэш

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Способы создания бина:

1. Аннотации (самый частый):

@Service
public class UserService { }

@Component
public class EmailSender { }

2. @Bean в конфигурации:

@Configuration
public class Config {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Правило:

  • Свой код → @Service, @Component
  • Сторонние классы → @Bean

🟡 Middle Level

Стереотипные аннотации

@Component        // Общий компонент
@Service          // Бизнес-логика
@Repository       // Работа с БД (автоматическая обработка исключений)
@Controller       // Web контроллер
@RestController   // REST API (@Controller + @ResponseBody)

Как работает:

@ComponentScan → сканирует пакеты
  → Нашёл @Component
  → Создал BeanDefinition
  → Зарегистрировал в контексте

Java Config (@Bean)

@Configuration
public class InfrastructureConfig {
    
    @Bean
    @Primary  // Главный бин, если несколько
    public ObjectMapper mapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.findAndRegisterModules();
        return mapper;
    }
    
    @Bean(initMethod = "init", destroyMethod = "close")
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

FactoryBean

// Для сложной логики создания
public class MyFactoryBean implements FactoryBean<MyService> {
    public MyService getObject() { return new MyService(); }
    public Class<?> getObjectType() { return MyService.class; }
}

// Spring вызовет getObject() и зарегистрирует результат

🔴 Senior Level

CGLIB проксирование @Configuration

@Configuration
public class Config {
    @Bean
    public A a() { return new A(); }
    
    @Bean
    public B b() { 
        return new B(a());  // Вызов метода a()!
    }
}

// Spring проксирует Config через CGLIB:
// → Первый вызов a() → создаёт бин, кэширует
// → Второй вызов a() → возвращает из кэша!
// → Всегда один экземпляр (Singleton)

Программная регистрация (Spring 5+)

// Без рефлексии и сканирования аннотаций → быстрее при старте.
// Spring Boot использует это для GraalVM Native Image, где рефлексия ограничена.
context.registerBean(MyService.class, () -> new MyService());

// Для GraalVM Native Image → рефлексия ограничена
// Программная регистрация предпочтительнее

ImportSelector / ImportBeanDefinitionRegistrar

// ImportSelector — интерфейс, позволяющий динамически импортировать классы-бины по условию. Так работают все @Enable... аннотации (например, @EnableCaching, @EnableScheduling).

// ASM — библиотека для чтения байт-кода классов без их загрузки в JVM. Spring использует её при @ComponentScan для поиска аннотаций.

// Так работают все @Enable... аннотации
public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata metadata) {
        return new String[] { "com.example.Service1", "com.example.Service2" };
    }
}

// Как это используется:
@Import(MyImportSelector.class)
public @interface EnableMyFeature { }
// Теперь @EnableMyFeature импортирует Service1 и Service2

@EnableMyFeature  // → Импортирует Service1 и Service2

Сканирование: ASM библиотека

ClassPathBeanDefinitionScanner:
  → Читает байт-код через ASM
  → НЕ загружает классы в JVM!
  → Ищет аннотации
  → Создаёт BeanDefinition
  
→ На больших проектах → медленный старт
→ Ограничьте @ComponentScan конкретными пакетами

Когда НЕ использовать @Bean

  1. Простые объекты данных — DTO, Entity — создаются через new или ORM
  2. Слишком много бинов — каждый бин = overhead при старте. Для 1000+ бинов рассмотрите ленивую инициализацию
  3. FactoryBean — нужен только когда логика создания сложная и зависит от внешних условий (например, EntityManagerFactory)

Production Experience

Реальный сценарий: медленный старт

  • 10,000 классов в classpath
  • Сканирование: 15 секунд
  • Решение: ограничили @ComponentScan(“com.myapp”)
  • Результат: старт 2 секунды

Best Practices

  1. @Service/@Component → свой код
  2. @Bean → сторонние библиотеки
  3. Ограничьте сканирование → конкретные пакеты
  4. @Primary → если несколько бинов одного типа
  5. Functional registration → GraalVM Native
  6. @Configuration проксируется → вызовы методов = кэш

Резюме для Senior

  • @Component → сканирование через ASM
  • @Bean → Java Config, CGLIB прокси
  • FactoryBean → сложная логика создания
  • registerBean → программно, GraalVM
  • ImportSelector → @Enable… аннотации
  • Ограничьте сканирование → быстрее старт

🎯 Шпаргалка для интервью

Обязательно знать:

  • Свой код → @Service, @Component, @Repository, @Controller (автоматическое сканирование)
  • Сторонние библиотеки → @Bean в @Configuration (ручная настройка)
  • @ComponentScan сканирует пакеты через ASM (чтение байт-кода без загрузки классов)
  • @Configuration проксируется через CGLIB: повторные вызовы методов возвращают кэшированный Singleton
  • @Primary — главный бин, если несколько одного типа
  • FactoryBean — для сложной логики создания (Spring вызывает getObject())
  • Программная регистрация registerBean() — GraalVM Native Image, без рефлексии
  • ImportSelector — динамический импорт бинов, так работают все @Enable... аннотации

Частые уточняющие вопросы:

  • Почему вызов a() внутри @Configuration возвращает тот же бин? CGLIB прокси перехватывает вызов и возвращает Singleton из кэша.
  • Зачем ограничивать @ComponentScan? На больших проектах сканирование тысяч классов замедляет старт (15 сек → 2 сек).
  • Когда использовать FactoryBean? Когда создание зависит от внешних условий (например, EntityManagerFactory).
  • Что такое ImportSelector? Интерфейс для динамического импорта классов-бинов — основа всех @Enable... аннотаций.

Красные флаги (НЕ говорить):

  • «Можно объявить @Bean для DTO/Entity» (это обычные объекты данных, не бины)
  • «Чем больше @ComponentScan, тем лучше — Spring всё найдёт» (медленный старт на больших проектах)
  • «@Configuration методы вызываются напрямую» (CGLIB прокси перехватывает и кэширует)
  • «FactoryBean и @Bean — одно и то же» (FactoryBean — интерфейс со сложной логикой создания, @Bean — простой метод)

Связанные темы:

  • [[4. Что такое Bean в Spring]]
  • [[6. Что такое Bean Lifecycle]]
  • [[7. Какие этапы жизненного цикла Bean]]
  • [[1. Что такое Dependency Injection]]
  • [[8. Что такое BeanPostProcessor]]