Как создать два бина одинакового сервиса?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Создание Двух Бинов одинакового Сервиса в 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 + @Qualifier | Default + специфичные | Удобно для инъекции | Нужно помнить имена |
| @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.
Заключение
Для создания двух бинов одинакового типа:
- Простой случай: @Bean(name = "name1") + @Bean(name = "name2") + @Qualifier
- С default: @Primary + @Bean для остальных
- Сложная логика: Factory Bean или @Bean с инициализацией
- Разные окружения: @Profile
- Разные реализации: Отдельные классы-имплементации