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:

  1. StubFactoryBean declares to resolve the FactoryBean generic to AbstractStub but it builds a sub-class and the injection point requires said sub-class.
  2. 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.
  3. 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 the FactoryBean 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.