Стерео-аннотации и @Bean: способы объявить бин
Объявить бин можно двумя путями: пометить класс стерео-аннотацией или описать его методом @Bean в конфигурации. Разберём оба и когда какой уместен.
Суть: @Component и его специализации (@Service, @Repository, @Controller) — для ваших классов. @Bean в @Configuration — для объектов из чужих библиотек, которые нельзя пометить аннотацией.
Контейнер наполняется бинами двумя способами. Первый — сканирование: Spring находит классы с аннотациями-маркерами. Второй — явное описание: вы пишете фабричный метод. Понимание разницы избавит от путаницы «почему мой бин не виден».
Стерео-аннотации
Это аннотации, помечающие класс как кандидата в бины. Все они — специализации @Component:
| Аннотация | Назначение |
|---|---|
| @Component | Любой компонент общего назначения |
| @Service | Бизнес-логика (сервисный слой) |
| @Repository | Доступ к данным; добавляет перевод исключений БД |
| @Controller / @RestController | Веб-слой |
Функционально @Service и @Repository почти равны @Component, но они выражают намерение и иногда добавляют поведение (например, @Repository переводит низкоуровневые исключения JDBC в единую иерархию Spring).
@Service
public class PricingService { /* бизнес-логика */ }
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> { }
Метод @Bean в @Configuration
Когда нужный объект — из чужой библиотеки (вы не можете дописать ему аннотацию), его создают фабричным методом:
import org.springframework.context.annotation.*;
@Configuration
public class AppConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // класс из Spring Security
}
@Bean
public RestClient restClient() {
return RestClient.create("https://api.example.com");
}
}
Метод, помеченный @Bean, возвращает объект — и этот объект становится бином с именем метода.
Как работает под капотом
@ComponentScan находит классы со стерео-аннотациями и создаёт из них бины. Параллельно Spring выполняет методы @Bean в @Configuration-классах и регистрирует их результаты. Оба источника наполняют один и тот же контейнер.
Источники бинов
┌─────────────────────────┐ ┌──────────────────────────┐
│ @ComponentScan │ │ @Configuration │
│ находит @Service, │ │ выполняет методы @Bean │
│ @Repository, @Component│ │ (PasswordEncoder, ...) │
└───────────┬─────────────┘ └────────────┬─────────────┘
└───────────┬───────────────────┘
v
IoC-контейнер (общий)
Смоделируем регистрацию бинов из двух источников:
# Два источника бинов наполняют один контейнер
container = {}
# 1) "сканирование" классов со стерео-аннотациями
scanned = {"PricingService": object(), "ProductRepository": object()}
for name, bean in scanned.items():
container[name] = bean
# 2) методы @Bean в конфигурации
def password_encoder():
return "BCryptPasswordEncoder"
bean_methods = {"passwordEncoder": password_encoder}
for name, factory in bean_methods.items():
container[name] = factory()
print("Бинов в контейнере:", len(container))
print("passwordEncoder:", container["passwordEncoder"])
Запустите «Попробуй сам ▶»: оба источника складывают бины в один реестр — точно как Spring.
Частые ошибки
- Пытаться пометить чужой класс. Класс из библиотеки нельзя аннотировать — используйте
@Bean. - @Bean вне @Configuration. Метод
@Beanв обычном классе не сработает как ожидается. - Дубли бинов одного типа. Два бина одного типа без квалификатора вызовут неоднозначность при внедрении.
Best practices
- Свои классы помечайте осмысленной стерео-аннотацией (
@Serviceдля логики,@Repositoryдля данных). - Сторонние объекты создавайте методами
@Beanв@Configuration. - При нескольких бинах одного типа используйте
@Qualifierили@Primary.
Итог: стерео-аннотации — для ваших классов, @Bean — для чужих. Оба пути наполняют один контейнер. Выбор зависит от того, можете ли вы дописать классу аннотацию.