Стерео-аннотации и @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 — для чужих. Оба пути наполняют один контейнер. Выбор зависит от того, можете ли вы дописать классу аннотацию.

Проверьте себя
1. Как правильно сделать бином объект стороннего класса, которому нельзя дописать аннотацию (например, BCryptPasswordEncoder)?
AНикак, такие объекты не могут быть бинами
BОписать его методом @Bean в классе с @Configuration
CПометить @Service в исходниках библиотеки
DСоздать через new в каждом месте использования
2. Чем @Repository отличается от обычного @Component?
AНичем, это полные синонимы
BВыражает слой доступа к данным и добавляет перевод низкоуровневых исключений БД в единую иерархию Spring
CСоздаёт несколько экземпляров вместо синглтона
DЗапрещает внедрение зависимостей