简体   繁体   English

自动装配两个实现相同接口的 bean - 如何将默认 bean 设置为自动装配?

[英]Autowiring two beans implementing same interface - how to set default bean to autowire?

Background:背景:

I have a Spring 2.5/Java/Tomcat application.我有一个 Spring 2.5/Java/Tomcat 应用程序。 There is the following bean, which is used throughout the application in many places有下面这个bean,在整个应用程序中很多地方都会用到

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)第一个bean是这样配置的(包中的所有bean都包含在内)

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

The second (new) bean is configured separately第二个(新)bean 单独配置

<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]嵌套异常是 org.springframework.beans.factory.NoSuchBeanDefinitionException: 没有定义类型 [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao] 的唯一 bean:预期的单个匹配 bean,但发现 2: [deviceDao, jdbcDeviceDao]

from a class trying to autowire the bean like this来自一个试图像这样自动装配 bean 的类

@Autowired
private DeviceDao hibernateDevicDao;

because there are two beans implementing the same interface.因为有两个 bean 实现了相同的接口。

The question:问题:

Is it possible to configure the beans so that是否可以配置bean,以便

1. I don't have to make changes to existing classes, which already have the HibernateDeviceDao autowired 1.我不必对已经自动装配HibernateDeviceDao现有类进行更改

2. still being able to use the second (new) bean like this: 2.仍然可以像这样使用第二个(新的)bean:

@Autowired
@Qualifier("jdbcDeviceDao")

Ie i would need a way to configure the HibernateDeviceDao bean as the default bean to be autowired, simultaneously allowing the usage of a the JdbcDeviceDao when explicitly specifying so with the @Qualifier annotation.即我需要一种方法来将HibernateDeviceDao bean 配置为要自动装配的默认 bean,同时允许在使用@Qualifier注释显式指定时使用JdbcDeviceDao

What I've already tried:我已经尝试过的:

I tried setting the property我尝试设置属性

autowire-candidate="false"

in the bean configuration for JdbcDeviceDao:在 JdbcDeviceDao 的 bean 配置中:

<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因为 Spring 文档说

Indicates whether or not this bean should be considered when looking for matching candidates to satisfy another bean's autowiring requirements.指示在寻找匹配候选以满足另一个 bean 的自动装配要求时是否应考虑此 bean。 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.*请注意,这不会影响按名称的显式引用,即使指定的 bean 未标记为自动装配候选者,也会得到解析。*

which I interpreted to mean that I could still autowire JdbcDeviceDao using the @Qualifier annotation and have the HibernateDeviceDao as default bean.我认为这意味着我仍然可以使用@Qualifier注释自动装配JdbcDeviceDao ,并将HibernateDeviceDao作为默认 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 [class com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao] 类型的不满意依赖:预计至少有 1 个匹配的 bean

coming from the class where I've tried autowiring the bean with a qualifier:来自我尝试使用限定符自动装配 bean 的类:

@Autowired
@Qualifier("jdbcDeviceDao")

Solution:解决方案:

skaffman's suggestion to try the @Resource annotation worked. skaffman 建议尝试使用 @Resource 注释。 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):因此,配置将 jdbcDeviceDao 的 autowire-candidate 设置为 false,并且在使用 jdbcDeviceDao 时,我使用 @Resource 注释(而不是 @Qualifier)来引用它:

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

I'd suggest marking the Hibernate DAO class with @Primary , ie (assuming you used @Repository on HibernateDeviceDao ):我建议标记使用Hibernate的DAO类@Primary ,即(假设你使用@RepositoryHibernateDeviceDao ):

@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.这样它将被选为默认的自动装配候选者,而无需在另一个 bean 上autowire-candidate

Also, rather than using @Autowired @Qualifier , I find it more elegant to use @Resource for picking specific beans, ie另外,我发现使用@Resource来挑选特定的 bean 比使用@Autowired @Qualifier更优雅,即

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

What about @Primary ? @Primary怎么样?

Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency.指示当多个候选者有资格自动装配单值依赖项时,应该优先考虑一个 bean。 If exactly one 'primary' bean exists among the candidates, it will be the autowired value.如果候选中恰好存在一个“主要”bean,它将是自动装配的值。 This annotation is semantically equivalent to the <bean> element's primary attribute in Spring XML.这个注解在语义上等同于 Spring XML 中<bean>元素的primary属性。

@Primary
public class HibernateDeviceDao implements DeviceDao

Or if you want your Jdbc version to be used by default:或者,如果您希望默认使用 Jdbc 版本:

<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. @Primary也非常适合集成测试,因为您可以通过注释轻松地将生产 bean 替换为存根版本。

For Spring 2.5, there's no @Primary .对于 Spring 2.5,没有@Primary The only way is to use @Qualifier .唯一的方法是使用@Qualifier

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.

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 @Resource(name = "{your child class name}") 工作但@Autowired 有时不工作的原因是因为它们的匹配顺序不同

Matching sequence of @Autowire @Autowire 的匹配顺序
Type, Qualifier, Name类型、限定符、名称

Matching sequence of @Resource @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;在这种情况下,从父类或接口继承的不同子类会混淆@Autowire,因为它们来自同一类型; As @Resource use Name as first matching priority , it works.由于@Resource 使用 Name 作为第一个匹配的优先级,它可以工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM