【问题标题】:Spring Couldn't autowired,there is more than one bean of `` typeSpring无法自动装配,有多个``类型的bean
【发布时间】:2016-09-30 14:47:36
【问题描述】:

这是我的问题:我有一个基接口和两个实现类。

并且一个Service类对基接口有依赖,代码是这样的:

@Component
public interface BaseInterface {}

@Component
public class ClazzImplA implements  BaseInterface{}

@Component
public class ClazzImplB implements  BaseInterface{}

而且配置是这样的:

@Configuration
public class SpringConfig {
    @Bean
    public BaseInterface clazzImplA(){
        return new ClazzImplA();
    }

    @Bean
    public BaseInterface clazzImplB(){
        return new ClazzImplB();
    }
}

服务类对基础接口有依赖,会通过一些业务逻辑决定自动装配哪个实现。代码如下:


@Service
@SpringApplicationConfiguration(SpringConfig.class)
public class AutowiredClazz {
    @Autowired
    private BaseInterface baseInterface;

    private AutowiredClazz(BaseInterface baseInterface){
        this.baseInterface = baseInterface;
    }
}

并且IDEA抛出异常:无法自动装配。BaseInterface类型的bean不止一个。

虽然使用@Qualifier可以解决,但是在这种情况下我不能选择依赖类。

@Autowired
@Qualifier("clazzImplA")
private BaseInterface baseInterface;

我尝试阅读 spring 文档,它提供了 Constructor-based dependency injection,但我仍然对这个问题感到困惑。

谁能帮帮我?

【问题讨论】:

  • 这种时候我讨厌Java
  • @gdbj facepalm 是关于 spring,而不是 java 你在任何其他有 DI 的语言中都会遇到同样的问题。如果您尝试将 2 个 bean 连接到一个字段,您将拥有这个

标签: java spring


【解决方案1】:

Spring 混淆了您在配置类中声明的 2 个 bean,因此您可以使用 @Qualifier 注释和 @Autowired 通过指定要连接的确切 bean 来消除混淆,将这些修改应用于您的配置类

@Configuration
public class SpringConfig {
    @Bean(name="clazzImplA")
    public BaseInterface clazzImplA(){
        return new ClazzImplA();
    }

    @Bean(name="clazzImplB")
    public BaseInterface clazzImplB(){
        return new ClazzImplB();
    }
}

然后在@autowired注解

@Service
@SpringApplicationConfiguration(SpringConfig.class)
public class AutowiredClazz {
    @Autowired
    @Qualifier("the name of the desired bean")
    private BaseInterface baseInterface;

    private AutowiredClazz(BaseInterface baseInterface){
        this.baseInterface = baseInterface;
    }
}

【讨论】:

  • 感谢您的回答,但是在句子@Qualifier("the name of the desired bean") 中,“the name of the desired bean”可以是动态字符串吗?因为 AutowiredClazz 可能在不同的情况下使用不同的实现。
  • spring 无法处理这个问题,因为 AutowiredClazz 只会被初始化一次,但我注意到你有一个 getInstance() 方法将返回适当的 bean。这将使使用动态
  • 你会认为 Spring 会实现一个简单的方法来尝试选择正确的方法,而不是在这里显示一个运行时的错误,这在向用户显示错误之前很难解决.非常糟糕的实施,IMO。
【解决方案2】:

仅使用spring框架无法解决这个问题。您提到基于某些逻辑,您需要一个 BaseInterface 的实例。这个用例可以使用工厂模式来解决。创建一个实际上是 BaseInterface 工厂的 Bean

@Component
public class BaseInterfaceFactory{

  @Autowired
  @Qualifier("clazzImplA")
  private BaseInterface baseInterfaceA;

  @Autowired
  @Qualifier("clazzImplb")
  private BaseInterface baseInterfaceB;

  public BaseInterface getInstance(parameters which will decides what type of instance you want){
    // your logic to choose Instance A or Instance B
    return baseInterfaceA or baseInterfaceB
  }

}

配置(无耻抄袭其他评论)

@Configuration
public class SpringConfig {
    @Bean(name="clazzImplA")
    public BaseInterface clazzImplA(){
        return new ClazzImplA();
    }

    @Bean(name="clazzImplB")
    public BaseInterface clazzImplB(){
        return new ClazzImplB();
    }
}

服务类

@Service
@SpringApplicationConfiguration(SpringConfig.class)
public class AutowiredClazz {
    @Autowired
    private BaseInterfaceFactory factory;

    public void someMethod(){
       BaseInterface a = factory.getInstance(some parameters);
       // do whatever with instance a
    }
}

【讨论】:

    【解决方案3】:

    如果您使用@Autowired,Spring 会搜索与您要自动装配的字段类型匹配的 bean。在您的情况下,BaseInterface 类型的 bean 不止一个。这意味着 Spring 无法明确选择匹配的 bean。

    在这种情况下,您别无选择明确声明 Spring 应使用的 bean 或解决歧义。

    【讨论】:

      【解决方案4】:

      一个更酷的解决方案是让实现本身包含确定它们是否适用的逻辑。您可以将所有可用的实现作为一个集合注入并对其进行迭代以找到一个(或多个,如果您需要的话)适用的:

      public interface BaseInterface {
          boolean canHandle(Object parameter);
          Object doTheWork(Object parameter);
      }
      
      @Service
      public class SomeService {
      
          private final BaseInterface[] implementations;
      
          // Spring injects all beans implementing BaseInterface
          public MessageService(BaseInterface... implementations) {
              this.implementations = implementations;
          }
      
          public Object doSomething(Object parameter) {
              BaseInterface baseInterface = findBaseInterface(parameter);
              return baseInterface.doTheWork(parameter);
          }    
      
          private BaseInterface findBaseInterface(Object parameter) {
              return Arrays.stream(implementations)
                  .findAny(i -> i.canHandle(parameter)
                  .orElseThrow(new MyRuntimeException(String.format("Could not find BaseInterface to handle %s", parameter)));
          }
      }
      

      【讨论】:

        【解决方案5】:

        这里的回答是正确的,在这种情况下,如果一个接口由多个类实现,我们必须使用@Component(name=$beanName) 来命名每个 bean

        我想再补充一点,在这种情况下,Spring 甚至可以在地图中自动装配此类 bean:

        @Autowired
        Map<String,InterfaceName> interfaceMap;
        //this will generate beans and index them with beanName as key of map
        

        【讨论】:

          猜你喜欢
          • 2016-09-27
          • 1970-01-01
          • 2012-12-04
          • 2019-09-16
          • 1970-01-01
          • 2022-01-09
          • 2019-06-14
          • 1970-01-01
          • 2014-03-11
          相关资源
          最近更新 更多