Autowiring deux beans implémentant la même interface - comment définir le bean par défaut sur autowire?

138

Contexte:

J'ai une application Spring 2.5 / Java / Tomcat. Il y a le haricot suivant, qui est utilisé tout au long de l'application dans de nombreux endroits

public class HibernateDeviceDao implements DeviceDao

et le haricot suivant qui est nouveau:

public class JdbcDeviceDao implements DeviceDao

Le premier bean est configuré ainsi (tous les beans du package sont inclus)

<context:component-scan base-package="com.initech.service.dao.hibernate" />

Le deuxième (nouveau) bean est configuré séparément

<bean id="jdbcDeviceDao" class="com.initech.service.dao.jdbc.JdbcDeviceDao">
    <property name="dataSource" ref="jdbcDataSource">
</bean>

Cela entraîne (bien sûr) une exception lors du démarrage du serveur:

l'exception imbriquée est org.springframework.beans.factory.NoSuchBeanDefinitionException: aucun bean unique de type [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao] est défini: le bean correspondant unique attendu mais trouvé 2: [deviceDao, jdbcDeviceDao]

d'une classe essayant de filer automatiquement le haricot comme ça

@Autowired
private DeviceDao hibernateDevicDao;

car il y a deux beans implémentant la même interface.

La question:

Est-il possible de configurer les beans pour que

1. Je ne dois pas apporter des modifications aux classes existantes, qui ont déjà HibernateDeviceDaoautowired

2. toujours en mesure d'utiliser le deuxième (nouveau) bean comme ceci:

@Autowired
@Qualifier("jdbcDeviceDao")

C'est-à-dire HibernateDeviceDaoque j'aurais besoin d'un moyen de configurer le bean en tant que bean par défaut à câbler automatiquement, permettant simultanément l'utilisation d'un the JdbcDeviceDaoen le spécifiant explicitement avec l' @Qualifierannotation.

Ce que j'ai déjà essayé:

J'ai essayé de définir la propriété

autowire-candidate="false"

dans la configuration du bean pour JdbcDeviceDao:

<bean id="jdbcDeviceDao" class="com.initech.service.dao.jdbc.JdbcDeviceDao" autowire-candidate="false">
    <property name="dataSource" ref="jdbcDataSource"/>
</bean>

parce que la documentation Spring dit que

Indique si ce bean doit ou non être pris en compte lors de la recherche de candidats correspondants pour satisfaire les exigences de câblage automatique d'un autre bean. Notez que cela n'affecte pas les références explicites par nom, qui seront résolues même si le bean spécifié n'est pas marqué comme candidat autowire. *

ce que j'ai interprété comme signifiant que je pouvais toujours JdbcDeviceDaoutiliser l' @Qualifierannotation et avoir le HibernateDeviceDaobean par défaut. Apparemment, mon interprétation n'était pas correcte, car cela entraîne le message d'erreur suivant lors du démarrage du serveur:

Dépendance non satisfaite de type [classe com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao]: attendu au moins 1 bean correspondant

venant de la classe où j'ai essayé d'autowiring le bean avec un qualificatif:

@Autowired
@Qualifier("jdbcDeviceDao")

Solution:

La suggestion de skaffman d'essayer l'annotation @Resource a fonctionné. Ainsi, la configuration a autowire-candidate définie sur false pour jdbcDeviceDao et lorsque j'utilise jdbcDeviceDao, je me réfère à lui en utilisant l'annotation @Resource (au lieu de @Qualifier):

@Resource(name = "jdbcDeviceDao")
private JdbcDeviceListItemDao jdbcDeviceDao;
Simon
la source
Si j'utilise cette interface à 100 emplacements dans le code et que je souhaite tout basculer vers une autre implémentation, je ne souhaite pas modifier les annotations de qualificatif ou de ressource à tous les endroits. Et je ne veux pas non plus changer le code de l'une ou l'autre des implémentations. Pourquoi il n'y a pas de possibilité de liaison explicite comme dans Guice?
Daniel Hári

Réponses:

134

Je vous suggère de marquer la classe Hibernate avec DAO @Primary, à savoir ( en supposant que vous utilisez @Repositorysur HibernateDeviceDao):

@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao

De cette façon, il sera sélectionné comme candidat par défaut du câble automatique, sans avoir besoin de autowire-candidatel'autre bean.

De plus, plutôt que d'utiliser @Autowired @Qualifier, je le trouve plus élégant à utiliser @Resourcepour cueillir des grains spécifiques, c'est-à-dire

@Resource(name="jdbcDeviceDao")
DeviceDao deviceDao;
skaffman
la source
J'ai oublié de mentionner dans la question que j'utilise Spring 2.5 (j'ai maintenant édité la question) donc @Primary n'est pas une option.
simon
1
@simon: Oui, c'était assez important. Essayez l' @Resourceannotation, comme je l'ai également suggéré.
skaffman
1
Merci, l'annotation de ressource a résolu le problème - maintenant la propriété autowire-candidate fonctionne comme je m'y attendais.
simon
Merci! Quelle est la différence entre spécifier le nom du haricot via @Resourceet @Qualifier, mis à part le fait que le premier est relativement plus récent que le second?
asgs
1
@asgs L'utilisation de l'annotation des ressources simplifie les choses. Au lieu d'avoir le combo autowired / qualifier, vous pouvez le marquer pour l'injection de dépendance et spécifier le nom sur une ligne. Notez que la solution de simon est redondante, l'annotation auto-câblée peut être supprimée.
The Gilbert Arenas Dagger
37

Et quoi @Primary?

Indique qu'un bean doit avoir la préférence lorsque plusieurs candidats sont qualifiés pour le câblage automatique d' une dépendance à valeur unique. S'il existe exactement un bean «primaire» parmi les candidats, il s'agira de la valeur auto-câblée. Cette annotation est sémantiquement équivalente à l' attribut de l' <bean>élément primarydans Spring XML.

@Primary
public class HibernateDeviceDao implements DeviceDao

Ou si vous souhaitez que votre version Jdbc soit utilisée par défaut:

<bean id="jdbcDeviceDao" primary="true" class="com.initech.service.dao.jdbc.JdbcDeviceDao">

@Primary est également idéal pour les tests d'intégration lorsque vous pouvez facilement remplacer le bean de production par une version stubbed en l'annotant.

Tomasz Nurkiewicz
la source
J'ai oublié de mentionner dans la question que j'utilise Spring 2.5 (j'ai maintenant édité la question) donc @Primary n'est pas une option.
simon
1
@simon: Je pense que l' primary=""attribut était disponible plus tôt. Déclarez simplement HibernateDeviceDaoen XML et excluez-le de l'analyse des composants / annotations.
Tomasz Nurkiewicz
1
Selon la documentation, il est disponible depuis la version 3.0: static.springsource.org/spring/docs/3.1.x/javadoc-api/org / ... Bon conseil de toute façon, je me souviendrai de l'annotation primaire pour le prochain projet quand je pourrai pour utiliser Spring 3.x
simon
8

Pour Spring 2.5, il n'y a pas de @Primary. Le seul moyen est d'utiliser @Qualifier.

blang
la source
2
The use of @Qualifier will solve the issue.
Explained as below example : 
public interface PersonType {} // MasterInterface

@Component(value="1.2") 
public class Person implements  PersonType { //Bean implementing the interface
@Qualifier("1.2")
    public void setPerson(PersonType person) {
        this.person = person;
    }
}

@Component(value="1.5")
public class NewPerson implements  PersonType { 
@Qualifier("1.5")
    public void setNewPerson(PersonType newPerson) {
        this.newPerson = newPerson;
    }
}

Now get the application context object in any component class :

Object obj= BeanFactoryAnnotationUtils.qualifiedBeanOfType((ctx).getAutowireCapableBeanFactory(), PersonType.class, type);//type is the qualifier id

you can the object of class of which qualifier id is passed.
Sandeep Jain
la source
0

La raison pour laquelle @Resource (name = "{votre nom de classe enfant}") fonctionne mais @Autowired ne fonctionne parfois pas est à cause de la différence de leur séquence de correspondance

Séquence correspondante de @Autowire
Type, Qualifier, Name

Séquence correspondante de @Resource
Name, Type, Qualifier

L'explication plus détaillée peut être trouvée ici:
Annotations d'injection et de ressource et autowired

Dans ce cas, une classe enfant différente héritée de la classe ou de l'interface parent confond @Autowire, car elles sont du même type; Comme @Resource utilise le nom comme première priorité correspondante, cela fonctionne.

RAYON
la source