Add support of init and destroy methods

This commit updates InitDestroyBeanPostProcessor so that it contributes
init or destroy method names to the `RootBeanDefinition`. This is then
used by the generator to provide these methods to the optimized AOT
context.

Invocation of those init methods still happen using reflection so
dedicated hints are contributed for them.

Closes gh-28151
This commit is contained in:
Stephane Nicoll
2022-03-10 11:20:16 +01:00
parent 1b7892c559
commit 672555a568
10 changed files with 458 additions and 18 deletions

View File

@@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -39,6 +40,8 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
@@ -46,6 +49,7 @@ import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
/**
@@ -72,13 +76,14 @@ import org.springframework.util.ReflectionUtils;
* for annotation-driven injection of named beans.
*
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 2.5
* @see #setInitAnnotationType
* @see #setDestroyAnnotationType
*/
@SuppressWarnings("serial")
public class InitDestroyAnnotationBeanPostProcessor
implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, Serializable {
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor,
MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, PriorityOrdered, Serializable {
private final transient LifecycleMetadata emptyLifecycleMetadata =
new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) {
@@ -146,8 +151,36 @@ public class InitDestroyAnnotationBeanPostProcessor
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
findInjectionMetadata(beanDefinition, beanType);
}
@Override
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
LifecycleMetadata metadata = findInjectionMetadata(beanDefinition, beanType);
if (!CollectionUtils.isEmpty(metadata.initMethods)) {
String[] initMethodNames = safeMerge(
beanDefinition.getInitMethodNames(), metadata.initMethods);
beanDefinition.setInitMethodNames(initMethodNames);
}
if (!CollectionUtils.isEmpty(metadata.destroyMethods)) {
String[] destroyMethodNames = safeMerge(
beanDefinition.getDestroyMethodNames(), metadata.destroyMethods);
beanDefinition.setDestroyMethodNames(destroyMethodNames);
}
return null;
}
private LifecycleMetadata findInjectionMetadata(RootBeanDefinition beanDefinition, Class<?> beanType) {
LifecycleMetadata metadata = findLifecycleMetadata(beanType);
metadata.checkConfigMembers(beanDefinition);
return metadata;
}
private String[] safeMerge(@Nullable String[] existingNames, Collection<LifecycleElement> detectedElements) {
Stream<String> detectedNames = detectedElements.stream().map(LifecycleElement::getIdentifier);
Stream<String> mergedNames = (existingNames != null
? Stream.concat(Stream.of(existingNames), detectedNames) : detectedNames);
return mergedNames.distinct().toArray(String[]::new);
}
@Override

View File

@@ -55,6 +55,7 @@ import org.springframework.javapoet.support.MultiStatement;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@@ -121,6 +122,14 @@ public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContr
* @param runtimeHints the runtime hints to use
*/
void registerRuntimeHints(RuntimeHints runtimeHints) {
String[] initMethodNames = this.beanDefinition.getInitMethodNames();
if (!ObjectUtils.isEmpty(initMethodNames)) {
registerInitDestroyMethodsRuntimeHints(initMethodNames, runtimeHints);
}
String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames();
if (!ObjectUtils.isEmpty(destroyMethodNames)) {
registerInitDestroyMethodsRuntimeHints(destroyMethodNames, runtimeHints);
}
registerPropertyValuesRuntimeHints(runtimeHints);
}
@@ -191,6 +200,15 @@ public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContr
return this.beanInstantiationGenerator.generateBeanInstantiation(runtimeHints);
}
private void registerInitDestroyMethodsRuntimeHints(String[] methodNames, RuntimeHints runtimeHints) {
for (String methodName : methodNames) {
Method method = ReflectionUtils.findMethod(getUserBeanClass(), methodName);
if (method != null) {
runtimeHints.reflection().registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE));
}
}
}
private void registerPropertyValuesRuntimeHints(RuntimeHints runtimeHints) {
if (!this.beanDefinition.hasPropertyValues()) {
return;
@@ -357,6 +375,14 @@ public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContr
private void handleBeanDefinitionMetadata(Builder code) {
String bdVariable = determineVariableName("bd");
MultiStatement statements = new MultiStatement();
String[] initMethodNames = this.beanDefinition.getInitMethodNames();
if (!ObjectUtils.isEmpty(initMethodNames)) {
handleInitMethodNames(statements, bdVariable, initMethodNames);
}
String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames();
if (!ObjectUtils.isEmpty(destroyMethodNames)) {
handleDestroyMethodNames(statements, bdVariable, destroyMethodNames);
}
if (this.beanDefinition.isPrimary()) {
statements.addStatement("$L.setPrimary(true)", bdVariable);
}
@@ -399,6 +425,26 @@ public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContr
code.add(")");
}
private void handleInitMethodNames(MultiStatement statements, String bdVariable, String[] initMethodNames) {
if (initMethodNames.length == 1) {
statements.addStatement("$L.setInitMethodName($S)", bdVariable, initMethodNames[0]);
}
else {
statements.addStatement("$L.setInitMethodNames($L)", bdVariable,
this.parameterGenerator.generateParameterValue(initMethodNames));
}
}
private void handleDestroyMethodNames(MultiStatement statements, String bdVariable, String[] destroyMethodNames) {
if (destroyMethodNames.length == 1) {
statements.addStatement("$L.setDestroyMethodName($S)", bdVariable, destroyMethodNames[0]);
}
else {
statements.addStatement("$L.setDestroyMethodNames($L)", bdVariable,
this.parameterGenerator.generateParameterValue(destroyMethodNames));
}
}
private void handleArgumentValues(MultiStatement statements, String bdVariable,
Map<Integer, ValueHolder> indexedArgumentValues) {
if (indexedArgumentValues.size() == 1) {