Comprendre l'utilisation de Spring @Autowired

309

Je lis la documentation de référence de Spring 3.0.x pour comprendre l'annotation Spring Autowired:

3.9.2 @Autowired et @Inject

Je ne peux pas comprendre les exemples ci-dessous. Avons-nous besoin de faire quelque chose dans le XML pour que cela fonctionne?

EXEMPLE 1

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

EXEMPLE 2

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

Comment les deux classes peuvent-elles être câblées automatiquement implémentant la même interface et utilisant la même classe?

Exemple:

class Red implements Color
class Blue implements Color

class myMainClass{
    @Autowired 
    private Color color;

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

Quelle méthode de conception sera appelée? Comment puis-je m'assurer que la méthode de conception de la classe rouge sera appelée et non bleue?

NewQueries
la source

Réponses:

542

TL; DR

L'annotation @Autowired vous épargne le besoin de faire le câblage par vous-même dans le fichier XML (ou de toute autre manière) et trouve juste pour vous ce qui doit être injecté où, et le fait pour vous.

Explication complète

L' @Autowiredannotation vous permet d'ignorer les configurations ailleurs de quoi injecter et le fait juste pour vous. En supposant que votre package est que com.mycompany.moviesvous devez mettre cette balise dans votre XML (fichier de contexte d'application):

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

Cette balise effectuera une analyse automatique. En supposant que chaque classe qui doit devenir un bean est annotée avec une annotation correcte comme @Component(pour le bean simple) ou @Controller(pour un contrôle de servlet) ou @Repository(pour les DAOclasses) et ces classes sont quelque part sous le package com.mycompany.movies, Spring les trouvera toutes et créera un haricot pour chacun. Cela se fait en 2 scans des classes - la première fois, il recherche juste les classes qui doivent devenir un bean et mappe les injections qu'il doit faire, et lors du deuxième scan il injecte les beans. Bien sûr, vous pouvez définir vos beans dans le fichier XML plus traditionnel ou avec une classe @Configuration (ou une combinaison des trois).

L' @Autowiredannotation indique à Spring où une injection doit avoir lieu. Si vous le placez sur une méthode, setMovieFinderil comprend (par le préfixe set+ l' @Autowiredannotation) qu'un bean doit être injecté. Dans la deuxième analyse, Spring recherche un bean de type MovieFinderet s'il trouve un tel bean, il l'injecte à cette méthode. S'il trouve deux de ces beans, vous obtiendrez un Exception. Pour éviter cela Exception, vous pouvez utiliser l' @Qualifierannotation et lui indiquer lequel des deux beans injecter de la manière suivante:

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

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

Ou si vous préférez déclarer les beans dans votre XML, cela ressemblerait à ceci:

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

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

Dans la @Autowireddéclaration, vous devez également ajouter le @Qualifierpour indiquer lequel des deux grains de couleur injecter:

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

Si vous ne souhaitez pas utiliser deux annotations (le @Autowiredet @Qualifier), vous pouvez utiliser @Resourcepour combiner ces deux:

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

Le @Resource(vous pouvez lire quelques données supplémentaires à ce sujet dans le premier commentaire sur cette réponse) vous épargne l'utilisation de deux annotations et à la place vous n'en utilisez qu'une.

Je vais juste ajouter deux autres commentaires:

  1. La bonne pratique serait d'utiliser à la @Injectplace de @Autowiredcar il n'est pas spécifique à Spring et fait partie de la JSR-330norme .
  2. Une autre bonne pratique serait de mettre le @Inject/ @Autowiredsur un constructeur au lieu d'une méthode. Si vous le placez sur un constructeur, vous pouvez valider que les beans injectés ne sont pas nuls et échouent rapidement lorsque vous essayez de démarrer l'application et en éviter un NullPointerExceptionlorsque vous devez réellement utiliser le bean.

Mise à jour : Pour compléter l'image, j'ai créé une nouvelle question sur la @Configurationclasse.

Avi
la source
6
Juste pour compléter votre réponse impressionnante: '@Resource' fait partie de la norme JSR-250 et a une sémantique supplémentaire en plus de l'injection simple (comme vous l'avez dit, '@Autowired' vient de Spring; et '@Inject' fait partie de la JSR-330) :)
Ignacio Rubio
S'il MovieFinders'agit d'une interface et que nous avons un bean pour MovieFinderImpl(bean id = movieFinder), Spring l'injectera automatiquement par type ou par nom?
Jaskey
@jaskey - cela dépend si vous utilisez @Qualifier. Si vous le faites - par nom, sinon - par type. Par type ne fonctionnerait que si vous n'avez qu'un seul bean de type MovieFinderdans votre contexte. Plus de 1 entraînerait une exception.
Avi
@Avi, réponse impressionnante. Mais je ne comprends pas comment l' @Autowiredannotation fonctionne sur la prepareméthode de l' exemple 2 . Il initialise le MovieRecommendermais, techniquement, ce n'est PAS un passeur.
Karan Chadha
@KaranChadha - Le @Autowiredfonctionne également pour les constructeurs. Il trouve les dépendances requises et les injecte au constructeur.
Avi
21

Rien dans l'exemple ne dit que les "classes implémentant la même interface". MovieCatalogest un type et CustomerPreferenceDaoest un autre type. Le printemps peut facilement les distinguer.

Au printemps 2.x, le câblage des beans se faisait principalement via des ID ou des noms de beans. Ceci est toujours pris en charge par Spring 3.x mais souvent, vous aurez une instance d'un bean avec un certain type - la plupart des services sont des singletons. Créer des noms pour ceux-là est fastidieux. Spring a donc commencé à prendre en charge "autowire by type".

Les exemples montrent différentes manières que vous pouvez utiliser pour injecter des beans dans des champs, des méthodes et des constructeurs.

Le XML contient déjà toutes les informations dont Spring a besoin, car vous devez spécifier le nom de classe complet dans chaque bean. Vous devez cependant être un peu prudent avec les interfaces:

Ce câblage automatique échouera:

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

Comme Java ne conserve pas les noms des paramètres dans le code d'octet, Spring ne peut plus distinguer les deux beans. Le correctif consiste à utiliser @Qualifier:

 @Autowired
 public void prepare( @Qualifier("bean1") Interface1 bean1,
     @Qualifier("bean2")  Interface1 bean2 ) { ... }
Aaron Digulla
la source
@AaronDigulla C'était bien. Cependant, je veux savoir comment appelez-vous la fonction prepare, quels paramètres seront utilisés pour appeler cette fonction?
Nguyen Quang Anh
@NguyenQuangAnh Je n'appelle pas la méthode, Spring le fera lorsque le bean sera créé. Cela se produit exactement lorsque des @Autowiredchamps sont injectés. Spring verra alors que les paramètres sont nécessaires et il utilisera les mêmes règles que celles utilisées pour l'injection sur le terrain pour trouver les paramètres.
Aaron Digulla
5

Oui, vous pouvez configurer le fichier xml de contexte de servlet Spring pour définir vos beans (c'est-à-dire, les classes), afin qu'il puisse effectuer l'injection automatique pour vous. Cependant, notez que vous devez effectuer d'autres configurations pour que Spring soit opérationnel et la meilleure façon de le faire est de suivre un tutoriel à la base.

Une fois que vous avez probablement configuré votre Spring, vous pouvez faire ce qui suit dans votre fichier xml de contexte de servlet Spring pour que l'exemple 1 ci-dessus fonctionne (veuillez remplacer le nom de package de com.movies par le vrai nom de package et s'il s'agit d'une tierce partie , assurez-vous que le fichier jar approprié se trouve sur le chemin d'accès aux classes):

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

ou si la classe MovieFinder a un constructeur avec une valeur primitive, alors vous pourriez quelque chose comme ça,

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

ou si la classe MovieFinder a un constructeur qui attend une autre classe, alors vous pouvez faire quelque chose comme ça,

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

... où ' otherBeanRef ' est un autre bean qui a une référence à la classe attendue.

Cem Sultan
la source
4
Définir tout le câblage dans le XML manque simplement l'idée de@Autowired
Avi