|
131 | 131 | import java.util.Objects; |
132 | 132 | import java.util.Optional; |
133 | 133 | import java.util.Set; |
| 134 | +import java.util.WeakHashMap; |
134 | 135 | import java.util.concurrent.ConcurrentHashMap; |
135 | 136 | import java.util.concurrent.CopyOnWriteArrayList; |
136 | 137 | import java.util.concurrent.ForkJoinPool; |
@@ -175,6 +176,13 @@ public class DefaultBeanContext implements InitializableBeanContext, Configurabl |
175 | 176 |
|
176 | 177 | protected final SingletonScope singletonScope = new SingletonScope(); |
177 | 178 |
|
| 179 | + /** |
| 180 | + * Track instances created by this context so that if the same instance is later |
| 181 | + * registered via registerSingleton(...) we can avoid duplicate injection/initialization. |
| 182 | + * Use a WeakHashMap-backed set so instances can be GC'ed and to avoid leaks. |
| 183 | + */ |
| 184 | + private final Set<Object> beansCreatedByContext = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); |
| 185 | + |
178 | 186 | private final BeanContextConfiguration beanContextConfiguration; |
179 | 187 |
|
180 | 188 | // The collection should be modified only when new bean definition is added |
@@ -691,7 +699,19 @@ public <T> BeanContext registerSingleton(@NonNull Class<T> type, @NonNull T sing |
691 | 699 | if (beanDefinition != null && !(beanDefinition instanceof RuntimeBeanDefinition<T>) && beanDefinition.getBeanType().isInstance(singleton)) { |
692 | 700 | try (BeanResolutionContext context = newResolutionContext(beanDefinition, null)) { |
693 | 701 | if (inject) { |
694 | | - doInjectAndInitialize(context, singleton, beanDefinition); |
| 702 | + // If this instance was created by this context earlier, it may already have been injected/initialized. |
| 703 | + // In such case skip duplicate injection/initialization. Use the beansCreatedByContext set to detect this. |
| 704 | + boolean createdByThisContext; |
| 705 | + synchronized (beansCreatedByContext) { |
| 706 | + createdByThisContext = beansCreatedByContext.remove(singleton); |
| 707 | + } |
| 708 | + if (!createdByThisContext) { |
| 709 | + doInjectAndInitialize(context, singleton, beanDefinition); |
| 710 | + } else { |
| 711 | + if (LOG_LIFECYCLE.isDebugEnabled()) { |
| 712 | + LOG_LIFECYCLE.debug("Skipping duplicate injection/initialization for bean [{}] as it was created by the context", singleton); |
| 713 | + } |
| 714 | + } |
695 | 715 | } |
696 | 716 | DefaultBeanContext.BeanKey<T> key = new DefaultBeanContext.BeanKey<>(beanDefinition.asArgument(), qualifier); |
697 | 717 | singletonScope.registerSingletonBean(BeanRegistration.of(this, key, beanDefinition, singleton), qualifier); |
@@ -2349,6 +2369,11 @@ private <T> T resolveByBeanFactory(@NonNull BeanResolutionContext resolutionCont |
2349 | 2369 | if (bean instanceof Qualified qualified) { |
2350 | 2370 | qualified.$withBeanQualifier(declaredQualifier); |
2351 | 2371 | } |
| 2372 | + |
| 2373 | + // Track instances created by this context so registerSingleton can avoid |
| 2374 | + // doing duplicate injection/initialization when someone registers the same instance. |
| 2375 | + beansCreatedByContext.add(bean); |
| 2376 | + |
2352 | 2377 | return bean; |
2353 | 2378 | } catch (DependencyInjectionException | DisabledBeanException | BeanInstantiationException e) { |
2354 | 2379 | throw e; |
|
0 commit comments