← Назад к вопросам

Как создать два бина одинакового сервиса?

2.2 Middle🔥 171 комментариев
#SOLID и паттерны проектирования#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Создание Двух Бинов одинакового Сервиса в Spring

Этот вопрос касается проблемы создания нескольких bean-объектов одного типа в Spring контейнере. По умолчанию Spring использует имя класса или метода для названия бина, поэтому два бина одинакового типа вызовут конфликт.

Проблема

@Configuration
public class AppConfig {
    
    @Bean
    public UserService userService1() {
        return new UserService("config1");
    }
    
    @Bean
    public UserService userService2() {  // Ошибка!
        return new UserService("config2");
    }
}

Ошибка:

Error creating bean with name 'userService2': 
Invalid bean definition with name 'userService'. 
There is already an instance method with name 'userService' 
in class [com.example.AppConfig]

Решение 1: @Bean с явным именем (Рекомендуется)

Самый простой способ:

@Configuration
public class AppConfig {
    
    @Bean(name = "userServicePrimary")
    public UserService userService1() {
        return new UserService("primary");
    }
    
    @Bean(name = "userServiceSecondary")
    public UserService userService2() {
        return new UserService("secondary");
    }
}

Инъекция по имени:

@Service
public class OrderService {
    
    @Qualifier("userServicePrimary")
    @Autowired
    private UserService primaryUserService;
    
    @Qualifier("userServiceSecondary")
    @Autowired
    private UserService secondaryUserService;
    
    public void process() {
        primaryUserService.doWork();      // Используем первый
        secondaryUserService.doWork();    // Используем второй
    }
}

Решение 2: @Primary для Default Bean

Если один из бинов используется чаще:

@Configuration
public class AppConfig {
    
    @Primary
    @Bean(name = "userServicePrimary")
    public UserService userService1() {
        return new UserService("primary");
    }
    
    @Bean(name = "userServiceSecondary")
    public UserService userService2() {
        return new UserService("secondary");
    }
}

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

@Service
public class OrderService {
    
    @Autowired
    private UserService userService;  // Получит primary бин автоматически
    
    @Qualifier("userServiceSecondary")
    @Autowired
    private UserService secondaryUserService;
}

Решение 3: @Component + @Qualifier

Для использования аннотаций вместо @Configuration:

// Создаём два класса, наследующих UserService
@Component
@Qualifier("userServicePrimary")
public class PrimaryUserService extends UserService {
    public PrimaryUserService() {
        super("primary");
    }
}

@Component
@Qualifier("userServiceSecondary")
public class SecondaryUserService extends UserService {
    public SecondaryUserService() {
        super("secondary");
    }
}

Ор использование composition вместо наследования:

@Component("userServicePrimary")
public class PrimaryUserServiceImpl implements IUserService {
    @Override
    public void doWork() { /* primary implementation */ }
}

@Component("userServiceSecondary")
public class SecondaryUserServiceImpl implements IUserService {
    @Override
    public void doWork() { /* secondary implementation */ }
}

Решение 4: Factory Bean

Для создания нескольких экземпляров с одинаковой логикой инициализации:

@Configuration
public class AppConfig {
    
    @Bean(name = "userServicePrimary")
    public UserService createPrimaryUserService() {
        UserService service = new UserService("primary");
        service.setConfig(loadConfig("primary.properties"));
        service.init();
        return service;
    }
    
    @Bean(name = "userServiceSecondary")
    public UserService createSecondaryUserService() {
        UserService service = new UserService("secondary");
        service.setConfig(loadConfig("secondary.properties"));
        service.init();
        return service;
    }
    
    private Config loadConfig(String filename) {
        // Логика загрузки конфигурации
        return new Config(filename);
    }
}

Решение 5: List/Map Injection

Инъекция всех бинов сразу:

@Configuration
public class AppConfig {
    
    @Bean(name = "userServicePrimary")
    public UserService userService1() {
        return new UserService("primary");
    }
    
    @Bean(name = "userServiceSecondary")
    public UserService userService2() {
        return new UserService("secondary");
    }
}

@Service
public class UserServiceRegistry {
    
    // Получить все UserService бины в виде List
    @Autowired
    private List<UserService> userServices;
    
    // Получить все в виде Map (ключ = имя бина)
    @Autowired
    private Map<String, UserService> userServicesMap;
    
    public void processAll() {
        userServices.forEach(service -> service.doWork());
        
        userServicesMap.get("userServicePrimary").doWork();
        userServicesMap.get("userServiceSecondary").doWork();
    }
}

Решение 6: Profile-based Beans

Разные бины для разных окружений:

@Configuration
public class AppConfig {
    
    @Bean
    @Profile("dev")
    public UserService userService() {
        return new UserService("dev-config");
    }
    
    @Bean
    @Profile("prod")
    public UserService userService() {
        return new UserService("prod-config");
    }
}

Запуск:

java -jar app.jar --spring.profiles.active=prod

Реальный Пример: Несколько Database Sources

@Configuration
public class DataSourceConfig {
    
    @Bean(name = "primaryDataSource")
    @Primary
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create()
                .driverClassName("org.postgresql.Driver")
                .url("jdbc:postgresql://localhost:5432/primary_db")
                .username("user1")
                .password("pass1")
                .build();
    }
    
    @Bean(name = "secondaryDataSource")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create()
                .driverClassName("org.postgresql.Driver")
                .url("jdbc:postgresql://localhost:5432/secondary_db")
                .username("user2")
                .password("pass2")
                .build();
    }
    
    @Bean(name = "primaryJdbcTemplate")
    @Primary
    public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource ds) {
        return new JdbcTemplate(ds);
    }
    
    @Bean(name = "secondaryJdbcTemplate")
    public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource ds) {
        return new JdbcTemplate(ds);
    }
}

@Repository
public class UserRepository {
    
    @Qualifier("primaryJdbcTemplate")
    @Autowired
    private JdbcTemplate primaryDb;
    
    @Qualifier("secondaryJdbcTemplate")
    @Autowired
    private JdbcTemplate secondaryDb;
    
    public User findFromPrimary(Long id) {
        return primaryDb.queryForObject(
                "SELECT * FROM users WHERE id = ?",
                new Object[]{id},
                User.class
        );
    }
    
    public User findFromSecondary(Long id) {
        return secondaryDb.queryForObject(
                "SELECT * FROM users WHERE id = ?",
                new Object[]{id},
                User.class
        );
    }
}

Сравнение Подходов

МетодИспользованиеПлюсыМинусы
@Bean(name)Несколько конфиговЯвно, контролируемоМного кода
@Primary + @QualifierDefault + специфичныеУдобно для инъекцииНужно помнить имена
@Component наследникиРазные реализацииКлассы выражают различияНарушает DRY
Factory BeanСложная инициализацияЛогика в одном местеМного кода
List/Map InjectionОбработка всехГибко, динамичноНе удобно для 2 бинов
@ProfileРазные окруженияЧистое разделениеДля окружений, не типов

Best Practices

1. Используйте осмысленные имена:

// ✅ Хорошо
@Bean(name = "primaryUserService")
public UserService userServiceForWriteOperations()

@Bean(name = "cacheUserService")
public UserService cachedUserService()

2. Документируйте цель каждого бина:

/**
 * Primary user service for write operations
 * Used in OrderService and PaymentService
 */
@Bean(name = "primaryUserService")
@Primary
public UserService userService1()

3. Используйте @Primary для default:

@Primary  // Инъекция без @Qualifier использует этот
@Bean
public UserService mainUserService()

4. Избегайте множественных бинов, если возможно: Лучше использовать strategy pattern или composition.

Заключение

Для создания двух бинов одинакового типа:

  1. Простой случай: @Bean(name = "name1") + @Bean(name = "name2") + @Qualifier
  2. С default: @Primary + @Bean для остальных
  3. Сложная логика: Factory Bean или @Bean с инициализацией
  4. Разные окружения: @Profile
  5. Разные реализации: Отдельные классы-имплементации
Как создать два бина одинакового сервиса? | PrepBro