@Autowired vs @Qualifier vs @Resource – jak Spring wybiera bean?

You are currently viewing @Autowired vs @Qualifier vs @Resource – jak Spring wybiera bean?

Wstrzykiwanie zależności w Springu

Pracujesz ze Springiem, masz kilka implementacji jednego interfejsu i nagle dostajesz NoUniqueBeanDefinitionException. To jeden z częstszych problemów, na który trafiają programiści – szczególnie gdy projekt zaczyna rosnąć i pojawiają się różne warianty tych samych serwisów.

Spring oferuje kilka mechanizmów wstrzykiwania zależności, które pozwalają wskazać konkretny bean. Trzy najczęściej używane to @Autowired, @Qualifier i @Resource. Każda z nich działa trochę inaczej i ma swoje zastosowania.

W tym wpisie z serii „Szybki Strzał” pokażę Ci, czym się różnią, jak Spring podejmuje decyzję o wyborze beana i którą adnotację wybrać w konkretnej sytuacji.

@Autowired – Jak działa domyślny mechanizm?

@Autowired to podstawowy mechanizm wstrzykiwania zależności w Springu. W najprostszym przypadku Spring szuka beana po typie. Jeśli w kontekście aplikacji istnieje dokładnie jeden bean danego typu, zostanie on wstrzyknięty bez żadnych dodatkowych konfiguracji.

@Service
public class OrderService {

    private final PaymentService paymentService;

    // od Springa 4.3 (Spring Boot 1.4+) adnotacja nie jest wymagana przy jednym konstruktorze
    @Autowired
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

Jeżeli PaymentService ma jedną implementację, wszystko działa bez problemu.

Problem pojawia się, gdy masz kilka implementacji tego samego interfejsu:

@Service
public class CardPaymentService implements PaymentService { }

@Service
public class BlikPaymentService implements PaymentService { }

Spring nadal próbuje wstrzyknąć zależność po typie, ale ponieważ pasują dwa beany, kończy się to wyjątkiem:

NoUniqueBeanDefinitionException: No qualifying bean of type 'PaymentService’ available: expected single matching bean but found 2: cardPaymentService, blikPaymentService

Warto wiedzieć, że jeśli Spring nie może jednoznacznie wybrać beana po typie, próbuje jeszcze dopasować nazwę pola lub parametru konstruktora do nazwy beana. Jeśli nazwiesz pole tak samo jak bean, wstrzyknięcie zadziała.

@Service
public class OrderService {

    private final PaymentService paymentService;

    @Autowired // Opcjonalna adnotacja
    public OrderService(PaymentService cardPaymentService) {
        this.paymentService = cardPaymentService;
    }
}

To jednak niezbyt dobre rozwiązanie. Wystarczy drobny refaktor nazwy pola i aplikacja przestaje działać.

Innym podejściem jest oznaczenie jednego beana jako domyślnego za pomocą @Primary:

@Service
@Primary
public class CardPaymentService implements PaymentService { }

@Service
public class BlikPaymentService implements PaymentService { }

Dzięki temu @Autowired bez dodatkowych adnotacji wstrzyknie CardPaymentService, bo jest oznaczony jako główny kandydat. To wygodne rozwiązanie, gdy masz jedną „standardową” implementację, a pozostałe są używane tylko w specyficznych przypadkach.

Jeśli jednak potrzebujesz w różnych miejscach różnych implementacji, lepiej sprawdzi się jawne wskazanie beana za pomocą @Qualifier.

@Qualifier – wskazanie konkretnego beana

@Qualifier rozwiązuje problem wielu beanów tego samego typu. Dzięki niemu możesz wprost powiedzieć Springowi, którego beana chcesz wstrzyknąć.

@Service
public class OrderService {

    private final PaymentService paymentService;

    public OrderService(@Qualifier("blikPaymentService") PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

Domyślna nazwa beana to nazwa klasy pisana camelCase’em – małą literą na początku czyli przykładowo dla klasy BlikPaymentService nazwa beana to blikPaymentService.

Natomiast jeśli standardowa nazwa beana Ci nie odpowiada, możesz nadać własną nazwę:

@Service("blik")
public class BlikPaymentService implements PaymentService { }

I wtedy użyć jej w @Qualifier:

...  
  public OrderService(@Qualifier("blik") PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

Takie podejście jest jawne i odporne na zmiany nazw pól. Od razu widać w kodzie, która implementacja jest używana i nie ma tu żadnej zgadywanki po stronie Springa. Dzięki temu refaktoryzacja jest bezpieczniejsza, a osoba czytająca kod nie musi analizować konfiguracji ani nazw beanów, żeby zrozumieć, co faktycznie zostanie wstrzyknięte.

@Resource – Wstrzykiwanie po nazwie

Teraz przechodzimy do @Resource. Ta adnotacja nie pochodzi bezpośrednio ze Springa, ale ze standardu Java – JSR-250, który obecnie jest częścią Jakarta EE (jakarta.annotation.Resource). Spring ją wspiera, ale działa ona według innej logiki niż @Autowired.

Podczas gdy @Autowired w pierwszej kolejności bazuje na typie, @Resource stawia na nazwę beana (byName). I to jest kluczowa różnica, o której łatwo zapomnieć, jeśli wcześniej pracowało się głównie z mechanizmami Springa.

W praktyce Spring rozstrzyga @Resource w bardzo prosty sposób:

  1. Najpierw próbuje dopasować beana po nazwie podanej w parametrze name.
  2. Jeśli parametr name nie został podany, próbuje dopasować beana po nazwie pola lub settera.
  3. Dopiero na końcu, gdy nie uda się znaleźć pasującej nazwy, sprawdza typ – podobnie jak przy @Autowired.
@Service
public class OrderService {

    @Resource(name = "cardPaymentService")
    private PaymentService paymentService;
}

Jeśli nie podasz atrybutu name, @Resource użyje nazwy pola jako nazwy beana:

@Resource
private PaymentService cardPaymentService; // Szuka beana o nazwie "cardPaymentService"

Warto pamiętać, że @Resource działa tylko na polach i setterach – nie użyjesz jej na konstruktorze. Jeśli w projekcie stawiasz na wstrzykiwanie przez konstruktor (co dziś jest standardem), lepiej zostać przy @Autowired w połączeniu z @Qualifier.

Którą adnotację wybrać?

Skoro mamy kilka opcji, pojawia się naturalne pytanie: czego używać w codziennej pracy?

@Autowired sprawdzi się wtedy, gdy w kontekście aplikacji masz tylko jeden bean danego typu. Spring znajdzie go automatycznie i wstrzyknie bez żadnej dodatkowej konfiguracji. Co więcej, od Springa 4.3 (Spring Boot 1.4+) przy jednym konstruktorze adnotacja jest w ogóle opcjonalna – Spring Boot domyślnie zakłada wstrzykiwanie zależności.

@Autowired w połączeniu z @Qualifier to najczęstsze podejście, gdy istnieje kilka implementacji tego samego interfejsu. Jawnie wskazujesz, który bean ma zostać użyty, a kod pozostaje czytelny i odporny na refaktoryzacje nazw.

@Primary dobrze działa w sytuacji, gdy masz jedną domyślną implementację, a pozostałe są wykorzystywane tylko w konkretnych miejscach. Dzięki temu nie musisz powtarzać @Qualifier przy każdym wstrzyknięciu.

@Resource ma sens w bardziej specyficznych przypadkach – gdy świadomie chcesz oprzeć się na standardach Jakarta EE albo pracujesz z kodem legacy, w którym wstrzykiwanie po nazwach jest już mocno zakorzenione. W nowych projektach Spring Bootowych rzadko jest to pierwszy wybór.

Niezależnie od użytej adnotacji, warto trzymać się wstrzykiwania przez konstruktor. Ułatwia testowanie, wymusza kompletność zależności i jest dziś standardem w aplikacjach Spring Boot.

Podsumowanie

Gdy Spring napotka wiele beanów tego samego typu, musisz mu pomóc w wyborze. @Autowired działa po typie i wymaga @Qualifier przy wielu kandydatach. @Resource domyślnie szuka po nazwie, co czasem bywa wygodniejsze.

W większości projektów sprawdzi się kombinacja @Autowired + @Qualifier – jest czytelna i dobrze znana w społeczności Spring. Jeśli masz pytania lub chcesz podzielić się swoimi doświadczeniami z wstrzykiwaniem zależności, daj znać w komentarzu!

Subskrybuj
Powiadom o
guest
0 komentarzy
Najstarsze
Najnowsze Najwięcej głosów
Opinie w linii
Zobacz wszystkie komentarze