As Spring AOT should offer a way to run an application without a locked down BeanFactory, we need a way to keep some of those optimizations.
A typical example is Spring Data AOT repositories: the current optimization generate a class and register the optimized class as fragment using a property on the BeanDefinition. This is quite interesting as we'd be able to do this at runtime, too.
Regardless of this ask, there is also a bit of a gap in the current API: when processing a RegisteredBean is is very much tied to bean registration. We don't really have something more isolated. BeanRegistrationCode allows to add an instance post-processor but it only is invoked with full bean registration. The concept of RegisteredBean is also confusing as we can't really use it to process a bean that may or may not be contributed at runtime.
Looking at the Spring Data use case, it could be the following:
- It is invoked with a matching bean that may or may not be present at runtime
- The same code generation API is provided so that the AOT repository fragment can be generated as before
- An instance callback can be provided, but a customizer of the bean definition as well. In that customizer we'd set the property value on the bean definition.
On the Spring AOT side, we would then need to call whatever was generated if such bean was contributor. We could reuse the existing BeanFactoryPostProcessor (tuning the bean definition) or BeanPostProcessor (instance callback).
The important bit is that the same callback must be used for Spring AOT "JVM" and Spring AOT "native". In the first case, the contribution is conditional while in the other it is invoked directly in the code for populating the bean definition and the instance supplier.
Comment From: SuganthiThomas
Hi, I’m interested in working on this issue. I have experience with Java and Spring Boot. Could I take this up?
Comment From: snicoll
We don't know yet what we're doing to do so I don't really see how you could contribue at this point. If you've analyzed the issue and have a suggestion, then we can start with that.
Comment From: snicoll
BeanRegistrationAotProcessor is strongly focused on the full AOT mode where the bean factory is fully prepared at build-time. Looking at its contract, it mixes several usages:
- The main, intended, usage is to contribute how the bean is going to be instantiated from generated code.
- Register hints
- Contribute additional code.
Contribute how the bean is instantiated
Examples of such usage are:
ScopedProxyBeanRegistrationAotProcessorand many others that need to tune any of the code fragments. That could be tuning the instantiation processor, where the code should be generated, which properties to include, etc. Some of these implementations can be found by searching for override ofBeanRegistrationCodeFragmentsDecorator.AutowiredAnnotationBeanPostProcessorreplaces its behavior by generated code that is registered viaBeanRegistrationCode#addInstancePostProcessor.- Spring Integration's
GatewayProxyInstantiationPostProcessortunes theBeanDefinition, which then has the effect of replacing the runtime post-processor.InitDestroyAnnotationBeanPostProcessoris doing the same thing. Those two examples are problematic as we don't return a contribution, but yet we can modify something. - Compute what the initialization would do and then provide the outcome in the form of generated code. By providing this extra option, the initialization is short-circuited.
PersistenceAnnotationBeanPostProcessorandHttpServiceProxyBeanRegistrationAotProcessorare an example of that.
Register hints
These implementations are not a great use of BeanRegistrationAotProcessor as they don't work on the registration of the bean but rather on a very specific aspect of it. An example is BeanValidationBeanRegistrationAotProcessor that post-processes each bean definitions to validate the hints that should be contributed.
Looking at implementations, a lot are passing GenerationContext to only work on RuntimeHints. This is probably calling for a different callback as it'd be nice if those implementations state more clearly they only contribute hints. We could then not invoke them at all if hints aren't required. Spring HATEOAS is a good example of this.
Contribute Additional Code
These implementations are a bit special as they contribute additional code that is linked via an update of the bean definition. Examples are:
ChildManagementContextInitializerthat invokes AOT processing on a child context. The related bean has then an instance supplier that is invoked when the bean is instantiated. This replaces the actual context refresh by the context that's been AOT-processed.- Spring Data AOT repository that is shared in
RepositoryRegistrationAotProcessor. They clearly show the use case of a feature one would like to opt-in to, with a way to define the various available options. Right now they rely on the environment to do that. See alsoRepositoryContributor. This abstraction has more cases likeJpaRepositoryContributorthat loads the EMF and contribute more data.