Skip to content
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

Prevent initializers from being applied twice in AOT mode #32262

Open
sdeleuze opened this issue Sep 8, 2022 · 5 comments
Open

Prevent initializers from being applied twice in AOT mode #32262

sdeleuze opened this issue Sep 8, 2022 · 5 comments
Labels
status: blocked An issue that's blocked on an external project change theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Milestone

Comments

@sdeleuze
Copy link
Contributor

sdeleuze commented Sep 8, 2022

Running the following application prints 2 times Foo constructor at startup while a single time as expected in regular mode.

@SpringBootApplication
public class CommandlinerunnerApplication {

	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(CommandlinerunnerApplication.class);
		app.addInitializers((ApplicationContextInitializer<GenericApplicationContext>) context ->
				context.registerBean(Foo.class));
		app.run(args);
	}
}

public class Foo {

	public Foo() {
		System.out.println("Foo constructor");
	}
}

A look at the generated code shows that Foo__BeanDefinitions generated during AOT transformations based on the initializer is created as expected, but it seems the second invocation comes from the fact that SpringApplication#applyInitializers is still invoked at runtime.

Notice that on native, this second invocation fails with Runtime reflection is not supported for public com.example.commandlinerunner.Foo() because unlike the AOT generated one, it requires ExecutableMode#INVOKE reflection hints (not inferred because not needed by the AOT generated code which is fine with ExecutableMode#INTROSPECT).

Both issues should be solved by removing the second invocation of the initializers, but this need to be explored because it could involve unwanted side effects with some initializers.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Sep 8, 2022
@philwebb
Copy link
Member

philwebb commented Sep 8, 2022

I'm not sure we can skip running ApplicationContextInitializers in AOT mode. In this example, the initializer is registering a bean but it's entirely possible that an initializer does something else entirely. I can't think of any good way to decide which to run and which to skip.

Perhaps the initializer iteself should be updated to not attempt bean registration if running in AOT mode?

@philwebb philwebb added the status: waiting-for-internal-feedback An issue that needs input from a member or another Spring Team label Sep 8, 2022
@wilkinsona
Copy link
Member

wilkinsona commented Sep 9, 2022

Perhaps the initializer iteself should be updated to not attempt bean registration if running in AOT mode?

If this becomes a common pattern, perhaps we can provide a convenience mechanism to do this such as an annotation to add to the initializer class or an interface for it to implement.

@snicoll
Copy link
Member

snicoll commented Sep 9, 2022

I'm not sure we can skip running ApplicationContextInitializers in AOT mode. In this example, the initializer is registering a bean but it's entirely possible that an initializer does something else entirely. I can't think of any good way to decide which to run and which to skip.

This is a problem that has a wider scope. Based on the experience of integrating features in Spring Native, I see the following:

  • Registering bean definitions (with or without a programmatic instance supplier)
  • Mutating the environment
  • Registering bean factory singletons

While the first one looks harmless (just replaying what the AOT-generated code has done), it really isn't. If there is an instance supplier, AOT can't optimize it. If there isn't, then it won't work in a native image as the bean definition contributed by the (replayed) initializer will require reflection.

If we don't run the initializer, then the change to the environment or the presence of singletons is lost. To me, this looks like something that should be addressed in framework somehow. We need to guide users what they can or cannot do if they want to optimize their context Ahead-of-Time as well as the escape hatch if they chose to use something that we can't optimize.

@snicoll
Copy link
Member

snicoll commented Sep 20, 2022

See #32422 for another example that is test-specific. We have ContextCustomizer implementations that are only impacting the BeanRegistry, or register a singleton that is only used as part of preparing the BeanRegistry. As such, running them again is harmless but require the necessary hints.

Rather than contributing hints for something that shouldn't run, I went down the route of using AotDetector from preventing those to run again. I would prefer this to be explicit/declarative rather.

@snicoll snicoll added the for: team-meeting An issue we'd like to discuss as a team to make progress label Sep 20, 2022
@mhalbritter mhalbritter added the theme: aot An issue related to Ahead-of-time processing label Oct 17, 2022
@philwebb philwebb added this to the 3.x milestone Nov 14, 2022
@philwebb philwebb removed the for: team-meeting An issue we'd like to discuss as a team to make progress label Nov 14, 2022
@philwebb
Copy link
Member

I've raised spring-projects/spring-framework#29484 to see if we can have some framework support for this.

@philwebb philwebb added the status: blocked An issue that's blocked on an external project change label Nov 14, 2022
@wilkinsona wilkinsona added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged status: waiting-for-internal-feedback An issue that needs input from a member or another Spring Team labels Jun 5, 2024
@wilkinsona wilkinsona changed the title Prevent initializers to be applied twice in AOT mode Prevent initializers from being applied twice in AOT mode Jun 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: blocked An issue that's blocked on an external project change theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

6 participants