Zrozumienie Spring @Autowired use

309

Czytam dokumentację źródłową wiosną 3.0.x, aby zrozumieć adnotację Spring Autowired:

3.9.2 @Autowired i @Inject

Nie jestem w stanie zrozumieć poniższych przykładów. Czy musimy coś zrobić w XML, aby to działało?

PRZYKŁAD 1

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

PRZYKŁAD 2

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
                    CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

Jak można automatycznie przypisać te dwie klasy, implementując ten sam interfejs i używając tej samej klasy?

Przykład:

class Red implements Color
class Blue implements Color

class myMainClass{
    @Autowired 
    private Color color;

    draw(){
        color.design(); 
    } 
}

Jaka metoda projektowania zostanie wywołana? Jak upewnić się, że metoda projektowania klasy Red będzie wywoływana, a nie niebieska?

NewQueries
źródło

Odpowiedzi:

542

TL; DR

Adnotacja @Autowired oszczędza potrzeby samodzielnego wykonywania okablowania w pliku XML (lub w jakikolwiek inny sposób) i po prostu znajduje dla Ciebie to, co należy wstrzyknąć, i robi to za Ciebie.

Pełne wyjaśnienie

@AutowiredAdnotacji pozwala pominąć konfiguracje gdzie indziej co do wstrzyknięcia i po prostu robi to za ciebie. Zakładając, że Twój pakiet wymaga com.mycompany.moviesumieszczenia tego znacznika w pliku XML (plik kontekstowy aplikacji):

<context:component-scan base-package="com.mycompany.movies" />

Ten tag wykona automatyczne skanowanie. Zakładając, że każda klasa, która musi stać się fasolą, jest opatrzona adnotacją z poprawną adnotacją, taką jak @Component(dla prostej fasoli) lub @Controller(dla kontroli serwletu) lub @Repository(dla DAOklas) i te klasy znajdują się gdzieś w pakiecie com.mycompany.movies, Spring znajdzie je wszystkie i utworzy fasola dla każdego. Odbywa się to w 2 skanach klas - za pierwszym razem po prostu wyszukuje klasy, które muszą stać się fasolą, i mapuje zastrzyki, które musi wykonać, a przy drugim skanie wstrzykuje ziarna. Oczywiście możesz zdefiniować swoje fasole w bardziej tradycyjnym pliku XML lub za pomocą klasy @Configuration (lub dowolnej kombinacji tych trzech).

@AutowiredAdnotacja mówi Wiosna gdzie zastrzyk musi nastąpić. Jeśli zastosujesz metodę, setMovieFinderktóra zrozumie (przedrostek set+ @Autowiredadnotacja), że fasola musi zostać wstrzyknięta. W drugim skanie Spring szuka fasoli typu MovieFinder, a jeśli ją znajdzie, wstrzykuje ją do tej metody. Jeśli znajdzie dwie takie fasole, dostaniesz Exception. Aby tego uniknąć Exception, możesz użyć @Qualifieradnotacji i powiedzieć, która z dwóch ziaren ma wstrzyknąć w następujący sposób:

@Qualifier("redBean")
class Red implements Color {
   // Class code here
}

@Qualifier("blueBean")
class Blue implements Color {
   // Class code here
}

Lub jeśli wolisz zadeklarować ziarna w pliku XML, wyglądałoby to mniej więcej tak:

<bean id="redBean" class="com.mycompany.movies.Red"/>

<bean id="blueBean" class="com.mycompany.movies.Blue"/>

W @Autowireddeklaracji należy również dodać @Qualifierinformację, który z dwóch kolorów fasoli do wstrzyknięcia:

@Autowired
@Qualifier("redBean")
public void setColor(Color color) {
  this.color = color;
}

Jeśli nie chcesz używać dwóch adnotacji ( @Autowiredi @Qualifier), możesz użyć @Resourcetych dwóch:

@Resource(name="redBean")
public void setColor(Color color) {
  this.color = color;
}

( @Resource(Możesz przeczytać dodatkowe dane na ten temat w pierwszym komentarzu do tej odpowiedzi) oszczędza ci używania dwóch adnotacji, a zamiast tego używasz tylko jednej.

Dodam jeszcze dwa komentarze:

  1. Dobrą praktyką byłoby użycie @Injectzamiast tego, @Autowiredponieważ nie jest ona specyficzna dla wiosny i jest częścią JSR-330standardu .
  2. Inną dobrą praktyką byłoby umieszczenie @Inject/ @Autowiredna konstruktorze zamiast metody. Jeśli umieścisz go w konstruktorze, możesz sprawdzić, czy wstrzykiwane ziarna nie mają wartości zerowej i szybko zawodzą podczas próby uruchomienia aplikacji i unikają NullPointerExceptionsytuacji, w której trzeba użyć fasoli.

Aktualizacja : Aby uzupełnić obraz, stworzyłem nowe pytanie dotyczące @Configurationklasy.

Avi
źródło
6
Wystarczy, że wypełnisz swoją niesamowitą odpowiedź: „@Resource” jest częścią standardu JSR-250 i ma dodatkową semantykę ponad prostym zastrzykiem (jak już powiedziałeś „@Autowired” pochodzi ze Springa, a „@Inject” jest częścią JSR-330) :)
Ignacio Rubio
Jeśli MovieFinderjest to interfejs i mamy fasolę dla MovieFinderImpl(identyfikator fasoli = movieFinder), Spring automatycznie wstrzyknie ją według typu lub nazwy?
Jaskey,
@jaskey - zależy od tego, czy używasz @Qualifier. Jeśli to zrobisz - według nazwy, jeśli nie - według typu. Według typu działa tylko wtedy, gdy MovieFinderw kontekście występuje tylko jedna fasola typu . Więcej niż 1 prowadziłoby do wyjątku.
Avi
@Avi, Niesamowita odpowiedź. Ale nie rozumiem, jak @Autowiredadnotacja działa w preparemetodzie z przykładu 2 . Inicjuje, MovieRecommenderale technicznie NIE jest to seter.
Karan Chadha
@KaranChadha - @AutowiredDziała również w przypadku konstruktorów. Znajduje wymagane zależności i wstrzykuje je do konstruktora.
Avi
21

Nic w tym przykładzie nie mówi, że „klasy implementujące ten sam interfejs”. MovieCatalogjest typem i CustomerPreferenceDaojest innym typem. Wiosna może łatwo ich rozdzielić.

Wiosną 2.x okablowanie fasoli odbywało się głównie za pomocą identyfikatorów lub nazw fasoli. Wciąż jest obsługiwany przez Spring 3.x, ale często będziesz mieć jedną instancję fasoli określonego typu - większość usług to singletony. Tworzenie imion jest żmudne. Więc Spring zaczął obsługiwać „autowire według typu”.

Przykłady pokazują różne sposoby wstrzykiwania fasoli do pól, metod i konstruktorów.

XML zawiera już wszystkie informacje, których potrzebuje Spring, ponieważ musisz podać w pełni kwalifikowaną nazwę klasy w każdej fasoli. Należy jednak zachować ostrożność przy korzystaniu z interfejsów:

To automatyczne okablowanie zakończy się niepowodzeniem:

 @Autowired
 public void prepare( Interface1 bean1, Interface1 bean2 ) { ... }

Ponieważ Java nie przechowuje nazw parametrów w kodzie bajtów, Spring nie może już rozróżniać dwóch ziaren. Poprawka polega na użyciu @Qualifier:

 @Autowired
 public void prepare( @Qualifier("bean1") Interface1 bean1,
     @Qualifier("bean2")  Interface1 bean2 ) { ... }
Aaron Digulla
źródło
@AaronDigulla To było miłe. Chciałbym jednak wiedzieć, jak wywołać funkcję prepare, które parametry zostaną użyte do wywołania tej funkcji?
Nguyen Quang Anh,
@NguyenQuangAnh Nie wywołuję metody, Spring zrobi to, gdy zostanie utworzona fasola. Dzieje się tak dokładnie po @Autowiredwstrzyknięciu pól. Wiosna wtedy zobaczy, że parametry są potrzebne i użyje tych samych reguł, co przy wstrzykiwaniu pola, aby znaleźć parametry.
Aaron Digulla,
5

Tak, możesz skonfigurować plik XML kontekstu serwletu Spring, aby zdefiniować komponenty bean (tj. Klasy), aby mógł wykonać dla Ciebie automatyczny zastrzyk. Pamiętaj jednak, że musisz wykonać inne konfiguracje, aby uruchomić Springa, a najlepszym sposobem na to jest skorzystanie z samouczka.

Po prawdopodobnie skonfigurowaniu Springa możesz wykonać następujące czynności w pliku XML kontekstu serwletu Spring dla przykładu 1 powyżej (aby zastąpić nazwę pakietu com.movies prawdziwą nazwą pakietu i jeśli jest to firma zewnętrzna klasy, a następnie upewnij się, że odpowiedni plik jar znajduje się w ścieżce klasy):

<beans:bean id="movieFinder" class="com.movies.MovieFinder" />

lub jeśli klasa MovieFinder ma konstruktor z prymitywną wartością, możesz coś takiego,

<beans:bean id="movieFinder" class="com.movies.MovieFinder" >
    <beans:constructor-arg value="100" />
</beans:bean>

lub jeśli klasa MovieFinder ma konstruktor oczekujący innej klasy, możesz zrobić coś takiego,

<beans:bean id="movieFinder" class="com.movies.MovieFinder" >
    <beans:constructor-arg ref="otherBeanRef" />
</beans:bean>

... gdzie „ otherBeanRef ” to kolejny komponent bean, który zawiera odwołanie do oczekiwanej klasy.

Cem Sultan
źródło
4
Zdefiniowanie całego okablowania w XML po prostu nie spełnia całego pomysłu@Autowired
Avi