Ensure package-private init/destroy methods are always invoked
Prior to this commit, if an init/destroy method was package-private and declared in a superclass in a package different from the package in which the registered bean resided, a local init/destroy method with the same name would effectively "shadow" the method from the different package, resulting in only the local init/destroy method being invoked. This commit addresses this issue by tracking package-private init methods from different packages using their fully-qualified method names, analogous to the existing support for private init/destroy methods. Closes gh-30718
This commit is contained in:
@@ -269,13 +269,13 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
|
||||
|
||||
ReflectionUtils.doWithLocalMethods(currentClass, method -> {
|
||||
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
|
||||
currInitMethods.add(new LifecycleMethod(method));
|
||||
currInitMethods.add(new LifecycleMethod(method, beanClass));
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Found init method on class [" + beanClass.getName() + "]: " + method);
|
||||
}
|
||||
}
|
||||
if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
|
||||
currDestroyMethods.add(new LifecycleMethod(method));
|
||||
currDestroyMethods.add(new LifecycleMethod(method, beanClass));
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Found destroy method on class [" + beanClass.getName() + "]: " + method);
|
||||
}
|
||||
@@ -404,12 +404,12 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
|
||||
|
||||
private final String identifier;
|
||||
|
||||
public LifecycleMethod(Method method) {
|
||||
public LifecycleMethod(Method method, Class<?> beanClass) {
|
||||
if (method.getParameterCount() != 0) {
|
||||
throw new IllegalStateException("Lifecycle annotation requires a no-arg method: " + method);
|
||||
}
|
||||
this.method = method;
|
||||
this.identifier = (Modifier.isPrivate(method.getModifiers()) ?
|
||||
this.identifier = (isPrivateOrNotVisible(method, beanClass) ?
|
||||
ClassUtils.getQualifiedMethodName(method) : method.getName());
|
||||
}
|
||||
|
||||
@@ -436,6 +436,23 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
|
||||
public int hashCode() {
|
||||
return this.identifier.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied lifecycle {@link Method} is private or not
|
||||
* visible to the supplied bean {@link Class}.
|
||||
* @since 6.0.11
|
||||
*/
|
||||
private static boolean isPrivateOrNotVisible(Method method, Class<?> beanClass) {
|
||||
int modifiers = method.getModifiers();
|
||||
if (Modifier.isPrivate(modifiers)) {
|
||||
return true;
|
||||
}
|
||||
// Method is declared in a class that resides in a different package
|
||||
// than the bean class and the method is neither public nor protected?
|
||||
return (!method.getDeclaringClass().getPackageName().equals(beanClass.getPackageName()) &&
|
||||
!(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -497,14 +497,15 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
||||
|
||||
/**
|
||||
* Register an externally managed configuration initialization method —
|
||||
* for example, a method annotated with JSR-250's
|
||||
* {@link jakarta.annotation.PostConstruct} annotation.
|
||||
* <p>The supplied {@code initMethod} may be the
|
||||
* {@linkplain Method#getName() simple method name} for non-private methods or the
|
||||
* for example, a method annotated with JSR-250's {@link javax.annotation.PostConstruct}
|
||||
* or Jakarta's {@link jakarta.annotation.PostConstruct} annotation.
|
||||
* <p>The supplied {@code initMethod} may be a
|
||||
* {@linkplain Method#getName() simple method name} or a
|
||||
* {@linkplain org.springframework.util.ClassUtils#getQualifiedMethodName(Method)
|
||||
* qualified method name} for {@code private} methods. A qualified name is
|
||||
* necessary for {@code private} methods in order to disambiguate between
|
||||
* multiple private methods with the same name within a class hierarchy.
|
||||
* qualified method name} for package-private and {@code private} methods.
|
||||
* A qualified name is necessary for package-private and {@code private} methods
|
||||
* in order to disambiguate between multiple such methods with the same name
|
||||
* within a type hierarchy.
|
||||
*/
|
||||
public void registerExternallyManagedInitMethod(String initMethod) {
|
||||
synchronized (this.postProcessingLock) {
|
||||
|
||||
Reference in New Issue
Block a user