mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 08:24:13 +00:00
Add support for programmatic CandidateComponentsIndex setup
Closes gh-35497 See gh-35472
This commit is contained in:
+3
-2
@@ -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 {
|
||||
|
||||
+2
-3
@@ -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 {
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+41
-11
@@ -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
|
||||
* @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
|
||||
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
|
||||
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
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
+76
-18
@@ -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<String, Entry> index;
|
||||
private final Set<String> registeredScans = new LinkedHashSet<>();
|
||||
|
||||
private final MultiValueMap<String, Entry> index = new LinkedMultiValueMap<>();
|
||||
|
||||
private final boolean complete;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new index instance from parsed components index files.
|
||||
*/
|
||||
CandidateComponentsIndex(List<Properties> content) {
|
||||
this.index = parseIndex(content);
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, Entry> parseIndex(List<Properties> content) {
|
||||
MultiValueMap<String, Entry> 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<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.
|
||||
@@ -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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-3
@@ -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.
|
||||
* <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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+120
-29
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -32,8 +32,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
public class CandidateComponentsIndexLoaderTests {
|
||||
|
||||
@Test
|
||||
|
||||
-2
@@ -30,8 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
public class CandidateComponentsIndexTests {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user