【问题标题】:Autowiring two beans implementing same interface - how to set default bean to autowire?自动装配两个实现相同接口的 bean - 如何将默认 bean 设置为自动装配?
【发布时间】:2012-05-19 00:41:18
【问题描述】:

背景:

我有一个 Spring 2.5/Java/Tomcat 应用程序。有下面这个bean,在整个应用中很多地方都用到了

public class HibernateDeviceDao implements DeviceDao

以及以下新的bean:

public class JdbcDeviceDao implements DeviceDao

第一个 bean 是这样配置的(包中的所有 bean 都包括在内)

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

第二个(新)bean单独配置

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

这会导致(当然)在启动服务器时出现异常:

嵌套异常是 org.springframework.beans.factory.NoSuchBeanDefinitionException:没有定义类型 [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao] 的唯一 bean:预期单个匹配 bean,但找到 2:[deviceDao, jdbcDeviceDao]

来自一个试图像这样自动装配 bean 的类

@Autowired
private DeviceDao hibernateDevicDao;

因为有两个 bean 实现了相同的接口。

问题:

是否可以配置 bean 以便

1.我不必对现有的类进行更改,这些类已经具有 HibernateDeviceDao 自动装配

2. 仍然可以像这样使用第二个(新)bean:

@Autowired
@Qualifier("jdbcDeviceDao")

即我需要一种将HibernateDeviceDao bean 配置为要自动装配的默认bean 的方法,同时允许在使用@Qualifier 注释明确指定时使用JdbcDeviceDao

我已经尝试过的:

我尝试设置属性

autowire-candidate="false"

在 JdbcDeviceDao 的 bean 配置中:

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

因为 Spring 文档是这么说的

指示是否应该在何时考虑此 bean 寻找匹配的候选人来满足另一个 bean 的 自动装配要求。 请注意,这不会影响显式 按名称引用,即使指定 bean 未标记为自动装配候选者。*

我解释为我仍然可以使用@Qualifier 注释自动装配JdbcDeviceDao 并将HibernateDeviceDao 作为默认bean。不过,显然我的解释不正确,因为这会在启动服务器时导致以下错误消息:

类型 [class com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao] 的不满足依赖性:预计至少有 1 个匹配 bean

来自我尝试使用限定符自动装配 bean 的类:

@Autowired
@Qualifier("jdbcDeviceDao")

解决方案:

skaffman's suggestion 尝试使用@Resource 注释。因此配置将 jdbcDeviceDao 的 autowire-candidate 设置为 false 并且在使用 jdbcDeviceDao 时我使用 @Resource 注释(而不是 @Qualifier)来引用它:

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

【问题讨论】:

  • 如果我在代码中的 100 个地方使用这个接口,并且我想全部切换到另一个实现,我不想在所有地方都更改限定符或资源注释。而且我也不想更改任一实现的代码。为什么没有像 Guice 那样的显式绑定可能性?

标签: java spring spring-mvc autowired


【解决方案1】:

我建议用@Primary 标记Hibernate DAO 类,即(假设您在HibernateDeviceDao 上使用@Repository):

@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao

这样它将被选为默认的自动装配候选者,而无需在另一个 bean 上autowire-candidate

另外,比起使用@Autowired @Qualifier,我发现使用@Resource 来挑选特定的bean 更优雅,即

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

【讨论】:

  • 我忘了在问题中提到我正在使用 Spring 2.5(我现在已经编辑了问题)所以@Primary 不是一个选项。
  • @simon:是的,这很重要。按照我的建议尝试@Resource 注释。
  • 谢谢,资源注释解决了这个问题——现在 autowire-candidate 属性可以正常工作了。
  • 谢谢!通过@Resource@Qualifier 指定bean 名称,除了前者比后者相对更新,有什么区别?
  • @asgs 使用资源注释可以简化事情。您可以将其标记为依赖注入并在一行中指定名称,而不是使用自动装配/限定符组合。注意simon的方案是多余的,可以去掉autowired注解。
【解决方案2】:

@Primary 呢?

表示当多个候选者有资格自动装配时,应为 bean 提供优先权单值依赖项。如果候选中恰好存在一个“主要”bean,它将是自动装配的值。这个注解在语义上等同于 Spring XML 中 &lt;bean&gt; 元素的 primary 属性。

@Primary
public class HibernateDeviceDao implements DeviceDao

或者如果您希望默认使用您的 Jdbc 版本:

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

@Primary 也非常适合集成测试,因为您可以通过注释轻松地将生产 bean 替换为存根版本。

【讨论】:

  • 我忘了在问题中提到我正在使用 Spring 2.5(我现在已经编辑了问题)所以@Primary 不是一个选项。
  • @simon:我相信primary="" 属性更早可用。只需在 XML 中声明 HibernateDeviceDao 并将其从组件/注释扫描中排除。
  • 根据文档,它从 3.0 开始可用:static.springsource.org/spring/docs/3.1.x/javadoc-api/org/… 无论如何,好的提示,当我能够使用 Spring 3.x 时,我会记住下一个项目的 Primary 注释
【解决方案3】:

对于 Spring 2.5,没有 @Primary。唯一的方法是使用@Qualifier

【讨论】:

    【解决方案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.
    

    【讨论】:

      【解决方案5】:

      @Resource(name = "{your child class name}") 有效而@Autowired 有时无效的原因是它们的匹配顺序不同

      @Autowire 的匹配序列
      类型、限定符、名称

      @Resource 的匹配序列
      名称、类型、限定符

      更详细的解释可以在这里找到:
      Inject and Resource and Autowired annotations

      在这种情况下,从父类或接口继承的不同子类会混淆@Autowire,因为它们来自同一类型;由于 @Resource 使用 Name 作为第一个匹配优先级,它可以工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-25
        • 1970-01-01
        • 2018-12-24
        相关资源
        最近更新 更多