Skip to content

Document how to configure FactoryBean with a configurable target with AOT #30434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
DanielThomas opened this issue May 5, 2023 · 5 comments
Closed
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing type: documentation A documentation task
Milestone

Comments

@DanielThomas
Copy link

DanielThomas commented May 5, 2023

After addressing #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
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label May 5, 2023
@snicoll snicoll self-assigned this May 6, 2023
@snicoll snicoll added in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing labels May 6, 2023
@snicoll snicoll changed the title FactoryBeans do not work with AOT Bean produced by a FactoryBean with a configurable target class is not found by type with AOT May 6, 2023
@snicoll
Copy link
Member

snicoll commented May 6, 2023

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): DanielThomas/spring-aot-issues#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.

@snicoll snicoll added the status: waiting-for-feedback We need additional information before we can continue label May 6, 2023
@DanielThomas
Copy link
Author

The raw generics felt off to me, so thanks for the pointers. Much appreciated @snicoll.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels May 7, 2023
@snicoll
Copy link
Member

snicoll commented May 8, 2023

I've created two issues:

I'll reuse this issue to document how to handle factory beans with AOT.

@snicoll snicoll changed the title Bean produced by a FactoryBean with a configurable target class is not found by type with AOT Document how to configure FactoryBean with a configurable target with AOT May 8, 2023
@snicoll snicoll added type: documentation A documentation task and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels May 8, 2023
@snicoll snicoll modified the milestones: 6.0.x, 6.0.9 May 8, 2023
@snicoll snicoll closed this as completed in 93fa010 May 9, 2023
@DanielThomas
Copy link
Author

@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?

@snicoll
Copy link
Member

snicoll commented Jun 15, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

3 participants