From f96872404d9ab495a2821955b649fec9a28f92ed Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 1 Mar 2022 15:02:57 +0100 Subject: [PATCH] Ensure private init/destroy method is invoked only once Closes gh-28083 --- .../AbstractAutowireCapableBeanFactory.java | 4 +- .../support/DisposableBeanAdapter.java | 5 +- .../factory/support/RootBeanDefinition.java | 63 ++++++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 7a94a91c86a8..c1e764b22a5f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -1844,7 +1844,7 @@ protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBea throws Throwable { boolean isInitializingBean = (bean instanceof InitializingBean); - if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { + if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) { if (logger.isTraceEnabled()) { logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); } @@ -1868,7 +1868,7 @@ protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBea String initMethodName = mbd.getInitMethodName(); if (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && - !mbd.isExternallyManagedInitMethod(initMethodName)) { + !mbd.hasAnyExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index b5fdef4dd813..4317ae901c9b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -52,6 +52,7 @@ * @author Juergen Hoeller * @author Costin Leau * @author Stephane Nicoll + * @author Sam Brannen * @since 2.0 * @see AbstractBeanFactory * @see org.springframework.beans.factory.DisposableBean @@ -109,12 +110,12 @@ public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition be this.beanName = beanName; this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed(); this.invokeDisposableBean = (bean instanceof DisposableBean && - !beanDefinition.isExternallyManagedDestroyMethod(DESTROY_METHOD_NAME)); + !beanDefinition.hasAnyExternallyManagedDestroyMethod(DESTROY_METHOD_NAME)); String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition); if (destroyMethodName != null && !(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodName)) && - !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) { + !beanDefinition.hasAnyExternallyManagedDestroyMethod(destroyMethodName)) { this.invokeAutoCloseable = (bean instanceof AutoCloseable && CLOSE_METHOD_NAME.equals(destroyMethodName)); if (!this.invokeAutoCloseable) { 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 f4fdafa3767e..13638768efa1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,7 @@ * * @author Rod Johnson * @author Juergen Hoeller + * @author Sam Brannen * @see GenericBeanDefinition * @see ChildBeanDefinition */ @@ -479,6 +480,36 @@ public boolean isExternallyManagedInitMethod(String initMethod) { } } + /** + * Determine if the given method name indicates an externally managed + * initialization method, regardless of method visibility. + *

In contrast to {@link #isExternallyManagedInitMethod(String)}, this + * method also returns {@code true} if there is a {@code private} external + * init method that has been + * {@linkplain #registerExternallyManagedInitMethod(String) registered} + * using a fully qualified method name instead of a simple method name. + * @since 5.3.17 + */ + boolean hasAnyExternallyManagedInitMethod(String initMethod) { + synchronized (this.postProcessingLock) { + if (isExternallyManagedInitMethod(initMethod)) { + return true; + } + if (this.externallyManagedInitMethods != null) { + for (String candidate : this.externallyManagedInitMethods) { + int indexOfDot = candidate.lastIndexOf("."); + if (indexOfDot >= 0) { + String methodName = candidate.substring(indexOfDot + 1); + if (methodName.equals(initMethod)) { + return true; + } + } + } + } + return false; + } + } + /** * Return all externally managed initialization methods (as an immutable Set). * @since 5.3.11 @@ -513,6 +544,36 @@ public boolean isExternallyManagedDestroyMethod(String destroyMethod) { } } + /** + * Determine if the given method name indicates an externally managed + * destruction method, regardless of method visibility. + *

In contrast to {@link #isExternallyManagedDestroyMethod(String)}, this + * method also returns {@code true} if there is a {@code private} external + * destroy method that has been + * {@linkplain #registerExternallyManagedDestroyMethod(String) registered} + * using a fully qualified method name instead of a simple method name. + * @since 5.3.17 + */ + boolean hasAnyExternallyManagedDestroyMethod(String destroyMethod) { + synchronized (this.postProcessingLock) { + if (isExternallyManagedDestroyMethod(destroyMethod)) { + return true; + } + if (this.externallyManagedDestroyMethods != null) { + for (String candidate : this.externallyManagedDestroyMethods) { + int indexOfDot = candidate.lastIndexOf("."); + if (indexOfDot >= 0) { + String methodName = candidate.substring(indexOfDot + 1); + if (methodName.equals(destroyMethod)) { + return true; + } + } + } + } + return false; + } + } + /** * Return all externally managed destruction methods (as an immutable Set). * @since 5.3.11