@@ -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 }
0 commit comments