Hi team,
I'm new to spring aot, and when doing process-aot, I got exception :
Caused by: java.lang.IllegalArgumentException: Code generation does not support com.test.MyBean
at org.springframework.beans.factory.aot.BeanDefinitionPropertyValueCodeGenerator.generateCode(BeanDefinitionPropertyValueCodeGenerator.java:134)
...
this is the code to regist myBean. It comes from another framework and will be called by an ApplicationListener.
AbstractBeanDefinition beanDefinition = genericBeanDefinition(MyBean.class).addConstructorArgValue(bean).getBeanDefinition();
beanDefinition.setSource(bean);
beanDefinition.setRole(ROLE_APPLICATION);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(beanName, beanDefinition);
Affects: Spring 6.0.11
I think the processing of spring aot will not want to deal with beans registered by users or other frameworks. And I don't find a way to supply a customValueGenerator for BeanDefinitionPropertyValueCodeGenerator. And I have no idea how to write a proper CodeBlock for myBean.
Since the registration code is not under my control, can I have a way to skip this or have any suggestions to deal with this matter? Thank you for your help.
Comment From: snicoll
Thanks for the report. The problem is not so much that this is a dynamic registered bean, but that it is a bean definition that refers to an instance for which we can't generate the code.
There's also a bit of a smell with ((BeanDefinitionRegistry) beanFactory). You shouldn't do that as it indicates that this code is trying to register a bean definition at the wrong phase.
I don't know where bean comes from in that code above so I can't offer much of an advice. Would you be able to provide more details?
Comment From: dpy1123
yes, above is a simplified code snippet for registerBeanInstance.
the bean it registed in an ApplicationListener:
@Override
protected void onApplicationEvent(SpringApplication springApplication, String[] args, ConfigurableApplicationContext context) {
BeanDefinitionRegistry registry = resolveBeanDefinitionRegistry(context);
// Register ZoneContext as Spring Bean
ZoneContext zoneContext = ZoneContext.get();
registerBeanInstance(ZONE_CONTEXT_BEAN_NAME, zoneContext, registry);
}
and the bean is ZoneContext
Comment From: snicoll
That's a bit odd. First of all, you need to make sure that there isn't a bean with that name. Then if what you want to register is this, why don't you call registerSingleon? I am expecting that code to run again on an AOT-processed context since it's trying to register a bean definition outside of the boundaries of the bean registry.
If you need to register a bean definition for another reason, then you can configure the BeanDefinition to specify that it should call ZoneContext#get to produce the bean, rather than doing this yourself.
Comment From: dpy1123
uh, sorry let me explain the thing again.
private void registerZoneContext(BeanDefinitionRegistry registry) {
ZoneContext zoneContext = ZoneContext.get();
AbstractBeanDefinition beanDefinition = genericBeanDefinition(SpringUtils.DelegatingFactoryBean.class)
.addConstructorArgValue(zoneContext)
.getBeanDefinition();
beanDefinition.setSource(zoneContext);
SpringUtils.registerBeanDefinition(ZONE_CONTEXT_BEAN_NAME, beanDefinition, registry);
}
this is the original version of code, and SpringUtils code here, get error:
Message:java.lang.IllegalArgumentException: Failed to generate code for 'ZoneContext{enabled=true, zone='defaultZone', preferenceEnabled=false, preferenceFilterOrder=10, preferenceUpstreamZoneReadyPercentage=100, preferenceUpstreamSameZoneMinAvailable=5, preferenceUpstreamDisabledZone=null}' with type com.xxx.zone.ZoneContext
and anthoer version is :
private void registerZoneLocator(BeanDefinitionRegistry registry, ConfigurableApplicationContext context) {
List<ZoneLocator> zoneLocators = SpringUtils.loadSpringFactories(context, ZoneLocator.class);
CompositeZoneLocator compositeZoneLocator = new CompositeZoneLocator(zoneLocators);
AbstractBeanDefinition beanDefinition = genericBeanDefinition(CompositeZoneLocator.class)
.addConstructorArgValue(zoneLocators)
.getBeanDefinition();
beanDefinition.setSource(compositeZoneLocator);
SpringUtils.registerBeanDefinition(ZONE_LOCATOR_BEAN_NAME, beanDefinition, registry);
}
where zoneLocators is a list of ZoneLocator registered in spring.factories , and init method of CompositeZoneLocator is :
public CompositeZoneLocator(List<ZoneLocator> zoneLocators) {
Assert.notNull(zoneLocators, "The argument 'zoneLocators' must not be null!");
this.zoneLocators = new ArrayList(zoneLocators);
AnnotationAwareOrderComparator.sort(this.zoneLocators);
}
get error:
Message:java.lang.IllegalArgumentException: Failed to generate code for '[ZoneLocator{beanName='ecsContainerMetadataFileZoneLocator', order=5}, ZoneLocator{beanName='ecsTaskMetadataEndpointV4ZoneLocator', order=10}, ZoneLocator{beanName='ec2AvailabilityZoneEndpointZoneLocator', order=15}, ZoneLocator{beanName='defaultZoneLocator', order=20}]' with type java.util.Collections$UnmodifiableRandomAccessList<?>
Comment From: snicoll
It's exactly the same thing. You shouldn't register a bean definition where the computation of the actual instance is done programmatically this way. AOT will never be able to handle that.
In other words, you could do something like this:
public CompositeZoneLocator createCompositeZoneLocator(ConfigurableApplicationContext context) {
List<ZoneLocator> zoneLocators = SpringUtils.loadSpringFactories(context, ZoneLocator.class);
return new CompositeZoneLocator(zoneLocators);
}
And then create a RootBeanDefinition that states that the factory method is createCompositeZoneLocator. You could make that method static to avoid the need to register the type to avoid having to register a an extra bean for it.
You could also avoid all that fuss by copying those 3 lines in a @Configuration class and stick @Bean on the method.
Comment From: dpy1123
I change the code as ur suggestion, and I try to use debug mode to run SpringApplicationAotProcessor, the init function runs okay, but at last get:
An exception has been raised by Name:main,ThreadId:1,Message:java.lang.IllegalArgumentException: Failed to generate code for 'org.springframework.context.annotation.AnnotationConfigApplicationContext@6848a051, started on Tue Sep 26 11:43:34 GST 2023' with type org.springframework.context.annotation.AnnotationConfigApplicationContext
at org.springframework.beans.factory.aot.BeanDefinitionPropertyValueCodeGenerator.generateCode(BeanDefinitionPropertyValueCodeGenerator.java:102)
at org.springframework.beans.factory.aot.BeanDefinitionPropertiesCodeGenerator.generateValue(BeanDefinitionPropertiesCodeGenerator.java:220)
at org.springframework.beans.factory.aot.BeanDefinitionPropertiesCodeGenerator.lambda$addConstructorArgumentValues$3(BeanDefinitionPropertiesCodeGenerator.java:171)
at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
at java.base/java.util.Collections$UnmodifiableMap.forEach(Collections.java:1553)
at org.springframework.beans.factory.aot.BeanDefinitionPropertiesCodeGenerator.addConstructorArgumentValues(BeanDefinitionPropertiesCodeGenerator.java:170)
at org.springframework.beans.factory.aot.BeanDefinitionPropertiesCodeGenerator.generateCode(BeanDefinitionPropertiesCodeGenerator.java:122)
at org.springframework.beans.factory.aot.DefaultBeanRegistrationCodeFragments.generateSetBeanDefinitionPropertiesCode(DefaultBeanRegistrationCodeFragments.java:167)
at org.springframework.beans.factory.aot.BeanRegistrationCodeGenerator.generateCode(BeanRegistrationCodeGenerator.java:86)
at org.springframework.beans.factory.aot.BeanDefinitionMethodGenerator.lambda$generateBeanDefinitionMethod$3(BeanDefinitionMethodGenerator.java:194)
at org.springframework.aot.generate.GeneratedMethod.<init>(GeneratedMethod.java:54)
at org.springframework.aot.generate.GeneratedMethods.add(GeneratedMethods.java:112)
at org.springframework.aot.generate.GeneratedMethods.add(GeneratedMethods.java:89)
at org.springframework.beans.factory.aot.BeanDefinitionMethodGenerator.generateBeanDefinitionMethod(BeanDefinitionMethodGenerator.java:188)
at org.springframework.beans.factory.aot.BeanDefinitionMethodGenerator.generateBeanDefinitionMethod(BeanDefinitionMethodGenerator.java:109)
at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.lambda$generateRegisterBeanDefinitionsMethod$2(BeanRegistrationsAotContribution.java:93)
at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.generateRegisterBeanDefinitionsMethod(BeanRegistrationsAotContribution.java:91)
at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.lambda$applyTo$1(BeanRegistrationsAotContribution.java:76)
at org.springframework.aot.generate.GeneratedMethod.<init>(GeneratedMethod.java:54)
at org.springframework.aot.generate.GeneratedMethods.add(GeneratedMethods.java:112)
at org.springframework.aot.generate.GeneratedMethods.add(GeneratedMethods.java:89)
at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.applyTo(BeanRegistrationsAotContribution.java:75)
at org.springframework.context.aot.BeanFactoryInitializationAotContributions.applyTo(BeanFactoryInitializationAotContributions.java:78)
at org.springframework.context.aot.ApplicationContextAotGenerator.lambda$processAheadOfTime$0(ApplicationContextAotGenerator.java:58)
at org.springframework.context.aot.ApplicationContextAotGenerator.withCglibClassHandler(ApplicationContextAotGenerator.java:67)
at org.springframework.context.aot.ApplicationContextAotGenerator.processAheadOfTime(ApplicationContextAotGenerator.java:53)
at org.springframework.context.aot.ContextAotProcessor.performAotProcessing(ContextAotProcessor.java:106)
at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:84)
at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:49)
at org.springframework.context.aot.AbstractAotProcessor.process(AbstractAotProcessor.java:82)
at org.springframework.boot.SpringApplicationAotProcessor.main(SpringApplicationAotProcessor.java:80)
Caused by: java.lang.IllegalArgumentException: Code generation does not support org.springframework.context.annotation.AnnotationConfigApplicationContext
at org.springframework.beans.factory.aot.BeanDefinitionPropertyValueCodeGenerator.generateCode(BeanDefinitionPropertyValueCodeGenerator.java:134)
at org.springframework.beans.factory.aot.BeanDefinitionPropertyValueCodeGenerator.generateCode(BeanDefinitionPropertyValueCodeGenerator.java:99)
... 31 more
code for regiestion
RootBeanDefinition beanDefinition = new RootBeanDefinition();
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, context);
beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
beanDefinition.setFactoryMethodName("createCompositeZoneLocator");
beanDefinition.setBeanClass(CompositeZoneLocator.class);
SpringUtils.registerBeanDefinition(ZONE_LOCATOR_BEAN_NAME, beanDefinition, registry);
Comment From: snicoll
This is going in circles I am afraid. You've fixed one problem to reintroduce another one. Now you're not asking AOT to generate the code that represents MyBean, but the code that represents the ConfigurableApplicationContext.
If you insist on creating a bean definition for this, you need to provide the metadata so that the context can find the necessary dependencies at runtime. My suggestion above was to use autowiring. It is enabled by default if you create a @Bean method (which is what I meant by removing all that fuss). If you can't do that (why?) then you need to opt-in for that in your custom code.
Something along those lines:
RootBeanDefinition beanDefinition = new RootBeanDefinition(CompositeZoneLocator.class);
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDefinition.setFactoryMethodName("createCompositeZoneLocator");
SpringUtils.registerBeanDefinition(ZONE_LOCATOR_BEAN_NAME, beanDefinition, registry);
Note that the constructor bit in the autowire mode really reflects the constructor or factory method as the core container considers those as equivalent.
Comment From: dpy1123
yes, I find that if I register a bean with none emplty contructor, aot will always try to generation code for the contruction params, witch cause the error because it doesn't have the proper Delegate.
I also tried the autowire_constructor way, but get "No qualifying bean available".
And CompositeZoneLocator is used in an ApplicationListener\<ApplicationPreparedEvent>, if I register it through @Bean in a @Configuration class, it still says "No qualifying bean available".
If you insist on creating a bean definition for this, you need to provide the metadata so that the context can find the necessary dependencies at runtime.
Could you tell me more about this? is there any demo code shows how to provide a metadata and what may the metadata looks like in this case? thx
Comment From: snicoll
I also tried the autowire_constructor way, but get "No qualifying bean available".
@dpy1123 the code above works, I added it to a sample before copy/pasting it here.
is there any demo code shows how to provide a metadata and what may the metadata looks like in this case?
I did that already. That's the "beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);" bit.
I think this conversation has run its course. If you need more support, please ask on StackOverflow. I'd suggest to add a sample that users can use to reproduce the problem as I can't see now why it's not working for you.