From 7bc2a7f3f24cbed40a0922134e28c8809729f458 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 9 Oct 2025 20:11:11 +0200 Subject: [PATCH] Add support for programmatic CandidateComponentsIndex setup Closes gh-35497 See gh-35472 --- .../processor/CandidateComponentsIndexer.java | 5 +- .../StandardStereotypesProvider.java | 5 +- ...athScanningCandidateComponentProvider.java | 52 ++++-- .../index/CandidateComponentsIndex.java | 94 ++++++++--- .../index/CandidateComponentsIndexLoader.java | 28 +++- .../ClassPathBeanDefinitionScannerTests.java | 149 ++++++++++++++---- .../CandidateComponentsIndexLoaderTests.java | 2 - .../index/CandidateComponentsIndexTests.java | 2 - .../example/scannable/spring.components | 1 - 9 files changed, 267 insertions(+), 71 deletions(-) diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java index 9092e9d2150..43b16ac8486 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java @@ -37,12 +37,13 @@ import javax.lang.model.element.TypeElement; /** * Annotation {@link Processor} that writes a {@link CandidateComponentsMetadata} - * file for spring components. + * file for Spring components. * * @author Stephane Nicoll * @author Juergen Hoeller * @since 5.0 - * @deprecated as of 6.1, in favor of the AOT engine. + * @deprecated as of 6.1, in favor of the AOT engine and the forthcoming + * support for an AOT-generated Spring components index */ @Deprecated(since = "6.1", forRemoval = true) public class CandidateComponentsIndexer implements Processor { diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java index e8d66623e5d..6bd49f7c360 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java @@ -25,8 +25,7 @@ import javax.lang.model.element.ElementKind; /** * A {@link StereotypesProvider} that extracts a stereotype for each - * {@code jakarta.*} or {@code javax.*} annotation present on a class or - * interface. + * {@code jakarta.*} annotation present on a class or interface. * * @author Stephane Nicoll * @since 5.0 @@ -50,7 +49,7 @@ class StandardStereotypesProvider implements StereotypesProvider { } for (AnnotationMirror annotation : this.typeHelper.getAllAnnotationMirrors(element)) { String type = this.typeHelper.getType(annotation); - if (type.startsWith("jakarta.") || type.startsWith("javax.")) { + if (type.startsWith("jakarta.")) { stereotypes.add(type); } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 6d6b21b6ba8..c5b9767eeb6 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -312,11 +312,14 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC */ public Set findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { - return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); - } - else { - return scanCandidateComponents(basePackage); + if (this.componentsIndex.hasScannedPackage(basePackage)) { + return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); + } + else { + this.componentsIndex.registerScan(basePackage); + } } + return scanCandidateComponents(basePackage); } /** @@ -339,14 +342,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC * @param filter the filter to check * @return whether the index supports this include filter * @since 5.0 + * @see #registerCandidateTypeForIncludeFilter(String, TypeFilter) * @see #extractStereotype(TypeFilter) */ private boolean indexSupportsIncludeFilter(TypeFilter filter) { if (filter instanceof AnnotationTypeFilter annotationTypeFilter) { Class annotationType = annotationTypeFilter.getAnnotationType(); - return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) || - annotationType.getName().startsWith("jakarta.") || - annotationType.getName().startsWith("javax.")); + return isStereotypeAnnotationForIndex(annotationType); } if (filter instanceof AssignableTypeFilter assignableTypeFilter) { Class target = assignableTypeFilter.getTargetType(); @@ -355,6 +357,28 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC return false; } + /** + * Register the given class as a candidate type with the runtime-populated index, if any. + * @param className the fully-qualified class name of the candidate type + * @param filter the include filter to introspect for the associated stereotype + */ + private void registerCandidateTypeForIncludeFilter(String className, TypeFilter filter) { + if (this.componentsIndex != null) { + if (filter instanceof AnnotationTypeFilter annotationTypeFilter) { + Class annotationType = annotationTypeFilter.getAnnotationType(); + if (isStereotypeAnnotationForIndex(annotationType)) { + this.componentsIndex.registerCandidateType(className, annotationType.getName()); + } + } + else if (filter instanceof AssignableTypeFilter assignableTypeFilter) { + Class target = assignableTypeFilter.getTargetType(); + if (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target)) { + this.componentsIndex.registerCandidateType(className, target.getName()); + } + } + } + } + /** * Extract the stereotype to use for the specified compatible filter. * @param filter the filter to handle @@ -372,6 +396,11 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC return null; } + private boolean isStereotypeAnnotationForIndex(Class annotationType) { + return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) || + annotationType.getName().startsWith("jakarta.")); + } + private Set addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) { Set candidates = new LinkedHashSet<>(); try { @@ -503,13 +532,14 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC * @return whether the class qualifies as a candidate component */ protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { - for (TypeFilter tf : this.excludeFilters) { - if (tf.match(metadataReader, getMetadataReaderFactory())) { + for (TypeFilter filter : this.excludeFilters) { + if (filter.match(metadataReader, getMetadataReaderFactory())) { return false; } } - for (TypeFilter tf : this.includeFilters) { - if (tf.match(metadataReader, getMetadataReaderFactory())) { + for (TypeFilter filter : this.includeFilters) { + if (filter.match(metadataReader, getMetadataReaderFactory())) { + registerCandidateTypeForIncludeFilter(metadataReader.getClassMetadata().getClassName(), filter); return isConditionMatch(metadataReader); } } diff --git a/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java b/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java index 49c20023f82..bf8c9f1bc29 100644 --- a/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java +++ b/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java @@ -17,6 +17,7 @@ package org.springframework.context.index; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Properties; import java.util.Set; @@ -44,34 +45,87 @@ import org.springframework.util.MultiValueMap; * a target type but it can be any marker really. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 5.0 - * @deprecated as of 6.1, in favor of the AOT engine. */ -@Deprecated(since = "6.1", forRemoval = true) public class CandidateComponentsIndex { private static final AntPathMatcher pathMatcher = new AntPathMatcher("."); - private final MultiValueMap index; + private final Set registeredScans = new LinkedHashSet<>(); + + private final MultiValueMap index = new LinkedMultiValueMap<>(); + + private final boolean complete; + /** + * Create a new index instance from parsed components index files. + */ CandidateComponentsIndex(List content) { - this.index = parseIndex(content); - } - - private static MultiValueMap parseIndex(List content) { - MultiValueMap index = new LinkedMultiValueMap<>(); for (Properties entry : content) { entry.forEach((type, values) -> { String[] stereotypes = ((String) values).split(","); for (String stereotype : stereotypes) { - index.add(stereotype, new Entry((String) type)); + this.index.add(stereotype, new Entry((String) type)); } }); } - return index; + this.complete = true; } + /** + * Create a new index instance for programmatic population. + * @since 7.0 + */ + public CandidateComponentsIndex() { + this.complete = false; + } + + + /** + * Register the given base packages (or base package patterns) as scanned. + * @since 7.0 + */ + public void registerScan(String... basePackages) { + Collections.addAll(this.registeredScans, basePackages); + } + + /** + * Return the registered base packages (or base package patterns). + * @since 7.0 + */ + public Set getRegisteredScans() { + return this.registeredScans; + } + + /** + * Determine whether this index contains entries for the given base package + * (or base package pattern). + * @since 7.0 + */ + public boolean hasScannedPackage(String packageName) { + return (this.complete || + this.registeredScans.stream().anyMatch(basePackage -> matchPackage(basePackage, packageName))); + } + + /** + * Programmatically register one or more stereotypes for the given candidate type. + * @since 7.0 + */ + public void registerCandidateType(String type, String... stereotypes) { + for (String stereotype : stereotypes) { + this.index.add(stereotype, new Entry(type)); + } + } + + /** + * Return the registered stereotypes packages (or base package patterns). + * @since 7.0 + */ + public Set getRegisteredStereotypes() { + return this.index.keySet(); + } /** * Return the candidate types that are associated with the specified stereotype. @@ -83,7 +137,7 @@ public class CandidateComponentsIndex { public Set getCandidateTypes(String basePackage, String stereotype) { List candidates = this.index.get(stereotype); if (candidates != null) { - return candidates.parallelStream() + return candidates.stream() .filter(t -> t.match(basePackage)) .map(t -> t.type) .collect(Collectors.toSet()); @@ -91,10 +145,19 @@ public class CandidateComponentsIndex { return Collections.emptySet(); } + private static boolean matchPackage(String basePackage, String packageName) { + if (pathMatcher.isPattern(basePackage)) { + return pathMatcher.match(basePackage, packageName); + } + else { + return packageName.startsWith(basePackage); + } + } + private static class Entry { - private final String type; + final String type; private final String packageName; @@ -104,12 +167,7 @@ public class CandidateComponentsIndex { } public boolean match(String basePackage) { - if (pathMatcher.isPattern(basePackage)) { - return pathMatcher.match(basePackage, this.packageName); - } - else { - return this.type.startsWith(basePackage); - } + return matchPackage(basePackage, this.packageName); } } diff --git a/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java b/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java index 091bd33888f..501a3c77da2 100644 --- a/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java +++ b/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java @@ -37,11 +37,9 @@ import org.springframework.util.ConcurrentReferenceHashMap; * Candidate components index loading mechanism for internal use within the framework. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 5.0 - * @deprecated as of 6.1, in favor of the AOT engine. */ -@Deprecated(since = "6.1", forRemoval = true) -@SuppressWarnings("removal") public final class CandidateComponentsIndexLoader { /** @@ -119,4 +117,28 @@ public final class CandidateComponentsIndexLoader { } } + + /** + * Programmatically add the given index instance for the given ClassLoader, + * replacing a file-determined index with a programmatically composed index. + *

The index instance will usually be pre-populated for AOT runtime setups + * or test scenarios with pre-configured results for runtime-attempted scans. + * Alternatively, it may be empty for it to get populated during AOT processing + * or a test run, for subsequent introspection the index-recorded candidate types. + * @param classLoader the ClassLoader to add the index for + * @param index the associated CandidateComponentsIndex instance + * @since 7.0 + */ + public static void addIndex(ClassLoader classLoader, CandidateComponentsIndex index) { + cache.put(classLoader, index); + } + + /** + * Clear the runtime index cache. + * @since 7.0 + */ + public static void clearCache() { + cache.clear(); + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java index 88619e095a5..db7fda429e8 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java @@ -16,10 +16,13 @@ package org.springframework.context.annotation; +import java.io.IOException; + import example.scannable.CustomComponent; import example.scannable.FooService; import example.scannable.FooServiceImpl; import example.scannable.NamedStubDao; +import example.scannable.ServiceInvocationCounter; import example.scannable.StubFooDao; import org.aspectj.lang.annotation.Aspect; import org.junit.jupiter.api.Test; @@ -35,9 +38,13 @@ import org.springframework.beans.factory.support.StaticListableBeanFactory; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.MessageSource; import org.springframework.context.annotation2.NamedStubDao2; +import org.springframework.context.index.CandidateComponentsIndex; +import org.springframework.context.index.CandidateComponentsIndexLoader; import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.testfixture.index.CandidateComponentsTestClassLoader; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.stereotype.Component; @@ -61,6 +68,7 @@ class ClassPathBeanDefinitionScannerTests { GenericApplicationContext context = new GenericApplicationContext(); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); int beanCount = scanner.scan(BASE_PACKAGE); + assertThat(beanCount).isGreaterThanOrEqualTo(12); assertThat(context.containsBean("serviceInvocationCounter")).isTrue(); assertThat(context.containsBean("fooServiceImpl")).isTrue(); @@ -73,8 +81,8 @@ class ClassPathBeanDefinitionScannerTests { assertThat(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)).isTrue(); assertThat(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME)).isTrue(); assertThat(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME)).isTrue(); - context.refresh(); + context.refresh(); FooServiceImpl fooService = context.getBean("fooServiceImpl", FooServiceImpl.class); assertThat(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent")).isTrue(); assertThat(fooService.foo(123)).isEqualTo("bar"); @@ -105,11 +113,30 @@ class ClassPathBeanDefinitionScannerTests { } @Test - void testDoubleScan() { + void testSimpleScanWithIndex() { GenericApplicationContext context = new GenericApplicationContext(); + context.setClassLoader(CandidateComponentsTestClassLoader.index( + ClassPathScanningCandidateComponentProviderTests.class.getClassLoader(), + new ClassPathResource("spring.components", FooServiceImpl.class))); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); int beanCount = scanner.scan(BASE_PACKAGE); + + assertThat(beanCount).isGreaterThanOrEqualTo(12); + assertThat(context.containsBean("serviceInvocationCounter")).isTrue(); + assertThat(context.containsBean("fooServiceImpl")).isTrue(); + assertThat(context.containsBean("stubFooDao")).isTrue(); + assertThat(context.containsBean("myNamedComponent")).isTrue(); + assertThat(context.containsBean("myNamedDao")).isTrue(); + assertThat(context.containsBean("thoreau")).isTrue(); + } + + @Test + void testDoubleScan() { + GenericApplicationContext context = new GenericApplicationContext(); + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); + int beanCount = scanner.scan(BASE_PACKAGE); + assertThat(beanCount).isGreaterThanOrEqualTo(12); ClassPathBeanDefinitionScanner scanner2 = new ClassPathBeanDefinitionScanner(context) { @@ -129,25 +156,6 @@ class ClassPathBeanDefinitionScannerTests { assertThat(context.containsBean("thoreau")).isTrue(); } - @Test - void testWithIndex() { - GenericApplicationContext context = new GenericApplicationContext(); - context.setClassLoader(CandidateComponentsTestClassLoader.index( - ClassPathScanningCandidateComponentProviderTests.class.getClassLoader(), - new ClassPathResource("spring.components", FooServiceImpl.class))); - - ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); - int beanCount = scanner.scan(BASE_PACKAGE); - assertThat(beanCount).isGreaterThanOrEqualTo(12); - - assertThat(context.containsBean("serviceInvocationCounter")).isTrue(); - assertThat(context.containsBean("fooServiceImpl")).isTrue(); - assertThat(context.containsBean("stubFooDao")).isTrue(); - assertThat(context.containsBean("myNamedComponent")).isTrue(); - assertThat(context.containsBean("myNamedDao")).isTrue(); - assertThat(context.containsBean("thoreau")).isTrue(); - } - @Test void testDoubleScanWithIndex() { GenericApplicationContext context = new GenericApplicationContext(); @@ -157,6 +165,7 @@ class ClassPathBeanDefinitionScannerTests { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); int beanCount = scanner.scan(BASE_PACKAGE); + assertThat(beanCount).isGreaterThanOrEqualTo(12); ClassPathBeanDefinitionScanner scanner2 = new ClassPathBeanDefinitionScanner(context) { @@ -182,8 +191,8 @@ class ClassPathBeanDefinitionScannerTests { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); scanner.setIncludeAnnotationConfig(false); int beanCount = scanner.scan(BASE_PACKAGE); - assertThat(beanCount).isGreaterThanOrEqualTo(7); + assertThat(beanCount).isGreaterThanOrEqualTo(7); assertThat(context.containsBean("serviceInvocationCounter")).isTrue(); assertThat(context.containsBean("fooServiceImpl")).isTrue(); assertThat(context.containsBean("stubFooDao")).isTrue(); @@ -482,12 +491,14 @@ class ClassPathBeanDefinitionScannerTests { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); scanner.setBeanNameGenerator(new TestBeanNameGenerator()); int beanCount = scanner.scan(BASE_PACKAGE); - assertThat(beanCount).isGreaterThanOrEqualTo(12); - context.refresh(); + assertThat(beanCount).isGreaterThanOrEqualTo(12); + + context.refresh(); FooServiceImpl fooService = context.getBean("fooService", FooServiceImpl.class); StaticListableBeanFactory myBf = (StaticListableBeanFactory) context.getBean("myBf"); MessageSource ms = (MessageSource) context.getBean("messageSource"); + assertThat(fooService.isInitCalled()).isTrue(); assertThat(fooService.foo(123)).isEqualTo("bar"); assertThat(fooService.lookupFoo(123)).isEqualTo("bar"); @@ -509,9 +520,10 @@ class ClassPathBeanDefinitionScannerTests { scanner.setIncludeAnnotationConfig(false); scanner.setBeanNameGenerator(new TestBeanNameGenerator()); int beanCount = scanner.scan(BASE_PACKAGE); - assertThat(beanCount).isGreaterThanOrEqualTo(7); - context.refresh(); + assertThat(beanCount).isGreaterThanOrEqualTo(7); + + context.refresh(); try { context.getBean("fooService"); } @@ -545,9 +557,78 @@ class ClassPathBeanDefinitionScannerTests { scanner.setAutowireCandidatePatterns("*NoSuchDao"); scanner.scan(BASE_PACKAGE); context.refresh(); - assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> - context.getBean("fooService")) - .satisfies(ex -> assertThat(ex.getMostSpecificCause()).isInstanceOf(NoSuchBeanDefinitionException.class)); + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(() -> context.getBean("fooService")) + .satisfies(ex -> + assertThat(ex.getMostSpecificCause()).isInstanceOf(NoSuchBeanDefinitionException.class)); + } + + @Test + void testWithManualProgrammaticIndex() { + // Pre-populating an index in order to replace a runtime scan + + GenericApplicationContext context = new GenericApplicationContext(); + context.setResourceLoader(new RestrictedResourcePatternResolver()); + + CandidateComponentsIndex index = new CandidateComponentsIndex(); + index.registerScan("example"); + index.registerCandidateType(ServiceInvocationCounter.class.getName(), Component.class.getName()); + index.registerCandidateType(FooServiceImpl.class.getName(), Component.class.getName()); + index.registerCandidateType(StubFooDao.class.getName(), Component.class.getName()); + CandidateComponentsIndexLoader.addIndex(context.getClassLoader(), index); + + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); + scanner.setIncludeAnnotationConfig(false); + int beanCount = scanner.scan(BASE_PACKAGE); // from index + CandidateComponentsIndexLoader.clearCache(); + + assertThat(beanCount).isEqualTo(3); + assertThat(context.containsBean("serviceInvocationCounter")).isTrue(); + assertThat(context.containsBean("fooServiceImpl")).isTrue(); + assertThat(context.containsBean("stubFooDao")).isTrue(); + } + + @Test + void testWithDerivedProgrammaticIndex() { + // Recording an index from a scan (e.g. during refreshForAotProcessing) + + GenericApplicationContext context = new GenericApplicationContext(); + + CandidateComponentsIndex scannedIndex = new CandidateComponentsIndex(); + CandidateComponentsIndexLoader.addIndex(context.getClassLoader(), scannedIndex); + + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); + scanner.scan(BASE_PACKAGE); // actual scan, populating the index instance above + CandidateComponentsIndexLoader.clearCache(); + + // Rebuilding a pre-computed index from the scanned index (AOT style) + // through String-based registerScan and registerCandidateType calls. + + context = new GenericApplicationContext(); + context.setResourceLoader(new RestrictedResourcePatternResolver()); + + CandidateComponentsIndex derivedIndex = new CandidateComponentsIndex(); + for (String basePackage : scannedIndex.getRegisteredScans()) { + derivedIndex.registerScan(basePackage); + } + for (String stereotype : scannedIndex.getRegisteredStereotypes()) { + for (String type : scannedIndex.getCandidateTypes(BASE_PACKAGE, stereotype)) { + derivedIndex.registerCandidateType(type, stereotype); + } + } + CandidateComponentsIndexLoader.addIndex(context.getClassLoader(), derivedIndex); + + scanner = new ClassPathBeanDefinitionScanner(context); + int beanCount = scanner.scan(BASE_PACKAGE); // from index + CandidateComponentsIndexLoader.clearCache(); + + assertThat(beanCount).isGreaterThanOrEqualTo(12); + assertThat(context.containsBean("serviceInvocationCounter")).isTrue(); + assertThat(context.containsBean("fooServiceImpl")).isTrue(); + assertThat(context.containsBean("stubFooDao")).isTrue(); + assertThat(context.containsBean("myNamedComponent")).isTrue(); + assertThat(context.containsBean("myNamedDao")).isTrue(); + assertThat(context.containsBean("thoreau")).isTrue(); } @@ -565,4 +646,14 @@ class ClassPathBeanDefinitionScannerTests { public class NonStaticInnerClass { } + + private static final class RestrictedResourcePatternResolver extends PathMatchingResourcePatternResolver { + + @Override + public Resource[] getResources(String locationPattern) throws IOException { + throw new UnsupportedOperationException(locationPattern); + } + } + + } diff --git a/spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexLoaderTests.java b/spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexLoaderTests.java index 8161f4f9359..32d4eb7c2a0 100644 --- a/spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexLoaderTests.java +++ b/spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexLoaderTests.java @@ -32,8 +32,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * * @author Stephane Nicoll */ -@Deprecated -@SuppressWarnings("removal") public class CandidateComponentsIndexLoaderTests { @Test diff --git a/spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexTests.java b/spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexTests.java index 2fbf17ce949..32befc3230e 100644 --- a/spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexTests.java +++ b/spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexTests.java @@ -30,8 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Stephane Nicoll */ -@Deprecated -@SuppressWarnings("removal") public class CandidateComponentsIndexTests { @Test diff --git a/spring-context/src/test/resources/example/scannable/spring.components b/spring-context/src/test/resources/example/scannable/spring.components index c56e0854cb8..a8e99afa391 100644 --- a/spring-context/src/test/resources/example/scannable/spring.components +++ b/spring-context/src/test/resources/example/scannable/spring.components @@ -9,6 +9,5 @@ example.scannable.ScopedProxyTestBean=example.scannable.FooService example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component example.scannable.StubFooDao=org.springframework.stereotype.Component example.scannable.sub.BarComponent=org.springframework.stereotype.Component -example.scannable.JakartaManagedBeanComponent=jakarta.annotation.ManagedBean example.scannable.JakartaNamedComponent=jakarta.inject.Named example.indexed.IndexedJakartaNamedComponent=jakarta.inject.Named