Питання 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 lifecycle]]
  • [[1. Що таке Dependency Injection]]
  • [[8. Що таке BeanPostProcessor]]