Autowiring zwei Beans, die dieselbe Schnittstelle implementieren - wie wird die Standard-Bean auf Autowire gesetzt?

138

Hintergrund:

Ich habe eine Spring 2.5 / Java / Tomcat-Anwendung. Es gibt die folgende Bohne, die an vielen Stellen in der gesamten Anwendung verwendet wird

public class HibernateDeviceDao implements DeviceDao

und die folgende Bohne, die neu ist:

public class JdbcDeviceDao implements DeviceDao

Die erste Bean ist so konfiguriert (alle Beans im Paket sind enthalten).

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

Die zweite (neue) Bean wird separat konfiguriert

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

Dies führt (natürlich) zu einer Ausnahme beim Starten des Servers:

verschachtelte Ausnahme ist org.springframework.beans.factory.NoSuchBeanDefinitionException: Es ist keine eindeutige Bean vom Typ [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao] definiert: erwartete einzelne übereinstimmende Bean, aber gefunden 2: [deviceDao, jdbcDeviceDao]

von einer Klasse, die versucht, die Bohne so automatisch zu verdrahten

@Autowired
private DeviceDao hibernateDevicDao;

weil es zwei Beans gibt, die dieselbe Schnittstelle implementieren.

Die Frage:

Ist es möglich, die Beans so zu konfigurieren?

1. Ich muss keine Änderungen an vorhandenen Klassen vornehmen, die bereits über eine HibernateDeviceDaoautomatische Verdrahtung verfügen

2. die zweite (neue) Bohne immer noch so verwenden können:

@Autowired
@Qualifier("jdbcDeviceDao")

Das heißt, ich würde eine Möglichkeit benötigen, die HibernateDeviceDaoBean als Standard-Bean für die automatische Verdrahtung zu konfigurieren und gleichzeitig die Verwendung eines zu ermöglichen, JdbcDeviceDaowenn dies explizit mit der @QualifierAnmerkung angegeben wird.

Was ich schon versucht habe:

Ich habe versucht, die Eigenschaft festzulegen

autowire-candidate="false"

in der Bean-Konfiguration für JdbcDeviceDao:

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

weil die Spring-Dokumentation das sagt

Gibt an, ob diese Bean berücksichtigt werden soll, wenn nach passenden Kandidaten gesucht wird, um die Autowiring-Anforderungen einer anderen Bean zu erfüllen. Beachten Sie, dass dies keine Auswirkungen auf explizite Verweise nach Namen hat, die auch dann aufgelöst werden, wenn die angegebene Bean nicht als Autowire-Kandidat markiert ist. *

was ich so interpretierte, dass ich JdbcDeviceDaomit der @QualifierAnnotation immer noch automatisch verdrahten und die HibernateDeviceDaoals Standard-Bean haben konnte. Anscheinend war meine Interpretation jedoch nicht korrekt, da dies beim Starten des Servers zu der folgenden Fehlermeldung führt:

Unbefriedigte Abhängigkeit vom Typ [Klasse com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao]: mindestens 1 übereinstimmende Bean erwartet

Ich komme aus der Klasse, in der ich versucht habe, die Bohne mit einem Qualifier automatisch zu verdrahten:

@Autowired
@Qualifier("jdbcDeviceDao")

Lösung:

Der Vorschlag von skaffman, die @ Resource-Annotation auszuprobieren, hat funktioniert. In der Konfiguration ist der Autowire-Kandidat für jdbcDeviceDao auf false gesetzt, und wenn ich jdbcDeviceDao verwende, verweise ich mit der Annotation @Resource (anstelle von @Qualifier) ​​darauf:

@Resource(name = "jdbcDeviceDao")
private JdbcDeviceListItemDao jdbcDeviceDao;
Simon
quelle
Wenn ich diese Schnittstelle an 100 Stellen im Code verwende und alle zu einer anderen Implementierung wechseln möchte, möchte ich nicht an allen Stellen Qualifizierer- oder Ressourcenanmerkungen ändern. Und ich möchte auch den Code beider Implementierungen nicht ändern. Warum gibt es keine expliziten Bindungsmöglichkeiten wie in Guice?
Daniel Hári

Antworten:

134

Ich würde vorschlagen , die Hibernate DAO Klasse mit Markierung @Primary, dh (vorausgesetzt , Sie verwenden @Repositoryauf HibernateDeviceDao):

@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao

Auf diese Weise wird es als Standard-Autowire-Kandidat ausgewählt, ohne dass dies für autowire-candidatedie andere Bean erforderlich ist .

Anstatt es zu verwenden @Autowired @Qualifier, finde ich es auch eleganter, @Resourcebestimmte Bohnen zu pflücken, d. H.

@Resource(name="jdbcDeviceDao")
DeviceDao deviceDao;
Skaffman
quelle
Ich habe vergessen, in der Frage zu erwähnen, dass ich Spring 2.5 verwende (ich habe die Frage jetzt bearbeitet), sodass @Primary keine Option ist.
Simon
1
@ Simon: Ja, das war ziemlich wichtig. Probieren Sie die @ResourceAnmerkung aus, wie ich auch vorgeschlagen habe.
Skaffman
1
Dank der Ressourcenanmerkung wurde das Problem behoben. Jetzt funktioniert die Eigenschaft "Autowire-Candidate" wie erwartet.
Simon
Vielen Dank! Was ist der Unterschied zwischen der Angabe des Bean-Namens über @Resourceund @Qualifier, abgesehen von der Tatsache, dass der erstere relativ neuer ist als der letztere?
Asgs
1
@asgs Die Verwendung von Ressourcenanmerkungen vereinfacht die Dinge. Anstatt die Kombination aus Autowired und Qualifier zu verwenden, können Sie sie für die Abhängigkeitsinjektion markieren und den Namen in einer Zeile angeben. Beachten Sie, dass die Lösung von simon redundant ist und die automatisch verdrahtete Anmerkung entfernt werden kann.
Der Gilbert Arenas Dolch
37

Was ist mit @Primary?

Gibt an, dass eine Bean bevorzugt werden sollte, wenn mehrere Kandidaten für die automatische Verdrahtung einer einwertigen Abhängigkeit qualifiziert sind. Wenn genau eine 'primäre' Bean unter den Kandidaten vorhanden ist, ist dies der automatisch verdrahtete Wert. Diese Annotation entspricht semantisch <bean>dem primaryAttribut des Elements in Spring XML.

@Primary
public class HibernateDeviceDao implements DeviceDao

Oder wenn Sie möchten, dass Ihre Jdbc-Version standardmäßig verwendet wird:

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

@Primary eignet sich auch hervorragend für Integrationstests, wenn Sie die Produktions-Bean durch Anmerkungen einfach durch eine gestubbte Version ersetzen können.

Tomasz Nurkiewicz
quelle
Ich habe vergessen, in der Frage zu erwähnen, dass ich Spring 2.5 verwende (ich habe die Frage jetzt bearbeitet), sodass @Primary keine Option ist.
Simon
1
@ Simon: Ich glaube, primary=""Attribut war früher verfügbar. HibernateDeviceDaoDeklarieren Sie einfach in XML und schließen Sie es vom Scannen von Komponenten / Anmerkungen aus.
Tomasz Nurkiewicz
1
Laut Dokumentation ist es seit 3.0 verfügbar: static.springsource.org/spring/docs/3.1.x/javadoc-api/org/… Guter Tipp, ich werde mich an die primäre Annotation für das nächste Projekt erinnern, wenn ich dazu in der Lage bin Spring 3.x
Simon
8

Für Spring 2.5 gibt es keine @Primary. Der einzige Weg ist zu verwenden @Qualifier.

blang
quelle
1
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
quelle
0

Der Grund, warum @Resource (name = "{Name Ihrer untergeordneten Klasse}") funktioniert, @Autowired jedoch manchmal nicht funktioniert, liegt in der unterschiedlichen Übereinstimmungssequenz

Übereinstimmende Reihenfolge von @Autowire-
Typ, Qualifizierer, Name

Übereinstimmende Reihenfolge von @ Ressourcenname
, Typ, Qualifizierer

Die ausführlichere Erklärung finden Sie hier:
Inject and Resource- und Autowired-Annotationen

In diesem Fall verwirrt eine andere untergeordnete Klasse, die von der übergeordneten Klasse oder Schnittstelle geerbt wurde, @Autowire, da sie vom selben Typ sind. Da @Resource Name als erste übereinstimmende Priorität verwendet, funktioniert dies.

STRAHL
quelle