Browse Source

Add support for programmatic CandidateComponentsIndex setup

Closes gh-35497
See gh-35472
pull/35603/head
Juergen Hoeller 2 months ago
parent
commit
7bc2a7f3f2
  1. 5
      spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java
  2. 5
      spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java
  3. 52
      spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java
  4. 94
      spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java
  5. 28
      spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java
  6. 137
      spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java
  7. 2
      spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexLoaderTests.java
  8. 2
      spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexTests.java
  9. 1
      spring-context/src/test/resources/example/scannable/spring.components

5
spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java

@ -37,12 +37,13 @@ import javax.lang.model.element.TypeElement; @@ -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 {

5
spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java

@ -25,8 +25,7 @@ import javax.lang.model.element.ElementKind; @@ -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 <i>present</i> on a class or
* interface.
* {@code jakarta.*} annotation <i>present</i> on a class or interface.
*
* @author Stephane Nicoll
* @since 5.0
@ -50,7 +49,7 @@ class StandardStereotypesProvider implements StereotypesProvider { @@ -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);
}
}

52
spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java

@ -312,11 +312,14 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC @@ -312,11 +312,14 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
*/
public Set<BeanDefinition> 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 @@ -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<? extends Annotation> 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 @@ -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<? extends Annotation> 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 @@ -372,6 +396,11 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
return null;
}
private boolean isStereotypeAnnotationForIndex(Class<? extends Annotation> annotationType) {
return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) ||
annotationType.getName().startsWith("jakarta."));
}
private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
@ -503,13 +532,14 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC @@ -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);
}
}

94
spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java

@ -17,6 +17,7 @@ @@ -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,35 +45,88 @@ import org.springframework.util.MultiValueMap; @@ -44,35 +45,88 @@ 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<String, Entry> index;
private final Set<String> registeredScans = new LinkedHashSet<>();
private final MultiValueMap<String, Entry> index = new LinkedMultiValueMap<>();
private final boolean complete;
CandidateComponentsIndex(List<Properties> content) {
this.index = parseIndex(content);
}
private static MultiValueMap<String, Entry> parseIndex(List<Properties> content) {
MultiValueMap<String, Entry> index = new LinkedMultiValueMap<>();
/**
* Create a new index instance from parsed components index files.
*/
CandidateComponentsIndex(List<Properties> content) {
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<String> 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<String> getRegisteredStereotypes() {
return this.index.keySet();
}
/**
* Return the candidate types that are associated with the specified stereotype.
* @param basePackage the package to check for candidates
@ -83,7 +137,7 @@ public class CandidateComponentsIndex { @@ -83,7 +137,7 @@ public class CandidateComponentsIndex {
public Set<String> getCandidateTypes(String basePackage, String stereotype) {
List<Entry> 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 { @@ -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 { @@ -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);
}
}

28
spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java

@ -37,11 +37,9 @@ import org.springframework.util.ConcurrentReferenceHashMap; @@ -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 { @@ -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.
* <p>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();
}
}

137
spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java

@ -16,10 +16,13 @@ @@ -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; @@ -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 { @@ -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 { @@ -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,22 +113,16 @@ class ClassPathBeanDefinitionScannerTests { @@ -105,22 +113,16 @@ 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);
ClassPathBeanDefinitionScanner scanner2 = new ClassPathBeanDefinitionScanner(context) {
@Override
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
super.postProcessBeanDefinition(beanDefinition, beanName);
beanDefinition.setAttribute("someDifference", "someValue");
}
};
scanner2.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(12);
assertThat(context.containsBean("serviceInvocationCounter")).isTrue();
assertThat(context.containsBean("fooServiceImpl")).isTrue();
assertThat(context.containsBean("stubFooDao")).isTrue();
@ -130,16 +132,22 @@ class ClassPathBeanDefinitionScannerTests { @@ -130,16 +132,22 @@ class ClassPathBeanDefinitionScannerTests {
}
@Test
void testWithIndex() {
void testDoubleScan() {
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);
ClassPathBeanDefinitionScanner scanner2 = new ClassPathBeanDefinitionScanner(context) {
@Override
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
super.postProcessBeanDefinition(beanDefinition, beanName);
beanDefinition.setAttribute("someDifference", "someValue");
}
};
scanner2.scan(BASE_PACKAGE);
assertThat(context.containsBean("serviceInvocationCounter")).isTrue();
assertThat(context.containsBean("fooServiceImpl")).isTrue();
assertThat(context.containsBean("stubFooDao")).isTrue();
@ -157,6 +165,7 @@ class ClassPathBeanDefinitionScannerTests { @@ -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 { @@ -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 { @@ -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();
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 { @@ -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();
context.refresh();
try {
context.getBean("fooService");
}
@ -545,9 +557,78 @@ class ClassPathBeanDefinitionScannerTests { @@ -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 { @@ -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);
}
}
}

2
spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexLoaderTests.java

@ -32,8 +32,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -32,8 +32,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*
* @author Stephane Nicoll
*/
@Deprecated
@SuppressWarnings("removal")
public class CandidateComponentsIndexLoaderTests {
@Test

2
spring-context/src/test/java/org/springframework/context/index/CandidateComponentsIndexTests.java

@ -30,8 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -30,8 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Stephane Nicoll
*/
@Deprecated
@SuppressWarnings("removal")
public class CandidateComponentsIndexTests {
@Test

1
spring-context/src/test/resources/example/scannable/spring.components

@ -9,6 +9,5 @@ example.scannable.ScopedProxyTestBean=example.scannable.FooService @@ -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

Loading…
Cancel
Save