1

How do I extend a named bean when using a @Qualifier specified bean injection point?

I have project 1 consisting of 3 beans:

@Component("bean1")
public class Bean1 implements Bean {
}

@Component("bean2")
public class Bean2 implements Bean {
}

@Component("bean3")
public class Bean3 {
    private Bean bean;

    public void setBean(@Qualifier("bean1") final Bean bean) {
        this.bean = bean;
    }
}

This project is bundled into a jar and included as a dependency on my 2nd project:

@Component("bean1")
@Primary
public class Bean1Child extends Bean1 {
}

What I want to happen is for the bean, Bean1Child, to be injected into Bean3's setter method. Unfortunatly I am receiving an error.

org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'bean1' for bean class [Bean1Child] conflicts with existing, non-compatible bean definition of same name and class [Bean1]

I needed to use @Qualifier so that Bean2 is not injected into Bean3 Using the @Primary annotation did not help. How can I have Bean1Child injected into Bean3 when running from my 2nd project?

Eric
  • 6,563
  • 5
  • 42
  • 66
  • The easiest workaround is to exclude `Bean1` from scanning in the project 2. Is it a Spring Boot application. If not, do you have Java or XML config? – Eugene Khyst Jan 22 '20 at 17:22

4 Answers4

4

If this is possible, you can change the way the beans are created by removing the @Component annotations:

In the first project, the BeanChild3 would be refactored to get the bean in the constructor

public class Bean3 {
    private final Bean bean;

    public Bean3(final Bean bean) {
        this.bean = bean;
    }
}

Then we can create the beans in a BeansConfig class

@Configuration
class BeansConfig {

   @ConditionalOnMissingBean
   @Bean("bean1")
   Bean bean1(){
      return new Bean1();
   }

   @Bean("bean2")
   Bean bean2(){
      return new Bean2();
   }

   @Bean("bean3")
   Bean bean3(@Autowired @Qualifier("bean1") Bean){
      return new Bean3(bean);
   }

}

The @ConditionalOnMissingBean allows us to provide another bean with the same name to be used instead. If no such bean exists, then the default one would be used.

You will then need to create a beanChild1 bean in your second project and it should be picked up.

   @Bean("bean1")
   Bean bean1(){
      return new Bean1Child();
   }
Ahmed Sayed
  • 1,429
  • 12
  • 12
2

You can easily achieve this using @ConditionalOnMissingBean feature.

Modify your Bean1 class as below

@Component("bean1")
@ConditionalOnMissingBean(type = "bean1")
public class Bean1 implements Bean {
}

modify Bean1Child class as below

@Component
@Qualifier("bean1")
@Primary
public class Bean1Child extends Bean1 {
}

How it works?

  • Spring will try to load a bean named "bean1". If it doesn't find any bean by the same name that is marked as @Primary it will fall back to Bean1 class and load it as "bean1".
  • Bean1Child has to be marked as primary because spring is going to find 2 beans by the same name. We need to tell which to load.
Yogesh Badke
  • 4,249
  • 2
  • 15
  • 23
1

You have multiple beans of the same type and want to prevent Bean2 from injecting. In some project inject Bean1 and in others Bean1Child.

There are multiple options.

Override bean definition with @Bean

Make Bean1Child bean definition the same as Bean1 has using @Bean

@Configuration
public class Config {

  @Primary
  @Bean
  public Bean1 bean1() { //return type must be Bean1
    return new Bean1Child();
  }
}

and set the property spring.main.allow-bean-definition-overriding=true

Create custom @Qualifier annotation

@Qualifier
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface BeanType {

}

@BeanType
public @interface Bean1Type {
}

@Bean1Type
@Component("bean1")
public class Bean1 implements Bean {
}

@Component("bean2")
public class Bean2 implements Bean {
}

@Component("bean3")
public class Bean3 {

  private final Bean bean;

  public Bean3(@Bean1Type Bean bean) {
    this.bean = bean;
  }
}

@Bean1Type
@Primary
@Component("bean1Child")
public class Bean1Child extends Bean1 {

}
Eugene Khyst
  • 9,236
  • 7
  • 38
  • 65
-1

You cannot have two beans specified with the same qualified name, as the error indicates:

Annotation-specified bean name 'bean1' for bean class [Bean1Child] 
conflicts with existing, non-compatible bean definition of same name and class [Bean1]

Giving a different qualifier name to Bean1Child should work.

@Component("bean1child")
@Primary
public class Bean1Child extends Bean1 {
}

and in Bean3 , public void setBean(@Qualifier("bean1child") final Bean bean) {

Bajal
  • 5,487
  • 3
  • 20
  • 25
  • I cannot change the qualifier because I need project 1 to inject Bean1 when it is run without the 2nd project – Eric Jan 19 '20 at 04:24