Correctness7 - SpotBugsExtensionForSpringFrameWork/CS5098 GitHub Wiki

Bug pattern name: AMBIGUOUS_INJECTION_BY_TYPE
Short description: When two beans inherit the same interface and the bean is not specified, Spring doesn't know which component to autowire (NoUniqueBeanDefinitionException)

Description

BeanB1 and BeanB2 inherit the same interface. BeanA wire IBeanB, but Spring doesn't know which one to wire. Here, byType autowiring method is used - namely by its interface, and Spring doesn't know which component to autowire.

@Component
public class BeanB1 implements IBeanB { ... } 
//------------------------------------------------
@Component
public class BeanB2 implements IBeanB { ... }
//------------------------------------------------
@Component
public class BeanA {

    @Autowired
    private IBeanB dependency; // error
    ...
}
 <bean class="com.example.demo.BeanA"/>
 <bean class="com.example.demo.BeanB1"/>
 <bean class="com.example.demo.BeanB2"/>

Solutions

  • use Primary
  • "Because autowiring by type may lead to multiple candidates, it is often necessary to have more control over the selection process. One way to accomplish this is with Spring’s @Primary annotation. @Primary indicates that a particular bean should be given preference when multiple beans are candidates to be autowired to a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value." (Spring 4 docs)
// Solution 1: case 1: Java-based Configuration in Configuration File
@Configuration
@ComponentScan(basePackages = "com.example.demo")
public class AppConfig {
    @Bean
    @Primary
    public IBeanB beanB1() {
        BeanB1 beanB1 = new BeanB1();
        return beanB1;
    }

    @Bean
    public IBeanB beanB2() {
        BeanB2 beanB2 = new BeanB2();
        return beanB2;
    }
}
//------------------------------------------------------
// Solution 2: Java-based Configuration in Component class
@Component
@Primary
public class BeanB1 implements IBeanB {
}

XML configuration

<!-- Solution 3: XML configuration --->
<bean id="beanB1" primary="true" class="com.example.demo.BeanB1"/>
<bean id="beanB2" class="com.example.demo.BeanB2"/>

Java-based configuration

@Configuration
public class AppConfig {
 @Bean
 @Primary
 public IBeanB beanB1() { ... }
 @Bean
 public IBeanB beanB2() { ... }
 // ...
}
  • use @Qualifier
@Component
public class BeanA {
    @Autowired
    @Qualifier("beanB2") // solved
    private IBeanB dependency;
}
//------------------------------------------------------
// In the other injection modes, @Qualifier can also be used.
@Component
public class BeanA {
    private IBeanB dependency;
    // @Qualifier("beanB2") can be used here as well
    BeanA ( @Qualifier("beanB2") IBeanA dependency){ 
       this.dependency =  dependency;
    }
}

Theory

  • "Allows a property to be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens; the property is not set." (spring docs)

"Spring supports five modes for autowiring: byName, byType, constructor, default, and no.".

Five autowiring modes

  • byName: When using byName autowiring, Spring attempts to wire each property to a bean of the same name. So, if the target bean has a property named foo and a foo bean is defined in ApplicationContext, the foo bean is assigned to the foo property of the target.
  • byType: When using byType autowiring, Spring attempts to wire each of the properties on the target bean by automatically using a bean of the same type in ApplicationContext.
  • constructor: This functions just like byType wiring, except that it uses constructors rather than setters to perform the injection. Spring attempts to match the greatest numbers of arguments it can in the constructor. So, if your bean has two constructors, one that accepts a String and one that accepts String and an Integer, and you have both a String and an Integer bean in your ApplicationContext, Spring uses the two-argument constructor.
  • default: Spring will choose between the constructor and byType modes automatically. If your bean has a default (no-arguments) constructor, Spring uses byType; otherwise, it uses constructor.
  • no: This is the default.

As there are many ways to autowire, here only byType is considered. If XML configuration file is like below (i.e., without bean ID), Spring use byType by default.

<beans ...>
    <bean class="com.example.demo.BeanA"/>
    <bean class="com.example.demo.BeanB1"/>
    <bean class="com.example.demo.BeanB2"/>
</beans>

The beans, BeanB1 and BeanB2, are defined without ID. With this case, problem occur when the beans implement same interface and other beans autowire the interface like below.

@Component
public class BeanB1 implements IBeanB { ... } 
//------------------------------------------------
@Component
public class BeanB2 implements IBeanB { ... }
//------------------------------------------------
@Component
public class BeanA {
    @Autowired
    private IBeanB dependency; // error
}

When Spring inject the dependency of IBeanB type in BeanA class, Spring doesn't know which one to wire. One of solution for this is to add @Qualifier like below.

@Autowired
@Qualifier("beanB1") // or @Qualifier("beanB2") 
private IBeanB dependency;

When Java-based configuration is used, it comes like below.

@Configuration
@ComponentScan(basePackages = "com.example.demo")
public class AppConfig {
    @Bean
    public IBeanB beanB1() {
        BeanB1 beanB1 = new BeanB1();
        return beanB1;
    }

    @Bean
    public IBeanB beanB2() {
        BeanB2 beanB2 = new BeanB2();
        return beanB2;
    }
}

If name attribute is not defined in @Bean, method name become the id by default, i.e., beanB1 for BeanB1 and beanB2 for BeanB2.

To specify own name of bean, it is like below.

@Bean1(name="specialBean")
public IBeanB beanB1() {
    BeanB1 beanB1 = new BeanB1();
    return beanB1;
}

What is important thing is that, in the IoC container, "If there's more than one bean of that type, the framework throws an exception."

need to write later

This would apply to not only @Component but also to other annotations such as @Repository, @Service, and @Controller. link

Comparison between Java-based and XML configuration

@Configuration
public class AppConfig {
 @Bean
 public MyService myService() {
   return new MyServiceImpl();
 }
}
<beans>
  <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

need to add later https://github.com/SpotBugsExtensionForSpringFrameWork/CS5098/wiki/Correctness35

Implement Strategy

1 Find beans that implement the same interface. 2 Check if the beans have own id by looking at XML or Java-based configuration file.

Reference List

⚠️ **GitHub.com Fallback** ⚠️