diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java index 063b1e43432..df1e11632ee 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java @@ -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))); + } + } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 7f331d3b5f2..68c865c4d1b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -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. - *
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. + *
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) {
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java b/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java
index 13f5de72ca5..8ac8784b6a7 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java
@@ -28,6 +28,8 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.lifecyclemethods.InitDestroyBean;
+import org.springframework.context.annotation.lifecyclemethods.PackagePrivateInitDestroyBean;
import static org.assertj.core.api.Assertions.assertThat;
@@ -53,11 +55,11 @@ class InitDestroyMethodLifecycleTests {
@Test
void initDestroyMethods() {
Class> beanClass = InitDestroyBean.class;
- DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "afterPropertiesSet", "destroy");
+ DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "initMethod", "destroyMethod");
InitDestroyBean bean = beanFactory.getBean(InitDestroyBean.class);
- assertThat(bean.initMethods).as("init-methods").containsExactly("afterPropertiesSet");
+ assertThat(bean.initMethods).as("init-methods").containsExactly("initMethod");
beanFactory.destroySingletons();
- assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("destroy");
+ assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("destroyMethod");
}
@Test
@@ -132,6 +134,26 @@ class InitDestroyMethodLifecycleTests {
);
}
+ @Test
+ void jakartaAnnotationsCustomPackagePrivateInitDestroyMethodsWithTheSameMethodNames() {
+ Class> beanClass = SubPackagePrivateInitDestroyBean.class;
+ DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "initMethod", "destroyMethod");
+ SubPackagePrivateInitDestroyBean bean = beanFactory.getBean(SubPackagePrivateInitDestroyBean.class);
+
+ assertThat(bean.initMethods).as("init-methods").containsExactly(
+ "PackagePrivateInitDestroyBean.postConstruct",
+ "SubPackagePrivateInitDestroyBean.postConstruct",
+ "initMethod"
+ );
+
+ beanFactory.destroySingletons();
+ assertThat(bean.destroyMethods).as("destroy-methods").containsExactly(
+ "SubPackagePrivateInitDestroyBean.preDestroy",
+ "PackagePrivateInitDestroyBean.preDestroy",
+ "destroyMethod"
+ );
+ }
+
@Test
void allLifecycleMechanismsAtOnce() {
Class> beanClass = AllInOneBean.class;
@@ -165,21 +187,6 @@ class InitDestroyMethodLifecycleTests {
}
- static class InitDestroyBean {
-
- final List