Understanding inter-bean method reference in Spring configuration
Dec 15, 2020 2022-03-17 10:06Understanding inter-bean method reference in Spring configuration
Understanding inter-bean method reference in Spring configuration
In this post, we’ll explore how inter-bean method reference works and why you should avoid it if you plan to generate GraalVM native images.
Java configuration in Spring gives you full control over the creation and initialization of your beans. There are multiple ways to inject or get a reference of a bean instance, one of them is calling the method defining it (also called inter-bean or cross bean method reference). Here’s an example:
@Configuration public class SomeConfiguration { @Bean public SharedDependency sharedDependency() { return new SharedDependency(); } @Bean public FirstService firstService() { return new FirstService(sharedDependency()); } @Bean public SecondService secondService() { return new SecondService(sharedDependency()); } }
This also works when calling bean methods from other configuration classes. Looking at the previous example and thinking in pure Java, our shared dependency will be initialized multiple times, but Spring will guarantee we end up with a single instance of it (preserving the scope of the bean which is by default singleton). How is this possible?
CGLIB enhanced configuration classes
During the initialization of the context, Spring will generate enhanced configuration classes (by creating a CGLIB proxy/subclass) to make those inter-bean method references possible. You will see it while debugging or by checking the actuator beans endpoint, the configuration class will now be of a type similar to SomeConfiguration$$EnhancerBySpringCGLIB$$4b5569b2.
Your method invocation will be intercepted to determine if the bean has already been created, is such a case, the existing cached instance will be returned otherwise your configuration class method will be called to create the bean. This guarantees your initialization code is called just once (assuming singleton scope). This default behavior is also referred to as full configuration classes.
Lite Beans
As opposed to full configuration classes, we have also the lite bean mode. Generating proxies for configuration classes takes some time and might affect slightly your application startup time, if you are not calling @Bean methods directly, you don’t need those proxies.
If @Bean methods are defined in a non-@Configuration class, those are not going to be intercepted and will be considered lite beans. Let’s change our configuration class and define our beans in a @Service:
@Service public class SomeConfiguration { @Bean public SharedDependency sharedDependency() { return new SharedDependency(); } @Bean public FirstService firstService() { return new FirstService(sharedDependency()); } @Bean public SecondService secondService() { return new SecondService(sharedDependency()); } }
In the above configuration, we will effectively create 3 instances of our shared dependency as opposed to 1.
Avoiding proxies in configuration classes
Even if this works, It is confusing to define beans in a @Service (or any other stereotype) class. Semantically a @Configuration class makes more sense.
The @Configuration annotation allows you to define the proxyBeanMethods=false property to avoid generating proxies and acting in a lite bean mode. You won’t be able to use inter-bean references, but this can be easily refactored by passing the dependencies as method arguments. Let’s see the refactored configuration class:
@Configuration(proxyBeanMethods = false) public class SomeConfiguration { @Bean public SharedDependency sharedDependency() { return new SharedDependency(); } @Bean public FirstService firstService(SharedDependency sharedDependency) { return new FirstService(sharedDependency); } @Bean public SecondService secondService(SharedDependency sharedDependency) { return new SecondService(sharedDependency); } }
What does it mean for the GraalVM story?
Compiling your Spring application to a native executable gives you several benefits like an extremely fast startup or a lower memory footprint, but GraalVM comes with its limitations. One of these limitations is not supporting dynamic class loading, and that means CGLIB proxies won’t work with native images.
If you want to be native compatible make sure you always mark your @Configuration classes with proxyBeanMethods = false. If you are using Spring Native for GraalVM, your configuration classes will always behave according to the lite bean mode even if not specified. To avoid unexpected behaviors, Spring Native for GraalVM will complain if you use inter-bean method references.
java.lang.IllegalStateException: ERROR: in 'training.spring.samples.NativeApplication' these methods are directly invoking methods marked @Bean: [sharedDependency] - due to the enforced proxyBeanMethods=false for components in a native-image, please consider refactoring to use instance injection. If you are confident this is not going to affect your application, you may turn this check off using -Dspring.native.verify=false.
Always defining your configuration classes with proxyBeanMethods = false is a good idea, and it might even become the default in future releases of Spring Framework.
Comment (1)
Michael I
nice blog Sergi! I didn’t know about lite beans.
Comments are closed.