Cableado automático de dos beans que implementan la misma interfaz: ¿cómo configurar el bean predeterminado para autowire?

138

Antecedentes:

Tengo una aplicación Spring 2.5 / Java / Tomcat. Existe el siguiente bean, que se utiliza en toda la aplicación en muchos lugares.

public class HibernateDeviceDao implements DeviceDao

y el siguiente bean que es nuevo:

public class JdbcDeviceDao implements DeviceDao

El primer bean está configurado de esta manera (todos los beans en el paquete están incluidos)

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

El segundo bean (nuevo) se configura por separado

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

Esto resulta (por supuesto) en una excepción al iniciar el servidor:

la excepción anidada es org.springframework.beans.factory.NoSuchBeanDefinitionException: no se ha definido un bean único de tipo [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao]: bean coincidente único esperado pero encontrado 2: [deviceDao, jdbcDeviceDao]

de una clase que intenta conectar automáticamente el bean así

@Autowired
private DeviceDao hibernateDevicDao;

porque hay dos beans que implementan la misma interfaz.

La pregunta:

¿Es posible configurar los beans para que

1. No tengo que hacer cambios en las clases existentes, que ya tienen el HibernateDeviceDaoautowired

2. todavía poder usar el segundo (nuevo) bean como este:

@Autowired
@Qualifier("jdbcDeviceDao")

Es decir, necesitaría una forma de configurar el HibernateDeviceDaobean como el bean predeterminado que se conectará automáticamente, permitiendo simultáneamente el uso de un JdbcDeviceDaocuando se especifique explícitamente con la @Qualifieranotación.

Lo que ya he probado:

Traté de configurar la propiedad

autowire-candidate="false"

en la configuración de bean para JdbcDeviceDao:

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

porque la documentación de Spring dice que

Indica si este bean debe considerarse o no cuando se buscan candidatos coincidentes para satisfacer los requisitos de cableado automático de otro bean. Tenga en cuenta que esto no afecta a las referencias explícitas por nombre, que se resolverán incluso si el bean especificado no está marcado como candidato de cableado automático. *

lo cual interpreté para significar que aún podía conectar automáticamente JdbcDeviceDaousando la @Qualifieranotación y tener el HibernateDeviceDaobean predeterminado. Aparentemente, mi interpretación no fue correcta, ya que esto da como resultado el siguiente mensaje de error al iniciar el servidor:

Dependencia insatisfecha del tipo [clase com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao]: esperaba al menos 1 bean coincidente

viniendo de la clase donde probé el cableado automático del bean con un calificador:

@Autowired
@Qualifier("jdbcDeviceDao")

Solución:

La sugerencia de skaffman de probar la anotación @Resource funcionó. Entonces, la configuración tiene autowire-candidato establecido en false para jdbcDeviceDao y cuando uso el jdbcDeviceDao me refiero a él usando la anotación @Resource (en lugar de @Qualifier):

@Resource(name = "jdbcDeviceDao")
private JdbcDeviceListItemDao jdbcDeviceDao;
Simón
fuente
Si uso esta interfaz en 100 lugares en el código, y quiero cambiar todo a otra implementación, no quiero cambiar el calificador o las anotaciones de recursos en todos los lugares. Y tampoco quiero cambiar el código de ninguna de las implementaciones. ¿Por qué no hay posibilidades vinculantes explícitas como en Guice?
Daniel Hári

Respuestas:

134

Yo sugeriría que marca la clase Hibernate DAO con @Primary, es decir, (suponiendo que utilizó @Repositoryen HibernateDeviceDao):

@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao

De esta manera, se seleccionará como el candididate de cableado automático predeterminado, sin necesidad autowire-candidatedel otro bean.

Además, en lugar de usar @Autowired @Qualifier, me parece más elegante usar @Resourcepara recoger frijoles específicos, es decir

@Resource(name="jdbcDeviceDao")
DeviceDao deviceDao;
skaffman
fuente
Olvidé mencionar en la pregunta que estoy usando Spring 2.5 (ahora he editado la pregunta), así que @Primary no es una opción.
Simon
1
@simon: Sí, eso fue bastante importante. Pruebe la @Resourceanotación, como también sugerí.
skaffman
1
Gracias, la anotación de recursos resolvió el problema: ahora la propiedad autowire-candidato funciona como esperaba.
Simon
¡Gracias! ¿Cuál es la diferencia entre especificar el nombre del bean vía @Resourcey @Qualifier, aparte del hecho de que el primero es relativamente más nuevo que el segundo?
pregunta el
1
@asgs El uso de la anotación de recursos simplifica las cosas. En lugar de tener el combo autowired / qualifier, puede marcarlo para la inyección de dependencia y especificar el nombre en una línea. Tenga en cuenta que la solución de Simon es redundante, la anotación con cableado automático se puede eliminar.
La daga Gilbert Arenas
37

¿Qué hay de @Primary?

Indica que se debe dar preferencia a un bean cuando varios candidatos están calificados para conectar automáticamente una dependencia de un solo valor. Si existe exactamente un bean 'primario' entre los candidatos, será el valor autoconectado. Esta anotación es semánticamente equivalente al atributo <bean>del elemento primaryen Spring XML.

@Primary
public class HibernateDeviceDao implements DeviceDao

O si desea que su versión Jdbc se use por defecto:

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

@Primary También es ideal para las pruebas de integración cuando puede reemplazar fácilmente el bean de producción con la versión tropezada al anotarlo.

Tomasz Nurkiewicz
fuente
Olvidé mencionar en la pregunta que estoy usando Spring 2.5 (ahora he editado la pregunta), así que @Primary no es una opción.
Simon
1
@simon: Creo que el primary=""atributo estaba disponible antes. Simplemente declare HibernateDeviceDaoen XML y excluya del escaneo de componentes / anotaciones.
Tomasz Nurkiewicz
1
De acuerdo con la documentación, está disponible desde 3.0: static.springsource.org/spring/docs/3.1.x/javadoc-api/org/… Un buen consejo de todos modos, recordaré la anotación primaria para el próximo proyecto cuando pueda usar Spring 3.x
simon
8

Para Spring 2.5, no hay @Primary. La única forma es usar @Qualifier.

blanquear
fuente
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
fuente
0

La razón por la cual @Resource (name = "{your child class name}") funciona pero @Autowired a veces no funciona es por la diferencia de su secuencia de correspondencia

Secuencia coincidente de @Autowire
Type, Qualifier, Name

Secuencia coincidente de @Resource
Nombre, Tipo, Calificador

La explicación más detallada se puede encontrar aquí:
Inyectar y Recurso y anotaciones con conexión automática

En este caso, una clase secundaria diferente heredada de la clase o interfaz principal confunde @Autowire, porque son del mismo tipo; Como @Resource usa Name como primera prioridad coincidente, funciona.

RAYO
fuente