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

Support [package-]private init/destroy methods in AOT mode #30724

Conversation

sbrannen
Copy link
Member

@sbrannen sbrannen commented Jun 22, 2023

Overview

Prior to this commit, private (and non-visible package-private) init/destroy methods were not supported in AOT mode. The reason is that such methods are tracked using their fully-qualified method names, and the AOT support for init/destroy methods previously did not take fully-qualified method names into account. In addition, the invocation order of init/destroy methods differed between standard JVM mode and AOT mode.

This commit addresses these issues in the following ways.

  • AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(), DisposableBeanAdapter.determineDestroyMethod(), and BeanDefinitionPropertiesCodeGenerator.addInitDestroyHint() now parse fully-qualified method names to locate the correct init/destroy methods.

  • AbstractAutowireCapableBeanFactory and DisposableBeanAdapter delegate to a new MethodDescriptor record which encapsulates the parsing of fully qualified method names; however, BeanDefinitionPropertiesCodeGenerator duplicates this logic since it resides in a different package, and we do not currently want to make MethodDescriptor public.

  • Init/destroy methods detected via annotations (such as @PostConstruct and @PreDestroy) are now invoked prior to init/destroy methods that are explicitly configured by name or convention. This aligns with the invocation order in standard JVM mode; however, InitializingBean#afterPropertiesSet() and DisposableBean#destroy() are still invoked before annotated init/destroy methods in AOT mode which differs from standard JVM mode.

  • Unit and integration tests have been updated to test the revised behavior.

Related Issues

@sbrannen sbrannen added type: bug A general bug in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing labels Jun 22, 2023
@sbrannen sbrannen added this to the 6.0.11 milestone Jun 22, 2023
@sbrannen sbrannen self-assigned this Jun 22, 2023
@sbrannen sbrannen requested review from jhoeller and snicoll June 22, 2023 12:11
@sbrannen
Copy link
Member Author

@jhoeller and @snicoll, if either of you have time, I would appreciate a quick review of this PR.

In any case, I think we should decide if we want to address the following (with a separate GitHub issue).

however, InitializingBean#afterPropertiesSet() and DisposableBean#destroy() are still invoked before annotated init/destroy methods in AOT mode which differs from standard JVM mode.

This difference in behavior stems from the fact that we merge init/destroy method names during AOT processing, and that causes @PostConstruct/@PreDestroy methods to be invoked after InitializingBean#afterPropertiesSet() and DisposableBean#destroy() when running in AOT mode.

Prior to this commit, private (and non-visible package-private)
init/destroy methods were not supported in AOT mode. The reason is that
such methods are tracked using their fully-qualified method names, and
the AOT support for init/destroy method previously did not take
fully-qualified method names into account. In addition, the invocation
order of init/destroy methods differed between standard JVM mode and
AOT mode.

This commit addresses these issues in the following ways.

- AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(),
  DisposableBeanAdapter.determineDestroyMethod(), and
  BeanDefinitionPropertiesCodeGenerator.addInitDestroyHint() now parse
  fully-qualified method names to locate the correct init/destroy
  methods.

- AbstractAutowireCapableBeanFactory and DisposableBeanAdapter delegate
  to a new MethodDescriptor record which encapsulates the parsing of
  fully-qualified method names; however,
  BeanDefinitionPropertiesCodeGenerator duplicates this logic since it
  resides in a different package, and we do not currently want to make
  MethodDescriptor public.

- Init/destroy methods detected via annotations (such as @PostConstruct
  and @PreDestroy) are now invoked prior to init/destroy methods that
  are explicitly configured by name or convention. This aligns with the
  invocation order in standard JVM mode; however,
  InitializingBean#afterPropertiesSet() and DisposableBean#destroy()
  are still invoked before annotated init/destroy methods in AOT mode
  which differs from standard JVM mode.

- Unit and integration tests have been updated to test the revised
  behavior.

Closes spring-projectsgh-30692

f
@sbrannen sbrannen force-pushed the issues/gh-30692-aot-private-init-destroy-methods branch from ee6f7b1 to 066dfe3 Compare June 23, 2023 15:41
@sbrannen sbrannen removed request for snicoll and jhoeller June 24, 2023 13:29
@sbrannen
Copy link
Member Author

Fixed in 3181dca

@sbrannen sbrannen closed this Jun 24, 2023
@sbrannen
Copy link
Member Author

sbrannen commented Jun 25, 2023

Reopening to ensure this has been tested in a native image, regarding reflection hints, etc.

@sbrannen
Copy link
Member Author

sbrannen commented Jul 2, 2023

Reopening to ensure this has been tested in a native image, regarding reflection hints, etc.

I have confirmed that the fix works within a GraalVM native image by creating the sample application as described in #30654 and modifying the build.gradle script as follows.

ext['spring-framework.version'] = '6.0.11-SNAPSHOT'

repositories {
	mavenCentral()
	maven {
		url "https://repo.spring.io/snapshot"
	}
}

To be thorough, I also verified the recently introduced support for shadowed package-private init/destroy methods (see #30718).

Using the following classes...

// resides in a different package than the other beans
public class SubpackageSuperBean {

	@PostConstruct
	void init() {
		System.out.println("Init SubpackageSuperBean");
	}

	@PreDestroy
	void destroy() {
		System.out.println("Destroy SubpackageSuperBean");
	}

}
public class SuperBean extends SubpackageSuperBean {

	@PostConstruct
	private void init() {
		System.out.println("Init SuperBean");
	}

	@PreDestroy
	private void destroy() {
		System.out.println("Destroy SuperBean");
	}

}
public class DemoBean extends SuperBean {

	@PostConstruct
	private void init() {
		System.out.println("Init DemoBean");
	}

	@PreDestroy
	private void destroy() {
		System.out.println("Destroy DemoBean");
	}

}

... the output from the native executable is:

Init SubpackageSuperBean
Init SuperBean
Init DemoBean

Destroy DemoBean
Destroy SuperBean
Destroy SubpackageSuperBean

In light of that, I am closing this issue.

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: bug A general bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant