mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Add support for programmatic CandidateComponentsIndex setup
Closes gh-35497 See gh-35472
This commit is contained in:
+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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user