To get a better understanding of the inner workings of Spring bean discovery, I created some examples that can be found here: https://github.com/maciejmiklas/spring-context-test.git
Each example has a name: exa (example A) or exb (example B). Each example has several exercises, like exa has: exa01, exa02, ..., ex05.
Bean Loading Order - Single Config
exa01
This is the first exercise. It defines a basic structure that will be modified as we go along.
The main method loads Spring context from the configuration file Conf.java. This configuration instructs Spring to create 3 beans. Each of these beans has two log statements, the first in the constructor and the second in @PostConstruct. The console output after the execution of this example is shown below the code.
Nothing spectacular happens here: beans are initialized from A to C right after the constructor Spring executes also @PostConstruct. There are no dependencies between the beans.The main method loads Spring context from the configuration file Conf.java. This configuration instructs Spring to create 3 beans. Each of these beans has two log statements, the first in the constructor and the second in @PostConstruct. The console output after the execution of this example is shown below the code.
exa02
Let's modify the order of factory methods in Conf.java.
The bean initialization order has changed. It means that method names do not matter, but the physical order within a class. It can change with JVM, so you should not rely on it!
Spring loads bean definitions from the imported configuration in the first place.exa03
Now we will extract the definition of BeanB into a dedicated configuration file:exa04
We will modify the previous exercise so that BeanB depends on BeanA. Spring loads dependent beans in the first place. It does not matter where it's located. Spring will scan all possible configurations to find it.exa05
The same as exa04, but BeanB does not have @DependsOn("beanA"). Instead, BeanB injects BeanA. We have a similar dependency situation to exa4: BanB depends on BeanA. However @PostConstructs are called in a different order: Spring creates an instance of BeanB, and during the construction phase (constructor), references to BeanA are not set. Once the instance of BeanB is created, BeanA will be created and injected into BeanB. BeanB cannot access BeanA in the constructor, first after a complete initialization is done - in the method @PostConstruct.Spring had to modify the order of @PostConstruct calls to ensure that the bean references were not null during an initialization phase.
Summary
- within a single configuration class, the bean instantiation order depends only on the methods order within this class, not method names. However, this might depend on JVM,
- Spring loads, in the first place, imported bean definitions. The previous rule applies only to the situation where direct bean definitions do not depend on imported beans. If this is the case, Spring will load the dependent bean in the first place,
- the initialization code has to be placed in the @PostConstruct method and not in a constructor. Otherwise, references to some beans might be null.
Bean Loading Order - Mixed Config
exb01
BeanA, BeanB, and BeanC are created through dedicated configuration classes: ConfBeanA, ConfBeanB, and ConfBeanC. BeanD is declared in Conf Beans are created in order: A, B, C, D.exb02
Similar to exb01, but the configuration class ConfBeanB has been renamed to ConfBeanXB.The instantiation order remains unchanged.
exb03
The same as exb01, but the factory method in ConfBeanB has been renamed from beanB to xyz. The instantiation order remains unchanged.exb04
The same as exb01, but BeanB has been renamed to BeanXB. This change did not influence instantiation order.exb05
Similar to exb01, but we've changed the import order from A, B, C into B, A, C. The instantiation order has changed as well.exb06
Same as exb01, but BeanC has been injected into BeanA. The instantiation order has been changed because BeanC had to be created before BeanASummary
- when the @Import statement contains multiple classes, Spring will load them from the left to the right side - it's iterating over an array, and names of configuration classes do not matter in this case,
- within single configuration class physical order of methods matters, not their names.
Duplicated Bean Name
exc01
We have 3 beans, A, B, and C, with dedicated config here. BeanA injects BeanB and BeanC.exc02
The same as exc01, but we've renamed factory method ConfBeanB#beanB() into ConfBeanB#beanC(). Spring goes over our @Import declaration to determine all possible bean definitions: ConfBeanA, ConfBeanB, ConfBeanC. There are two factory methods with the same name: ConfBeanB#beanC() and ConfBeanC#beanC(), so ConfBeanC overwrites the bean created through ConfBeanB because it creates a bean with the same name.exc03
Similar to exc02, but this time we've renamed method ConfBeanC#beanC() into ConfBeanC#beanB(), so that we have two methods beanB, and not as it was in exc02 two methods with the name: beanC. The output is still the same, BeanB is missing. We've just changed the name of the factory method, and we already know that names do not change loading order, and we still have two beans with the same name.exc04
Similar to exc02, but we've changed the import order for config classes from A, B, C to A, C, B. Now ConfBeanB will get scanned on end, and it overwrites the previous bean with the same name, so BeanC is missing.exc05
It's a modified exc02. We've set AllowBeanDefinitionOverriding to false on Application Context. Bean overwriting is disabled now, so instead of bean not found, we are getting an exception that we are trying to register two beans under the same name.exc06
The same as exc02, there are still two factory methods: beanC. Additionally, BeanB and BeanC are implementing the common interface. Now BeanA does not inject BeanB and BeanC directly but injects a collection of beans implementing our interface. In the case of direct injection, one of the beans was missing; now both are there!
Summary
In the case of two beans with the same name, the last one wins. You cannot directly inject such overwritten beans, but you can inject a collection of such beans sharing a common interface.Lazy Loading
exd01
We have three beans: A, B, and C, and there are no dependencies between them. BeanC is being defined through a dedicated configuration class. Nothing special here. The initialization order is as expected.exd02
The same as exd02, but we've annotated BeanB as lazy. Spring does not load BeanB at all. There are no dependencies to that bean.exd03
Now we've injected BeanB into BeanA. @Lazy is set on injection point and bean definition. Additionally, we have defined a method on BeanA that calls a method on BeanB: ApplicationExD03 -> beanA.method() -> beanB.method() Spring postpones the creation of BeanB until the method is being called on it, @Lazy works as expected.exd04
Same as exd03, but @Lazy has been removed on the injection point. BeanB is not lazy anymore, @Lazy on bean definition is not enough.exd05
Same as exd03, but @Lazy has been removed from the bean configuration. BeanB is not lazy anymore, @Lazy on bean definition is not enough.Summary
- lazy annotation has to be provided on configuration, and all injection points, otherwise Spring eagerly initializes such beans,
- lazy beans that are not referenced (injected) are not loaded at all,
- you should not rely on @Lazy unless you are 100% sure you can control all possible injection points. Otherwise, one missing Lazy-Annotation will disable laziness on the such bean.