Как создать Bean в Spring?
4. @Primary → если несколько бинов одного типа 5. Functional registration → GraalVM Native 6. @Configuration проксируется → вызовы методов = кэш
🟢 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
- Простые объекты данных — DTO, Entity — создаются через
newили ORM - Слишком много бинов — каждый бин = overhead при старте. Для 1000+ бинов рассмотрите ленивую инициализацию
- FactoryBean — нужен только когда логика создания сложная и зависит от внешних условий (например, EntityManagerFactory)
Production Experience
Реальный сценарий: медленный старт
- 10,000 классов в classpath
- Сканирование: 15 секунд
- Решение: ограничили @ComponentScan(“com.myapp”)
- Результат: старт 2 секунды
Best Practices
- @Service/@Component → свой код
- @Bean → сторонние библиотеки
- Ограничьте сканирование → конкретные пакеты
- @Primary → если несколько бинов одного типа
- Functional registration → GraalVM Native
- @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]]