В каких случаях в Spring требуется создавать несколько экземпляров бина одного типа?

Ответ

По умолчанию Spring создает бины в singleton scope (один экземпляр на контейнер). Несколько экземпляров требуются в следующих сценариях:

  1. Прототипные (prototype) бины: Когда состояние бина не должно разделяться между запросами.

    @Component
    @Scope("prototype") // Каждый запрос на внедрение/получение создает новый объект
    public class ShoppingCart {
        private List<Item> items = new ArrayList<>();
        // У каждого пользователя/сессии своя корзина
    }
  2. Несколько реализаций одного интерфейса с разной конфигурацией:

    @Configuration
    public class DataSourceConfig {
        @Bean
        @Primary // Основной бин для автопривязки без @Qualifier
        public DataSource primaryDataSource() {
            return DataSourceBuilder.create()
                .url("jdbc:mysql://host1/db")
                .username("user1")
                .build();
        }
    
        @Bean
        public DataSource secondaryDataSource() {
            return DataSourceBuilder.create()
                .url("jdbc:mysql://host2/db")
                .username("user2")
                .build();
        }
    }

    Использование с @Qualifier:

    @Service
    public class ReportingService {
        private final DataSource readOnlyDataSource;
        private final DataSource writeDataSource;
    
        public ReportingService(
                @Qualifier("secondaryDataSource") DataSource readOnlyDataSource,
                @Qualifier("primaryDataSource") DataSource writeDataSource) {
            this.readOnlyDataSource = readOnlyDataSource;
            this.writeDataSource = writeDataSource;
        }
    }
  3. Бины, зависящие от внешнего контекста (например, HTTP-запроса или сессии):

    @Component
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class UserPreferences {
        // У каждого HTTP-сеанса будет свой экземпляр
    }
  4. Создание бинов динамически, на основе условий:

    @Configuration
    public class DynamicBeanConfig {
        @Bean
        @ConditionalOnProperty(name = "cache.provider", havingValue = "redis")
        public CacheManager redisCacheManager() { /* ... */ }
    
        @Bean
        @ConditionalOnProperty(name = "cache.provider", havingValue = "caffeine")
        public CacheManager caffeineCacheManager() { /* ... */ }
    }

Ключевые аннотации и подходы:

  • @Scope("prototype") – для создания нового экземпляра при каждом запросе.
  • @Qualifier("имяБина") – для выбора конкретной реализации при внедрении зависимости.
  • @Primary – чтобы пометить бин по умолчанию.
  • Условное создание бинов (@Conditional, @Profile) – для создания разных бинов в разных средах.

Ответ 18+ 🔞

А, ну вот, опять про эти ваши бины в Спринге! Сидит народ и думает: "Один экземпляр на весь контейнер, и всем хватает". А потом такой: "Ой, а у меня у всех пользователей одна корзина в интернет-магазине, блядь!". Ну да, конечно, один на всех ShoppingCart — это ж как в коммуналке мыло, всем одно, ебать.

Так вот, слушай, когда тебе надо несколько экземпляров, а не один синглтон, который на всех поршан:

  1. Прототипные бины, ёпта. Это когда состояние у каждого должно быть своё, личное, интимное. Представь, что у тебя ShoppingCart. Если он синглтон, то я, ты и твоя бабушка — все кладём товары в одну общую корзину. Пиздец, а не заказ. Поэтому вешаем @Scope("prototype"). Каждый раз, когда кто-то просит этот бин — ему выдают свеженький, только что с конвейера, и он там один со своими items может делать что хочет. Чистая магия, блядь.

    @Component
    @Scope("prototype") // Вот эта хуйня делает новый объект каждый раз, когда его просят
    public class ShoppingCart {
        private List<Item> items = new ArrayList<>();
        // У каждого юзера теперь своя корзина, а не общий сортир
    }
  2. Несколько реализаций одного интерфейса. Классика, блядь! Допустим, у тебя два источника данных: один для чтения, другой для записи. Spring смотрит на интерфейс DataSource и охуевает: "Какой из двух, нахуй, тебе подсовывать?". А ты ему: "Да вот этот, падла, для чтения, а вот этот — для записи!". Для этого есть @Qualifier. А чтобы не париться и не указывать его везде, одному из бинов можно дать медальку @Primary — он будет главным по умолчанию.

    @Configuration
    public class DataSourceConfig {
        @Bean
        @Primary // Этот — главный пацан. Будет вставляться, если не кричать "я хочу другой!"
        public DataSource primaryDataSource() {
            return DataSourceBuilder.create()
                .url("jdbc:mysql://host1/db")
                .username("user1")
                .build();
        }
    
        @Bean
        public DataSource secondaryDataSource() {
            return DataSourceBuilder.create()
                .url("jdbc:mysql://host2/db")
                .username("user2")
                .build();
        }
    }

    А вот как их потом различать, когда внедряешь:

    @Service
    public class ReportingService {
        private final DataSource readOnlyDataSource;
        private final DataSource writeDataSource;
    
        public ReportingService(
                @Qualifier("secondaryDataSource") DataSource readOnlyDataSource, // Кричим: "Дай мне secondary!"
                @Qualifier("primaryDataSource") DataSource writeDataSource) { // А тут: "Дай primary!"
            this.readOnlyDataSource = readOnlyDataSource;
            this.writeDataSource = writeDataSource;
        }
    }
  3. Бины, привязанные к контексту (сессии, запросу). Это вообще отдельная песня, ебать мои старые костыли. Допустим, настройки пользователя. Они же у каждого свои, мать их! Нельзя же одну на всех UserPreferences сделать. Вешаем scope SCOPE_SESSION — и у каждого HTTP-сеанса будет свой личный экземпляр. А proxyMode — это такая хитрая жопа, чтобы Spring мог вставить прокси, даже если бин ещё не создан.

    @Component
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class UserPreferences {
        // У Васи — свои настройки, у Пети — свои. И все довольны.
    }
  4. Динамическое создание, на основе каких-то условий. Тут вообще раздолье, блядь. Хочешь Redis-кэш, если в настройках cache.provider=redis? Пожалуйста! Хочешь Caffeine? Да не вопрос! Spring почитает application.properties и создаст только тот бин, который подходит под условие. Остальные — нахуй, их даже не будет в контексте.

    @Configuration
    public class DynamicBeanConfig {
        @Bean
        @ConditionalOnProperty(name = "cache.provider", havingValue = "redis")
        public CacheManager redisCacheManager() { /* ... */ } // Создастся, только если property совпало
    
        @Bean
        @ConditionalOnProperty(name = "cache.provider", havingValue = "caffeine")
        public CacheManager caffeineCacheManager() { /* ... */ }
    }

Короче, запомни, чувак:

  • @Scope("prototype") — когда надо "каждому по новенькому".
  • @Qualifier("имяБина") — когда кричишь Spring'у: "Не любой давай, а конкретно вот этот, сука!".
  • @Primary — когда говоришь: "Вот этот — мой любимчик, его вставляй, если я не уточнил".
  • Условные аннотации (@Conditional, @Profile) — когда твоя конфигурация зависит от звёзд, фазы луны или, что более вероятно, от переменной окружения.