Skip to content

Commit

Permalink
Support private init/destroy methods in AOT mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrannen committed Jun 21, 2023
1 parent b317620 commit e06698d
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 35 deletions.
Expand Up @@ -182,7 +182,7 @@ private LifecycleMetadata findLifecycleMetadata(RootBeanDefinition beanDefinitio
private static String[] safeMerge(@Nullable String[] existingNames, Collection<LifecycleMethod> detectedMethods) {
Stream<String> detectedNames = detectedMethods.stream().map(LifecycleMethod::getIdentifier);
Stream<String> mergedNames = (existingNames != null ?
Stream.concat(Stream.of(existingNames), detectedNames) : detectedNames);
Stream.concat(detectedNames, Stream.of(existingNames)) : detectedNames);
return mergedNames.distinct().toArray(String[]::new);
}

Expand Down
Expand Up @@ -72,6 +72,7 @@
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.0
*/
class BeanDefinitionPropertiesCodeGenerator {
Expand Down Expand Up @@ -138,6 +139,7 @@ private void addInitDestroyMethods(Builder code,
}

private void addInitDestroyHint(Class<?> beanUserClass, String methodName) {
// TODO Handle fully-qualified method names
Method method = ReflectionUtils.findMethod(beanUserClass, methodName);
if (method != null) {
this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
Expand Down
Expand Up @@ -1840,18 +1840,32 @@ protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBea
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName)
throws Throwable {

Class<?> beanClass = bean.getClass();
Class<?> methodDeclaringClass = beanClass;
String methodName = initMethodName;

// Parse fully-qualified method name if necessary.
int indexOfDot = initMethodName.lastIndexOf('.');
if (indexOfDot > 0) {
String className = initMethodName.substring(0, indexOfDot);
methodName = initMethodName.substring(indexOfDot + 1);
if (!beanClass.getName().equals((className))) {
methodDeclaringClass = ClassUtils.forName(className, beanClass.getClassLoader());
}
}

Method initMethod = (mbd.isNonPublicAccessAllowed() ?
BeanUtils.findMethod(bean.getClass(), initMethodName) :
ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName));
BeanUtils.findMethod(methodDeclaringClass, methodName) :
ClassUtils.getMethodIfAvailable(beanClass, methodName));

if (initMethod == null) {
if (mbd.isEnforceInitMethod()) {
throw new BeanDefinitionValidationException("Could not find an init method named '" +
initMethodName + "' on bean with name '" + beanName + "'");
methodName + "' on bean with name '" + beanName + "'");
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No default init method named '" + initMethodName +
logger.trace("No default init method named '" + methodName +
"' found on bean with name '" + beanName + "'");
}
// Ignore non-existent default lifecycle methods.
Expand All @@ -1860,9 +1874,9 @@ protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefi
}

if (logger.isTraceEnabled()) {
logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'");
logger.trace("Invoking init method '" + methodName + "' on bean with name '" + beanName + "'");
}
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, bean.getClass());
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, beanClass);

try {
ReflectionUtils.makeAccessible(methodToInvoke);
Expand Down
Expand Up @@ -255,19 +255,32 @@ else if (this.destroyMethodNames != null) {
private Method determineDestroyMethod(String name) {
try {
Class<?> beanClass = this.bean.getClass();
Method destroyMethod = findDestroyMethod(beanClass, name);
Class<?> methodDeclaringClass = beanClass;
String methodName = name;

// Parse fully-qualified method name if necessary.
int indexOfDot = name.lastIndexOf('.');
if (indexOfDot > 0) {
String className = name.substring(0, indexOfDot);
methodName = name.substring(indexOfDot + 1);
if (!beanClass.getName().equals((className))) {
methodDeclaringClass = ClassUtils.forName(className, beanClass.getClassLoader());
}
}

Method destroyMethod = findDestroyMethod(methodDeclaringClass, methodName);
if (destroyMethod != null) {
return destroyMethod;
}
for (Class<?> beanInterface : beanClass.getInterfaces()) {
destroyMethod = findDestroyMethod(beanInterface, name);
destroyMethod = findDestroyMethod(beanInterface, methodName);
if (destroyMethod != null) {
return destroyMethod;
}
}
return null;
}
catch (IllegalArgumentException ex) {
catch (ClassNotFoundException | IllegalArgumentException ex) {
throw new BeanDefinitionValidationException("Could not find unique destroy method on bean with name '" +
this.beanName + ": " + ex.getMessage());
}
Expand Down
Expand Up @@ -70,8 +70,8 @@ void processAheadOfTimeWhenHasInitDestroyAnnotationsAndCustomDefinedMethodNamesA
beanDefinition.setDestroyMethodNames("customDestroyMethod");
processAheadOfTime(beanDefinition);
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("customInitMethod", "initMethod");
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("customDestroyMethod", "destroyMethod");
assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("initMethod", "customInitMethod");
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod", "customDestroyMethod");
}

@Test
Expand Down Expand Up @@ -129,16 +129,16 @@ void processAheadOfTimeWithMultipleLevelsOfPublicAndPrivateInitAndDestroyMethods
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
assertSoftly(softly -> {
softly.assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly(
"afterPropertiesSet",
"customInit",
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateInit", // fully-qualified private method
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateInit" // fully-qualified private method
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateInit", // fully-qualified private method
"afterPropertiesSet",
"customInit"
);
softly.assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly(
"destroy",
"customDestroy",
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateDestroy", // fully-qualified private method
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateDestroy" // fully-qualified private method
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateDestroy", // fully-qualified private method
"destroy",
"customDestroy"
);
});
}
Expand Down

0 comments on commit e06698d

Please sign in to comment.