After addressing https://github.com/spring-projects/spring-framework/issues/30410 we still see failures wiring our GRPC client beans. The registration is:
/**
* Get the bean definition for 'grpcClient_myclient'.
*/
public static BeanDefinition getGrpcClientmyclientBeanDefinition() {
Class<?> beanType = StubFactoryBean.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, MyClientBlockingStub.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "myclient");
beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.demo.GrpcSpringClient", "myclient"));
beanDefinition.setInstanceSupplier(getGrpcClientmyclientInstanceSupplier());
return beanDefinition;
}
With AOT enabled the application fails to start with:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.demo.DemoApplication$RequiresGrpcClient required a bean of type 'com.example.demo.MyClientBlockingStub' that could not be found.
The injection point has the following annotations:
- @com.example.demo.GrpcSpringClient("myclient")
Without AOT, the bean is found successfully via:
doGetBeanNamesForType:584, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeanNamesForType:540, DefaultListableBeanFactory (org.springframework.beans.factory.support)
beanNamesForTypeIncludingAncestors:260, BeanFactoryUtils (org.springframework.beans.factory)
findAutowireCandidates:1581, DefaultListableBeanFactory (org.springframework.beans.factory.support)
doResolveDependency:1380, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:885, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:789, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:245, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:326, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$286/0x00000008011a2070 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:324, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:200, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:973, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:917, AbstractApplicationContext (org.springframework.context.support)
refresh:584, AbstractApplicationContext (org.springframework.context.support)
refresh:732, SpringApplication (org.springframework.boot)
refreshContext:434, SpringApplication (org.springframework.boot)
run:310, SpringApplication (org.springframework.boot)
run:1304, SpringApplication (org.springframework.boot)
run:1293, SpringApplication (org.springframework.boot)
main:17, DemoApplication (com.example.demo)
However, with AOT FactoryBean.getObjectType()
isn't even called on the StubFactoryBean
.
Example project:
https://github.com/DanielThomas/spring-aot-issues/tree/dannyt/factory-beans
Run and note the failure:
./gradlew bootJar && java -Dspring.aot.enabled=true -jar build/libs/demo-0.0.1-SNAPSHOT.jar
Comment From: snicoll
Thanks for the reproducer. FactoryBean
with generics are challenging as they often reveal the bean target type at runtime and AOT needs to capture the type at build-time. We can't really instantiate factory beans at build-time as it would have a cascading effect in terms of the things that we instantiate.
The ApplicationContext
has several fallbacks that makes the use case you've shared work at runtime, but I believe the current arrangement is not optimal:
StubFactoryBean
declares to resolve theFactoryBean
generic toAbstractStub
but it builds a sub-class and the injection point requires said sub-class.- The only place where the actual type is exposed is via a custom constructor argument (
MyClientBlockingStub
), so there's no way for us to know about the type. - A
RootBeanDefinition
is a better and more optimized type for this, and I don't think the current javadoc reflects that very well so we'll have to do something about that.
I've pushed an update to your sample to demonstrate a cleaner arrangement (regardless of AOT): https://github.com/DanielThomas/spring-aot-issues/pull/1
The keys are:
- The
beanClass
should be theFactoryBean
class so that the container knows it's not any other bean but something that produces a bean - The resolvable type should be set to a generic type for the FactoryBean class where
T
is as precise as possible.
With those changes in place, the sample starts. We have a few things to do on our side (ping @jhoeller):
- Review the javadoc of
GenericBeanDefinition
- Add a section in the reference guide regarding
FactoryBean
and AOT - We probably want to offer a way to set the resolvable type on
BeanDefinitionBuilder
.
Let me know what you think.
Comment From: DanielThomas
The raw generics felt off to me, so thanks for the pointers. Much appreciated @snicoll.
Comment From: snicoll
I've created two issues:
-
30444
-
30445
I'll reuse this issue to document how to handle factory beans with AOT.
Comment From: DanielThomas
@snicoll we ran into a similar issue, but with FactoryBean
defined in a configuration:
@Bean
public FactoryBean<MutableSpan> spanFactory() {
return new SpanFactory();
}
Where SpanFactory
is:
@Component
public class SpanFactory implements FactoryBean<MutableSpan>
The generated definition obviously doesn't have the type parameters, so can't be wired at runtime:
/**
* Get the bean definition for 'spanFactory'.
*/
public static BeanDefinition getSpanFactoryBeanDefinition() {
ResolvableType beanType = ResolvableType.forClassWithGenerics(FactoryBean.class, MutableSpan.class);
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(getSpanFactoryInstanceSupplier());
return beanDefinition;
}
What pattern do you recommend when using AOT with these?
Comment From: snicoll
You should return SpanFactory
, not FactoryBean<MutableSpan>
. If the exposed type is not precise enough, there's no way for us to know what to honor statically. For instance, if SpanFactory
needs callback for dependencies (autowiring) they won't happen as a FactoryBean
does not need that. This is documented here.
I am not sure what you mean by "similar issue" though. Can you please check and if that is still failing, a new issue with a reproducer would be much appreciated.