Overview
ApplicationContextAotGenerator
currently does not support a target component for the name of the generated ApplicationContextInitializer
. This is sufficient if you are processing a single ApplicationContext
for AOT. It's also sufficient if you do not want the generated initializers to reside in the same package as the target component.
However, for the AOT support in the TestContext framework we would like to generate an ApplicationContextInitializer
per test class (or more specifically per unique MergedContextConfiguration
) in the same package as the original test class.
Status Quo
With the ClassNameGenerator
configured with org.springframework.test.context.aot.TestContextAotGenerator
as the default target class, we get the following by default, which does not create the classes in the test's package.
org.springframework.test.context.aot.TestContextAotGenerator__ApplicationContextInitializer
org.springframework.test.context.aot.TestContextAotGenerator__ApplicationContextInitializer1
org.springframework.test.context.aot.TestContextAotGenerator__ApplicationContextInitializer2
If we pass generationContext.withName(testClass.getName())
to ApplicationContextAotGenerator
, we get the following, which is very verbose and still not in the test's package.
org.springframework.test.context.aot.TestContextAotGenerator__Org.springframework.test.context.aot.samples.basic.BasicSpringVintageTestsApplicationContextInitializer
org.springframework.test.context.aot.TestContextAotGenerator__Org.springframework.test.context.aot.samples.basic.BasicSpringJupiterTestsApplicationContextInitializer
org.springframework.test.context.aot.TestContextAotGenerator__Org.springframework.test.context.aot.samples.basic.BasicSpringTestNGTestsApplicationContextInitializer
If we pass generationContext.withName(testClass.getSimpleName())
to ApplicationContextAotGenerator
, we get the following, which is not as verbose but still not in the test's package.
org.springframework.test.context.aot.TestContextAotGenerator__BasicSpringTestNGTestsApplicationContextInitializer
org.springframework.test.context.aot.TestContextAotGenerator__BasicSpringJupiterTestsApplicationContextInitializer
org.springframework.test.context.aot.TestContextAotGenerator__BasicSpringVintageTestsApplicationContextInitializer
Whereas, if we allow the "target component class" to be specified we can achieve the following.
org.springframework.test.context.aot.samples.basic.BasicSpringTestNGTests__ApplicationContextInitializer
org.springframework.test.context.aot.samples.basic.BasicSpringVintageTests__ApplicationContextInitializer
org.springframework.test.context.aot.samples.basic.BasicSpringJupiterTests__ApplicationContextInitializer
Deliverables
- [ ] Introduce support for targeting a component in
ApplicationContextAotGenerator
andApplicationContextInitializationCodeGenerator
.
Comment From: sbrannen
Current work on this issue can be viewed in the following feature branch.
https://github.com/sbrannen/spring-framework/commits/issues/gh-28928-ApplicationContextAotGenerator-target-component
Note, however, that I am investigating a different approach that would override the default target type for each invocation of processAheadOfTime()
(analogous to GenerationContext#withName(String)
) instead of supplying a targetComponent
. The following explains the rationale for investigating a different approach.
The current tests show that BeanFactoryRegistrations
are still generated in a class whose name is based on the default target type (which is TestTarget
for the TestGenerationContext
used in the tests):
org/springframework/core/testfixture/aot/generate/TestTarget__BeanFactoryRegistrations.java
org/springframework/core/testfixture/aot/generate/TestTarget__BeanFactoryRegistrations1.java
Whereas, ideally these would be:
org/springframework/context/testfixture/context/generator/SimpleComponent__BeanFactoryRegistrations.java
org/springframework/context/testfixture/context/generator/annotation/AutowiredComponent__BeanFactoryRegistrations.java
Comment From: sbrannen
Commit https://github.com/sbrannen/spring-framework/commit/a15133942979b432a6b111c3b8459cd9d2f2b2fc reverts all changes to ApplicationContextAotGenerator
and ApplicationContextInitializationCodeGenerator
and addresses the underlying issue by introducing support for forking the GenerationContext
for a particular target class.
This new approach simplifies the programming model for clients of ApplicationContextAotGenerator
that need to invoke processAheadOfTime()
for multiple application contexts while retaining state within a single code generation/compilation phase.
Comment From: snicoll
GenerationContext
should not have such capability. When AOT runs on a given ApplicationContext
, it has a given target and allowing processors to change it while the context is being processed looks broken to me.
There are essentially two ways to fix this issue that I can see based on several discussions with @philwebb.
We could add such a method to DefaultGenerationContext
which looks pretty much the same as your proposal, except the capability is not exposed to consumers of GenerationContext
. Here is an example.
Or we could allow the creation of a GenerationContext
per invocation. This means that we create the stateful resources at the beginning (files and hints) and we reuse them in fresh context created for each run. Here is an updated example (doesn't compile atm).
There is an additional problem regardless of which approach we pick. We can't strictly speaking rely on withName
to provide a unique prefix for the test as processors can invoke this and therefore "reset" the name that was set. We should probably update ClassNameGenerator
to be more flexible and retain the "Prefix of the prefix" if withName
is used. If we do this, then we have the guarantee that each invocation will not create similar resources. The counter in DefaultGenerationContext
doesn't then need to be transmitted from one context to another.
Comment From: sbrannen
Or we could allow the creation of a
GenerationContext
per invocation. This means that we create the stateful resources at the beginning (files and hints) and we reuse them in fresh context created for each run. Here is an updated example (doesn't compile atm).
This is effectively the route I've gone in the current implementation of #28204. The following are the only changes I had to make to core AOT infrastructure:
- introduction of a public
DefaultGenerationContext(ClassNameGenerator,GeneratedFiles,RuntimeHints)
constructor that allows me to provide aClassNameGenerator
for each test class for which we need to create a unique set of AOT artifacts. - changed the return type of
DefaultGenerationContext.withName(String)
fromGenerationContext
toDefaultGenerationContext
.
In light of that, let's hold off on providing support for targeting a component -- as I originally proposed -- until the AOT work for the TestContext framework is further along.
There is an additional problem regardless of which approach we pick. We can't strictly speaking rely on
withName
to provide a unique prefix for the test as processors can invoke this and therefore "reset" the name that was set. We should probably updateClassNameGenerator
to be more flexible and retain the "Prefix of the prefix" ifwithName
is used. If we do this, then we have the guarantee that each invocation will not create similar resources. The counter inDefaultGenerationContext
doesn't then need to be transmitted from one context to another.
I created #28974 to address this particular issue.
Comment From: snicoll
Should this still be opened?
Comment From: sbrannen
Superseded by:
- commit e5f9bb76b1207711732b843ec228c10f51a9ec64
- commit 9ab046bdbbec7b5f29284c5586e454bc8880a621
-
28974
-
29076