1

Description

Consider the case when there are two (or more) Spring configuration classes, each returning a bean with the same name (e.g. baseBean). Also, suppose that these config classes create other beans (e.g. anotherBean1 and anotherBean2), which contain baseBean instances.

Is there a way to make Spring inject the baseBean instances created in the same configuration class?

Example

// Beans
public abstract class BaseBean {
    public abstract String getName();
}

public class Bean1 extends BaseBean {
    @Override
    public String getName() {
        return "Bean1";
    }
}

public class Bean2 extends BaseBean {
    @Override
    public String getName() {
        return "Bean2";
    }
}

// Configurations
@Configuration
public class Config1 {

    @Bean
    public BaseBean baseBean() {
        return new Bean1();
    }

    @Bean
    public AnotherBean anotherBean1() {
        return new AnotherBean(baseBean());
    }

}

@Configuration
public class Config2 {

    @Bean
    public BaseBean baseBean() {
        return new Bean2();
    }

    @Bean
    public AnotherBean anotherBean2() {
        return new AnotherBean(baseBean());
    }
}

// Main class
public class Main {

    public static void main(String [] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config1.class, Config2.class);

        AnotherBean anotherBean1 = (AnotherBean) applicationContext.getBean("anotherBean1");
        AnotherBean anotherBean2 = (AnotherBean) applicationContext.getBean("anotherBean2");

        System.out.println(anotherBean1.baseBean.getName());
        System.out.println(anotherBean2.baseBean.getName());
    }
}

Expected output:

Bean1
Bean2

Actual output:

Bean2
Bean2

In other words, both Config1.anotherBean1() and Config2.anotherBean2() invoke Config2.baseBean(). Instead, I would like each Config class to invoke its own baseBean method, i.e. Config1.anotherBean1() should use Config1.baseBean() (and Config2.anotherBean2() should of course use Config2.baseBean()).

Is there a built-in way to force this behaviour somehow, e.g with some annotation?

Remarks

  • By "built-in" I mean, that I would not like to write BeanPostProcessor's, or other similar extension. I would like to add an already existing annotation, or some similar solution.
  • Adding @Primary, @Qualifier, etc. is not an option: while it would work in the above example, the concrete code is structured in such a way, that does not make this possible. More precisely, baseBean() is an abstract method in an abstract base class, which is invoked by another method, anotherBean() in the same abstract class. Concrete classes then provide implementation for baseBean method. This means, that in the base class I obviously cannot use any qualifier to refer to baseBean (and cannot make any of the implementations @Primary either).
public class BaseConfig {
       @Bean
       public abstract BaseBean baseBean();

       @Bean
       public AnotherBean anotherBean() {
              return new AnotherBean(baseBean());
       }
}

          +----------------------------+
          |   BaseConfig               |
          |                            | 
          | + (abstract) baseBean()    |
          | + anotherBean()            |
          |____________________________|
                    /\
                   /__\
           _________|__________
   _______|_______      _______|__________
  |    Config1    |     |    Config2     |
  | + baseBean()  |     |  + baseBean()  |
  |_______________|     |________________|
  • All beans need to be singleton, i.e. not prototype.
  • I strongly suspect that this is not possible (I checked what Spring does under the hood, and it seems that it maintains a "bean name" -> bean instance map, which is filled with whichever bean is encountered first. And then, the call to the Java config method is intercepted, and the already cached bean is returned) So a confirmation, that what I want is indeed not possible, is also a valid answer. But I could also be wrong on this...
1
  • I think it's unlikely, because the second definition of the BaseBean will replace the first in the applicationContext. Spring won't allow you to define two beans of the same type with the same name. System.out.println(applicationContext.getBeansOfType(BaseBean.class).size()); will print "1"
    – barcom
    Commented Aug 21, 2017 at 21:53

1 Answer 1

2

Is there a reason that the different baseBean need to be Spring-Managed Beans themselves?

It sounds like you're only planning on using them from within AnotherBean so you could simply remove the @Bean annotation from ConfigX.baseBean() and you'd get the expected results.

Not the answer you're looking for? Browse other questions tagged or ask your own question.