Autowiring di due bean che implementano la stessa interfaccia: come impostare il bean predefinito su autowire?


138

Sfondo:

Ho un'applicazione Spring 2.5 / Java / Tomcat. C'è il seguente bean, che viene utilizzato in tutta l'applicazione in molti punti

public class HibernateDeviceDao implements DeviceDao

e il seguente bean che è nuovo:

public class JdbcDeviceDao implements DeviceDao

Il primo bean è configurato in questo modo (tutti i bean nel pacchetto sono inclusi)

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

Il secondo (nuovo) bean è configurato separatamente

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

Ciò comporta (ovviamente) un'eccezione all'avvio del server:

l'eccezione nidificata è org.springframework.beans.factory.NoSuchBeanDefinitionException: nessun bean univoco di tipo [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao] è definito: bean di corrispondenza singolo previsto ma trovato 2: [deviceDao, jdbcDeviceDao]

da una classe che cerca di autorizzare il bean in questo modo

@Autowired
private DeviceDao hibernateDevicDao;

perché ci sono due bean che implementano la stessa interfaccia.

La domanda:

È possibile configurare i bean in modo che

1. Non devo apportare modifiche alle classi esistenti, che hanno già l' HibernateDeviceDaoautowired

2. essere ancora in grado di usare il secondo (nuovo) bean in questo modo:

@Autowired
@Qualifier("jdbcDeviceDao")

Cioè avrei bisogno di un modo per configurare il HibernateDeviceDaobean come bean predefinito da autowired, consentendo contemporaneamente l'uso di un the JdbcDeviceDaoquando lo specifica esplicitamente con l' @Qualifierannotazione.

Quello che ho già provato:

Ho provato a impostare la proprietà

autowire-candidate="false"

nella configurazione bean per JdbcDeviceDao:

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

perché lo dice la documentazione di primavera

Indica se questo bean deve essere preso in considerazione quando si cercano candidati corrispondenti per soddisfare i requisiti di autowiring di un altro bean. Si noti che ciò non influisce sui riferimenti espliciti per nome, che verranno risolti anche se il bean specificato non è contrassegnato come candidato autowire. *

che ho interpretato nel senso che potrei ancora autowire JdbcDeviceDaousando l' @Qualifierannotazione e avere HibernateDeviceDaocome bean predefinito. Apparentemente la mia interpretazione non è stata corretta, tuttavia, poiché si verifica il seguente messaggio di errore all'avvio del server:

Dipendenza insoddisfatta del tipo [class com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao]: previsto almeno 1 bean corrispondente

proveniente dalla classe in cui ho provato ad autorizzare il bean con un qualificatore:

@Autowired
@Qualifier("jdbcDeviceDao")

Soluzione:

Il suggerimento di skaffman di provare l'annotazione @Resource ha funzionato. Quindi la configurazione ha autowire-candidate impostato su false per jdbcDeviceDao e quando uso jdbcDeviceDao mi riferisco ad esso usando l'annotazione @Resource (invece di @Qualifier):

@Resource(name = "jdbcDeviceDao")
private JdbcDeviceListItemDao jdbcDeviceDao;

Se uso questa interfaccia a 100 posti nel codice e desidero passare a un'altra implementazione, non desidero modificare il qualificatore o le annotazioni delle risorse in tutti i punti. E anche io non voglio cambiare il codice di nessuna delle due implementazioni. Perché non ci sono possibilità esplicite di rilegatura come in Guice?
Daniel Hári,

Risposte:


134

Io suggerirei che segna la classe Hibernate DAO con @Primary, per esempio (supponendo che hai utilizzato @Repositorysu HibernateDeviceDao):

@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao

In questo modo verrà selezionato come candididate autowire predefinito, senza necessità autowire-candidatesull'altro bean.

Inoltre, piuttosto che usarlo @Autowired @Qualifier, lo trovo più elegante da usare @Resourceper raccogliere fagioli specifici, ad es

@Resource(name="jdbcDeviceDao")
DeviceDao deviceDao;

Ho dimenticato di menzionare nella domanda che sto usando Spring 2.5 (ora ho modificato la domanda) quindi @Primary non è un'opzione.
simon

1
@simon: Sì, era piuttosto importante. Prova l' @Resourceannotazione, come ho anche suggerito.
Skaffman,

1
Grazie, l'annotazione delle risorse ha risolto il problema: ora la proprietà candidata per l'autowire funziona come mi aspettavo.
simon

Grazie! Qual è la differenza tra specificare il nome del bean tramite @Resourcee @Qualifier, a parte il fatto che il primo è relativamente più recente del secondo?
chiede il

1
@asgs L'uso dell'annotazione delle risorse semplifica le cose. Invece di avere il combo autowired / qualificatore, è possibile contrassegnarlo per l'iniezione di dipendenza e specificare il nome in una riga. Nota che la soluzione di simon è ridondante, l'annotazione autowired può essere rimossa.
The Gilbert Arenas Dagger

37

Che dire @Primary?

Indica che è necessario dare la preferenza a un bean quando più candidati sono qualificati per autorizzare una dipendenza a valore singolo. Se tra i candidati esiste esattamente un bean "primario", sarà il valore autowired. Questa annotazione è semanticamente equivalente all'attributo <bean>dell'elemento primaryin Spring XML.

@Primary
public class HibernateDeviceDao implements DeviceDao

O se vuoi che la tua versione di Jdbc sia usata per impostazione predefinita:

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

@Primary è ottimo anche per i test di integrazione quando è possibile sostituire facilmente il bean di produzione con la versione testata annotandolo.


Ho dimenticato di menzionare nella domanda che sto usando Spring 2.5 (ora ho modificato la domanda) quindi @Primary non è un'opzione.
simon

1
@simon: credo che l' primary=""attributo fosse disponibile prima. Dichiarare semplicemente HibernateDeviceDaoin XML ed escluderlo dalla scansione di componenti / annotazioni.
Tomasz Nurkiewicz,

1
Secondo la documentazione è disponibile dal 3.0: static.springsource.org/spring/docs/3.1.x/javadoc-api/org/… Un buon consiglio comunque, ricorderò l'annotazione primaria per il prossimo progetto quando potrò usare Spring 3.x
simon

8

Per la primavera 2.5, non c'è @Primary. L'unico modo è usare @Qualifier.


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.

0

Il motivo per cui @Resource (name = "{your class class name}") funziona ma @Autowired a volte non funziona è a causa della differenza della sequenza corrispondente

Sequenza corrispondente di @Autowire
Type, Qualifier, Name

Sequenza corrispondente di @Resource
Nome, Tipo, Qualificatore

La spiegazione più dettagliata può essere trovata qui:
Inject and Resource e annotazioni autowired

In questo caso, diverse classi figlio ereditate dalla classe o dall'interfaccia padre confondono @Autowire, perché sono dello stesso tipo; Poiché @Resource usa Nome come prima priorità di corrispondenza, funziona.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.