10
.
7
.
2018

Spring Beans Discovery through Java Config in Practice

Spring's Java Config is great tool giving us precise way to control bootstrap and the wiring of an application. However it does not always work as expected and we are running sometimes into some strange issues, where particular bean it not being instantiated at expected time, or lazy loading is not working as expected, or whole configuration is being ignored for no reason.

So I've decided to go over some examples, to get better understanding of how things are in practice.

Examples can be found here: https://github.com/maciejmiklas/spring-context-test.git

Each example has name, like: exa (example A) or exb (example B). Within each example there are several exercises, like: exa has: exa01, exa02, ..., ex05.

Bean Loading Order - Single Config

https://github.com/maciejmiklas/spring-context-test/tree/master/src/main/java/org/test/context/exa

exa01

This is the first exercise, it defines basic structure that will be modified in following chapters.

The main method loads spring context from configuration file Conf.java. This configuration file creates 3 beans. Each of those beans has two log statements, first one in constructor, second in @PostConstruct. We can see also the console output after execution of this example.

github:6d0a51270222d7574b0e55c8a507c3ad

Nothing spectacular happens here: beans are initialised from A to C, right after constructor Spring executes also @PostConstruct - there are not dependencies between beans.

exa02

Lets modify order of factory methods in Conf.java

github:b99bccefcd912c9ff7b53e5282f1ec7f

Bean initialisation order has changed. This means that method names do not matter, but the physical order within class. I can imagine, that this can change with JVM, so you definitely should not rely on it!

exa03

Now we will extract definition of BeanB into dedicated configuration file:

github:75334849d5f173ea59a94330c5d04e21

Spring loads bean definitions from imported configuration in the first place.

exa04

We will modify previous exercise, so that BeanA depends on BeanB.

github:b3c06ae96713570cc711424f094a0154

Spring loads dependent bean in the first place, it does not matter where it's located, Spring will scan all possible configurations to locate it.

exa05

The same as exa04, but BeanA does not have @DependsOn("beanB"), instead BeanA injects BeanB.

github:bef03f99ced5cf9552248674ccc34c60

We have logically similar dependency situation to exa4: BanA depends on BeanB. However @PostConstructs are called in different order: Spring creates instance of BeanA, during construction phase (constructor) references to BeanB are not set. Once the instance of BeanA is created, BeanB will be created and injected to BeanA. BeanA cannot access BeanB in constructor, first after full initialisation is done - in method @PostConstruct.

Spring had to modify order of @PostConstruct calls, to ensure that bean references are not null during initialisation phase.

Summary

  • within a single configuration class the bean instantiation order depends only on methods order within this class and not method names. However this might depend on JVM,
  • Spring loads in the first place imported bean definitions,
  • previous rule applies only to situation where direct bean definitions do not have dependencies on imported beans, if this is the case Spring will load dependent bean in the first place,
  • initialisation code has to be placed in @PostConstruct method and not in a constructor, otherwise references to some beans might be null.

Bean Loading Order - Mixed Config

https://github.com/maciejmiklas/spring-context-test/tree/master/src/main/java/org/test/context/exb

exb01

BeanA, BeanB and BeanC are created through dedicated configuration classes: ConfBeanA, ConfBeanB and ConfBeanC.

github:35230013d2c410a0775b74d3c7f78602

Beans are created in order: A, B, C.

exb02

Similar to exb01, but configuration class ConfBeanB has been renamed to ConfBeanXB

github:e422e4dfb8408c0a04b1eff96938abae

The instantiation order remains unchanged.

exb03

The same as exb01 but factory method in ConfBeanB has been renamed from beanB to xyz.

github:7b30dd95a60eb92e579b57ac3b9e9249

The instantiation order remains unchanged.

exb04

The same as exb01 but BeanB has been renamed to BeanXB.

github:7570ea0f766cd8f016f0a33b355fb647

This change did not influence instantiation order.

exb05

Similar to exb01 but we've changed import order from A, B, C into B, A, C.

github:537255d8db6eb1489c852e4a383a7cad

The instantiation order has changed as well.

Summary

  • when @Import statement contains multiple classes, Spring will load them from the left to the right side - it's iterating over array, 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

github:7a8b64da32d7a94aaf9c20ab80856a89

We have here 3 beans A, B, C with dedicated config.  BeanA injects BeanB and BeanC.

exc02

The same as exc01, but we've renamed factory method ConfBeanB#beanB() into ConfBeanB#beanC()

github:371f581e72adc7d3756719032cfe7369

In order to find out all possible bean definitions Spring goes over our @Import declaration, which is: ConfBeanA, ConfBeanB, ConfBeanC. There are two factory methods with the same name: ConfBeanB#beanC() and ConfBeanC#beanC(), so ConfBeanC overwrites bean created through ConfBeanB, because it creates 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 name: beanC

github:4a587aa22ac297491b0261b57416dafd

The output is still the same, BeanB is missing. We've just changed name of factory method, and we already know that names do not change loading order, and we have still two beans with the same name.

exc04

Similar to exc02, but we've changed import order for config classes from A, B, C to A, C, B.

github:dcad90d4cbc9dc4bd650d4d551651bad

Now ConfBeanB will get scanned on the end and it overwrites 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.

github:33a1c52f5d0f4313fdf1f0ec7464d2c3

Bean overwriting is disabled now, so instead of bean not found we are getting 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 common interface. Now BeanA does not inject directly BeanB and BeanC, but it injects collection of beans implementing our interface.

github:612ea088d56354fb79dbf183b07978b8

In case of direct injection one of the beans was missing, now both are there!

Summary

In case of two beans with the same name the last one wins. You will not be able to directly inject such overwritten bean, but you can inject collection of such beans sharing common interface.

Lazy Loading

exd01

We have here three beans A, B, C, there are no dependencies between them. BeanC is being defined through dedicated configuration class.

github:ac092bd5504e5d0ac23e1a3d7a0c5265

Nothing special here, initialisation order is as expected.

exd02

The same as exd02, but we've annotated BeanB as lazy.

github:96ffa544ba13e6eae7e20bc7674d7028

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 method on BeanA that calls method on BeanB: ApplicationExD03 -> beanA.method() -> beanB.method()

github:9b0e48f6a12fcae9cf963bf8c1209b00

Spring postpones creation of BeanB until method is being called on it, @Lazy works as expected.

exd04

Same as exd03, but @Lazy has been removed on injection point.

github:258ac9b04751b7a17ccf2a00d226f4a6

BeanB is not lazy any more, @Lazy on bean definition is not enough.

exd05

Same as exd03, but @Lazy has been removed from bean configuration.

github:7a04892b1df69656d90e5a6df388fb1b

BeanB is not lazy any more, @Lazy on bean definition is not enough.

Summary

  • lazy annotation has to be provided on configuration and all injection points, otherwise spring eagerly initialises such bean,
  • lazy beans that are not referenced (injected) are not loaded at all,
  • you should not rely on @Lazy, unless you are 100% sure that you can control all possible injection points, otherwise one missing Lazy-Annotation will disable laziness on such bean.
Maciej
Lean Java Expert 

We want to share our experience and expertise. We want to standardize solutions we find. That's why we develop products that reflect our philosophy: lean, agile, data and knowledge driven.

Find out more about our products here.