Skip to content

Commit 5fafb37

Browse files
committed
add a fix for #11758; the bug is fixed by checking the reference metadata first and only loading definitions if a @Replaces stereotype is present on the reference.
1 parent aa7d150 commit 5fafb37

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

inject/src/main/java/io/micronaut/context/DefaultBeanContext.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2521,14 +2521,58 @@ protected void processParallelBeans(List<BeanDefinitionProducer> parallelBeans)
25212521

25222522
private <T> void filterReplacedBeans(BeanResolutionContext resolutionContext, Collection<BeanDefinition<T>> candidates) {
25232523
if (candidates.size() > 1) {
2524+
// Collect replacement definitions not only from the current candidates, but also
2525+
// from the global registry so replacements declared elsewhere are considered.
25242526
List<BeanDefinition<T>> replacementTypes = new ArrayList<>(2);
2527+
2528+
// 1) Check already-loaded candidates for @Replaces
25252529
for (BeanDefinition<T> candidate : candidates) {
25262530
if (candidate.getAnnotationMetadata().hasStereotype(REPLACES_ANN)) {
25272531
replacementTypes.add(candidate);
25282532
}
25292533
}
2534+
2535+
// 2) Scan global bean producers for references that declare @Replaces.
2536+
// Try to use the reference metadata first to avoid loading the full definition.
2537+
for (BeanDefinitionProducer producer : this.beanDefinitionsClasses) {
2538+
BeanDefinitionReference<?> ref = producer.getReferenceIfEnabled(this);
2539+
if (ref == null) {
2540+
continue;
2541+
}
2542+
if (ref.getAnnotationMetadata().hasStereotype(REPLACES_ANN)) {
2543+
// We need a BeanDefinition to perform detailed replace matching.
2544+
// Use getDefinitionIfEnabled which may load/cached the definition.
2545+
@SuppressWarnings("unchecked") BeanDefinition<T> def = (BeanDefinition<T>) producer.getDefinitionIfEnabled(this);
2546+
if (def != null) {
2547+
replacementTypes.add(def);
2548+
}
2549+
}
2550+
}
2551+
25302552
if (!replacementTypes.isEmpty()) {
2531-
candidates.removeIf(definition -> checkIfReplacementExists(resolutionContext, replacementTypes, definition));
2553+
candidates.removeIf(definition -> {
2554+
boolean replaced = checkIfReplacementExists(resolutionContext, replacementTypes, definition);
2555+
if (replaced) {
2556+
// Targeted cache invalidation: purge candidate & resolution caches for the replaced bean type
2557+
try {
2558+
purgeCacheForBeanType(definition.getBeanType());
2559+
} catch (Exception e) {
2560+
if (LOG.isDebugEnabled()) {
2561+
LOG.debug("Error purging caches for replaced bean type [{}]: {}", definition.getBeanType(), e.getMessage(), e);
2562+
}
2563+
}
2564+
// Remove any singleton registration associated with the replaced definition so subsequent lookups
2565+
// don't return the stale instance.
2566+
try {
2567+
singletonScope.removeRegistrationForDefinition(definition);
2568+
} catch (Exception e) {
2569+
if (LOG.isDebugEnabled()) {
2570+
LOG.debug("Error removing singleton registration for replaced bean definition [{}]: {}", definition, e.getMessage(), e);
2571+
}
2572+
}
2573+
}
2574+
return replaced;
2575+
});
25322576
}
25332577
}
25342578
}

inject/src/main/java/io/micronaut/context/SingletonScope.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,21 @@ synchronized <T> void purgeCacheForBeanInstance(BeanDefinition<T> beanDefinition
304304
singletonByArgumentAndQualifier.entrySet().removeIf(entry -> entry.getKey().beanType.isInstance(bean));
305305
}
306306

307+
/**
308+
* Remove any singleton registration associated with the given bean definition.
309+
* This is a targeted cache invalidation used when a bean definition is replaced.
310+
*
311+
* @param beanDefinition The bean definition whose registration should be removed
312+
*/
313+
synchronized void removeRegistrationForDefinition(BeanDefinition<?> beanDefinition) {
314+
BeanDefinitionIdentity id = BeanDefinitionIdentity.of(beanDefinition);
315+
BeanRegistration removed = singletonByBeanDefinition.remove(id);
316+
if (removed != null) {
317+
// Remove any indexed entries that point to the removed registration.
318+
singletonByArgumentAndQualifier.entrySet().removeIf(entry -> entry.getValue() == removed);
319+
}
320+
}
321+
307322
/**
308323
* Cleanup the scope.
309324
*/

0 commit comments

Comments
 (0)