When use a BeanFactory inside my project I have two different result for the same operation after standard build and native build.
The different behavior is related to prototype's scope bean when use the following methods:
- beanFactory.getBean(Class
I have created a simple project in order to explain better my problem spring-boot-test. It is based on spring-boot 3.2.4 with GraalVM
Java version: 17.0.11, vendor: Oracle Corporation
I didn't seen something on documentation, I hope it is my mistake.
Command Pattern
When call the api http://localhost:8080/test/1 the expectation is to set the value "1" using the CommandExample constructor. This approach it works on standard build, with native the beanFactory will create a prototype using always the empty constructor. The result is the command not have any data to proceed with the elaboration.
Initialization Bean
When call the api http://localhost:8080/instance the expectation is to instantiate a BeanInstanceExample class programmatically during the initialization of the ServiceBeanInstanceExample. Also in this case the initialization of the BeanInstanceExample with the autowireBean and initializeBean works fine with the standard build, but not work in a native build.
I hope everything is clear
Comment From: snicoll
Thanks for the report. I can see that the BeanFactory
indeed calls the default constructor of CommandExample
albeit being provided with the integer argument.
The resolution of the constructor is highly dynamic and AOT cannot detect this pattern at build time. I had hoped that providing the necessary reflection hints would fix it but it does not work and will need some further investigation.
Comment From: snicoll
So it turns out that the code path of this method does not ignore an instance supplier if one is defined. This explains why the default constructor is invoked albeit a matching argument being provided.
Please note that creating a bean like this is not idiomatic Spring and we won't be able to infer the necessary hints as we can't detect at build time that this constructor is going to be invoked by reflection.
Comment From: snicoll
I didn't seen something on documentation, I hope it is my mistake.
Yes and no. There was definitely a bug that we used an instance supplier even in the case getBean
was used with custom arguments. So I fixed that but the whole thing is a bit strange with AOT/native.
The instance supplier is used to apply the optimizations we've discovered at build-time. Typically, the autowiring on the field in CommandExample
is processed there, rather than discovering things at runtime. The pattern you've used means that you'd like to instantiate the bean in a custom manner. This has two consequences:
- We don't know the constructor you're going to invoke as the arguments are in your own code with no way to discover them. As such, the sample with this fix would not work until you add some reflection hint for the constructor that takes the
Integer
argument. - Now that the instance supplier is discarded, the autowiring is no longer applied and that field is null.
Looking at your simplified example, I wonder if CommandExample
has to be a bean. IMO it would be better to provide the service and the integer to the constructor, and resolve the service in your controller. I am sure your actual code is more involved but not making it the command a bean is something you should be pursuing for native compatibility. In particular, getBean
with arguments is not recommended. I've created #32690 to document that bit.