Autowiring two beans implementing same interface - how to set default bean to autowire?
Solution 1
I'd suggest marking the Hibernate DAO class with @Primary
, i.e. (assuming you used @Repository
on HibernateDeviceDao
):
@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao
This way it will be selected as the default autowire candididate, with no need to autowire-candidate
on the other bean.
Also, rather than using @Autowired @Qualifier
, I find it more elegant to use @Resource
for picking specific beans, i.e.
@Resource(name="jdbcDeviceDao")
DeviceDao deviceDao;
Solution 2
What about @Primary
?
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value. This annotation is semantically equivalent to the
<bean>
element'sprimary
attribute in Spring XML.
@Primary
public class HibernateDeviceDao implements DeviceDao
Or if you want your Jdbc version to be used by default:
<bean id="jdbcDeviceDao" primary="true" class="com.initech.service.dao.jdbc.JdbcDeviceDao">
@Primary
is also great for integration testing when you can easily replace production bean with stubbed version by annotating it.
Solution 3
For Spring 2.5, there's no @Primary
. The only way is to use @Qualifier
.
Solution 4
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.
Solution 5
The reason why @Resource(name = "{your child class name}") works but @Autowired sometimes don't work is because of the difference of their Matching sequence
Matching sequence of @Autowire
Type, Qualifier, Name
Matching sequence of @Resource
Name, Type, Qualifier
The more detail explanation can be found here:
Inject and Resource and Autowired annotations
In this case, different child class inherited from the parent class or interface confuses @Autowire, because they are from same type; As @Resource use Name as first matching priority , it works.
simon
Updated on February 03, 2020Comments
-
simon over 4 years
Background:
I have a Spring 2.5/Java/Tomcat application. There is the following bean, which is used throughout the application in many places
public class HibernateDeviceDao implements DeviceDao
and the following bean which is new:
public class JdbcDeviceDao implements DeviceDao
The first bean is configured so (all beans in the package are included)
<context:component-scan base-package="com.initech.service.dao.hibernate" />
The second (new) bean is configured separately
<bean id="jdbcDeviceDao" class="com.initech.service.dao.jdbc.JdbcDeviceDao"> <property name="dataSource" ref="jdbcDataSource"> </bean>
This results (of course) in an exception when starting the server:
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao] is defined: expected single matching bean but found 2: [deviceDao, jdbcDeviceDao]
from a class trying to autowire the bean like this
@Autowired private DeviceDao hibernateDevicDao;
because there are two beans implementing the same interface.
The question:
Is it possible to configure the beans so that
1. I don't have to make changes to existing classes, which already have the
HibernateDeviceDao
autowired2. still being able to use the second (new) bean like this:
@Autowired @Qualifier("jdbcDeviceDao")
I.e. i would need a way to configure the
HibernateDeviceDao
bean as the default bean to be autowired, simultaneously allowing the usage of a theJdbcDeviceDao
when explicitly specifying so with the@Qualifier
annotation.What I've already tried:
I tried setting the property
autowire-candidate="false"
in the bean configuration for JdbcDeviceDao:
<bean id="jdbcDeviceDao" class="com.initech.service.dao.jdbc.JdbcDeviceDao" autowire-candidate="false"> <property name="dataSource" ref="jdbcDataSource"/> </bean>
because the Spring documentation says that
Indicates whether or not this bean should be considered when looking for matching candidates to satisfy another bean's autowiring requirements. Note that this does not affect explicit references by name, which will get resolved even if the specified bean is not marked as an autowire candidate.*
which I interpreted to mean that I could still autowire
JdbcDeviceDao
using the@Qualifier
annotation and have theHibernateDeviceDao
as default bean. Apparently my interpretation was not correct, though, as this results in the following error message when starting the server:Unsatisfied dependency of type [class com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao]: expected at least 1 matching bean
coming from the class where I've tried autowiring the bean with a qualifier:
@Autowired @Qualifier("jdbcDeviceDao")
Solution:
skaffman's suggestion to try the @Resource annotation worked. So the configuration has autowire-candidate set to false for jdbcDeviceDao and when using the jdbcDeviceDao I refer to it using the @Resource annotation (instead of @Qualifier):
@Resource(name = "jdbcDeviceDao") private JdbcDeviceListItemDao jdbcDeviceDao;
-
Daniel Hári about 7 yearsIf I use this interface at 100places in the code, and I want to switch all to another implementation I don't want to change qualifier or resource annotations at all places. And I also don't want to change the code of either implementations. Why there is no explicit binding possibilities like in Guice?
-
-
simon about 12 yearsI forgot to mention in the question that I'm using Spring 2.5 (I have now edited the question) so @Primary is not an option.
-
simon about 12 yearsI forgot to mention in the question that I'm using Spring 2.5 (I have now edited the question) so @Primary is not an option.
-
skaffman about 12 years@simon: Yes, that was rather important. Try the
@Resource
annotation, as I also suggested. -
Tomasz Nurkiewicz about 12 years@simon: I believe
primary=""
attribute was available earlier. Just declareHibernateDeviceDao
in XML and exclude it from component/annotation scanning. -
simon about 12 yearsThanks, the resource annotation solved the problem - now the autowire-candidate property works as I expected.
-
simon about 12 yearsAccording to the documentation it's available since 3.0: static.springsource.org/spring/docs/3.1.x/javadoc-api/org/… Good tip anyhow, I'll remember the Primary annotation for the next project when I'm able to use Spring 3.x
-
asgs almost 9 yearsThanks! What's the difference between specifying the bean name via
@Resource
and@Qualifier
, apart from the fact that the former is relatively newer than the latter? -
The Gilbert Arenas Dagger almost 9 years@asgs Using resource annotation simplifies things. Instead of having the autowired / qualifier combo, you can mark it for dependency injection and specify the name in one line. Note that simon's solution is redundant, the autowired annotation can be removed.