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

Какой паттерн реализует аннотация @Qualifier?

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

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

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

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

Паттерн @Qualifier в Spring: Strategy и Factory

Аннотация @Qualifier реализует несколько паттернов одновременно, но основные — это Strategy и Factory паттерны с элементами Dependency Injection.

Основной паттерн: Strategy + DI

Strategy паттерн позволяет выбирать поведение во время выполнения программы.

// БЕЗ @Qualifier (плохо)
@Service
public class PaymentProcessor {
    @Autowired
    private PaymentGateway paymentGateway;  // Какой? Stripe, PayPal, YandexKassa?
    
    public void process(Order order) {
        paymentGateway.charge(order);  // Undefined behavior!
    }
}

// С @Qualifier (хорошо)
@Service
public class PaymentProcessor {
    @Autowired
    @Qualifier("stripePaymentGateway")  // Явно выбираем Strategy
    private PaymentGateway paymentGateway;
    
    public void process(Order order) {
        paymentGateway.charge(order);  // Точно знаем, какой gateway
    }
}

Множество реализаций одного интерфейса

Это классический случай для Strategy паттерна:

// Интерфейс Strategy
public interface PaymentGateway {
    PaymentResult charge(Order order);
}

// Различные реализации Strategy
@Service
@Qualifier("stripe")
public class StripePaymentGateway implements PaymentGateway {
    @Override
    public PaymentResult charge(Order order) {
        // Интеграция со Stripe API
        return new PaymentResult(true, "Charged via Stripe");
    }
}

@Service
@Qualifier("paypal")
public class PayPalPaymentGateway implements PaymentGateway {
    @Override
    public PaymentResult charge(Order order) {
        // Интеграция с PayPal API
        return new PaymentResult(true, "Charged via PayPal");
    }
}

@Service
@Qualifier("yandex")
public class YandexKassaPaymentGateway implements PaymentGateway {
    @Override
    public PaymentResult charge(Order order) {
        // Интеграция с Yandex Kassa API
        return new PaymentResult(true, "Charged via Yandex");
    }
}

Теперь в контроллере выбираем Strategy динамически:

@RestController
@RequestMapping("/api/payments")
public class PaymentController {
    @Autowired
    private Map<String, PaymentGateway> paymentGateways;  // Все реализации!
    
    @PostMapping("/checkout")
    public PaymentResponse checkout(@RequestBody CheckoutRequest request) {
        // Выбираем Strategy по параметру из request
        String provider = request.getPaymentProvider();  // "stripe", "paypal", "yandex"
        
        PaymentGateway gateway = paymentGateways.get(provider);
        if (gateway == null) {
            throw new IllegalArgumentException("Unknown provider: " + provider);
        }
        
        PaymentResult result = gateway.charge(request.getOrder());
        return new PaymentResponse(result);
    }
}

@Qualifier для разрешения неоднозначности

Это основная задача @Qualifier — когда Spring не может понять, какой bean внедрить.

// ПРОБЛЕМА: дво бина реализуют один интерфейс
@Component
public class OrderRepositoryMySQL implements OrderRepository {
    // ...
}

@Component
public class OrderRepositoryPostgres implements OrderRepository {
    // ...
}

// При внедрении Spring выбросит исключение:
// "required a single bean, but 2 were found"

@Service
public class OrderService {
    // ОШИБКА: какой bean выбрать?
    // @Autowired
    // private OrderRepository orderRepository;  // AMBIGUITY!
    
    // РЕШЕНИЕ 1: @Qualifier
    @Autowired
    @Qualifier("orderRepositoryPostgres")
    private OrderRepository orderRepository;
    
    // РЕШЕНИЕ 2: @Qualifier с именем переменной (должна совпадать с именем bean)
    @Autowired
    private OrderRepository orderRepositoryPostgres;
    
    // РЕШЕНИЕ 3: Использовать @Primary
    // (в классе OrderRepositoryPostgres)
}

Вариант с кастомной аннотацией (Advanced)

Можно создать свою аннотацию для более удобного использования:

// Кастомная аннотация вместо @Qualifier("value")
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PaymentProviderQualifier {
    String value();
}

// Использование
@Service
public class StripePaymentGateway implements PaymentGateway {
    @Override
    public PaymentResult charge(Order order) {
        return new PaymentResult(true, "Stripe");
    }
}

@Service
public class PaymentProcessor {
    @Autowired
    @PaymentProviderQualifier("stripe")
    private PaymentGateway stripeGateway;
}

Паттерны, реализуемые @Qualifier

1. Strategy Pattern (основной)

// Контекст
@Service
public class PaymentContext {
    private final PaymentGateway strategy;
    
    // Выбор Strategy через конструктор
    public PaymentContext(
        @Qualifier("stripe") PaymentGateway strategy
    ) {
        this.strategy = strategy;
    }
    
    public void executePayment(Order order) {
        strategy.charge(order);  // Выполняем стратегию
    }
}

2. Factory Pattern (косвенно)

// Factory создает нужную реализацию
@Component
public class PaymentGatewayFactory {
    private final Map<String, PaymentGateway> gateways;
    
    public PaymentGatewayFactory(
        @Qualifier("stripe") PaymentGateway stripe,
        @Qualifier("paypal") PaymentGateway paypal,
        @Qualifier("yandex") PaymentGateway yandex
    ) {
        this.gateways = Map.ofEntries(
            Map.entry("stripe", stripe),
            Map.entry("paypal", paypal),
            Map.entry("yandex", yandex)
        );
    }
    
    public PaymentGateway create(String provider) {
        PaymentGateway gateway = gateways.get(provider);
        if (gateway == null) {
            throw new IllegalArgumentException("Unknown provider: " + provider);
        }
        return gateway;
    }
}

3. Dependency Injection (основной механизм)

// @Qualifier — часть DI механизма Spring
// Позволяет явно указать, какую зависимость внедрить

@Service
public class OrderService {
    private final OrderRepository repository;
    private final PaymentGateway paymentGateway;
    
    // Constructor Injection с @Qualifier
    public OrderService(
        @Qualifier("orderRepositoryPostgres") OrderRepository repository,
        @Qualifier("stripe") PaymentGateway paymentGateway
    ) {
        this.repository = repository;
        this.paymentGateway = paymentGateway;
    }
}

Практические примеры

Пример 1: Мульти-логирование

// Интерфейс
public interface Logger {
    void log(String message);
}

// Реализации
@Component
@Qualifier("console")
public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}

@Component
@Qualifier("file")
public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // Запись в файл
    }
}

@Component
@Qualifier("slack")
public class SlackLogger implements Logger {
    @Override
    public void log(String message) {
        // Отправка в Slack
    }
}

// Использование
@Service
public class NotificationService {
    private final Logger errorLogger;
    private final Logger auditLogger;
    
    public NotificationService(
        @Qualifier("slack") Logger errorLogger,
        @Qualifier("file") Logger auditLogger
    ) {
        this.errorLogger = errorLogger;
        this.auditLogger = auditLogger;
    }
}

Пример 2: Кэширование с разными стратегиями

public interface CacheStrategy {
    Object get(String key);
    void put(String key, Object value);
}

@Component
@Qualifier("redis")
public class RedisCacheStrategy implements CacheStrategy {
    private final RedisTemplate template;
    // Реализация
}

@Component
@Qualifier("memory")
public class MemoryCacheStrategy implements CacheStrategy {
    private final Map<String, Object> cache = new ConcurrentHashMap<>();
    // Реализация
}

@Service
public class CachedUserRepository {
    private final UserRepository database;
    private final CacheStrategy cache;
    
    public CachedUserRepository(
        UserRepository database,
        @Qualifier("redis") CacheStrategy cache  // Или @Qualifier("memory")
    ) {
        this.database = database;
        this.cache = cache;
    }
    
    public User findById(Long id) {
        String cacheKey = "user:" + id;
        User cached = (User) cache.get(cacheKey);
        
        if (cached == null) {
            cached = database.findById(id).orElse(null);
            if (cached != null) {
                cache.put(cacheKey, cached);
            }
        }
        
        return cached;
    }
}

Пример 3: Различные профили БД

@Configuration
public class DatabaseConfig {
    @Bean("productionDataSource")
    @Qualifier("production")
    public DataSource productionDataSource() {
        return new HikariDataSource(productionConfig);
    }
    
    @Bean("testDataSource")
    @Qualifier("test")
    public DataSource testDataSource() {
        return new HikariDataSource(testConfig);
    }
}

@Service
public class UserRepository {
    private final JdbcTemplate jdbc;
    
    // Выбираем DataSource через @Qualifier
    public UserRepository(
        @Qualifier("production") DataSource dataSource
    ) {
        this.jdbc = new JdbcTemplate(dataSource);
    }
}

Сравнение подходов разрешения неоднозначности

ПодходПримерПлюсыМинусы
@Qualifier@Qualifier("stripe")Явный, гибкийVerbose
@PrimaryНа одном бинеПростойРаботает только для одного bean
@Resource@Resource(name="stripe")JSR 250 стандартСпецифичен для имени
Имя переменнойPaymentGateway stripeDI по имениХрупкий, зависит от имена
Конструктор-параметрыpublic OrderService(@Qualifier(...) ...)Явный, type-safeТребует конструктора

Best Practices

// ✅ ХОРОШО: Явное использование @Qualifier
@Service
public class PaymentService {
    private final PaymentGateway gateway;
    
    public PaymentService(
        @Qualifier("stripe") PaymentGateway gateway
    ) {
        this.gateway = gateway;
    }
}

// ❌ ПЛОХО: Зависимость от имена переменной
@Service
public class PaymentService {
    @Autowired
    private PaymentGateway stripe;  // Зависит от имени
}

// ✅ ЛУЧШЕ: Использование Map для flexibility
@Service
public class PaymentService {
    private final Map<String, PaymentGateway> gateways;
    
    public PaymentService(Map<String, PaymentGateway> gateways) {
        this.gateways = gateways;
    }
    
    public PaymentResult pay(Order order, String provider) {
        return gateways.get(provider).charge(order);
    }
}

Итоговый вывод

@Qualifier реализует:

  1. Strategy Pattern — выбор алгоритма во время выполнения
  2. Dependency Injection — явное указание зависимости
  3. Factory Pattern — создание нужной реализации (косвенно)
  4. Resolver Pattern — разрешение неоднозначности при DI

Это позволяет иметь множество реализаций одного интерфейса и выбирать нужную в runtime с полной type-safety и явностью кода.

Какой паттерн реализует аннотация @Qualifier? | PrepBro