Skip to content

Commit 76cceef

Browse files
committed
Introduce TypeCollectorPredicateProvider.
We now provide a SPI for libraries that want to provide predicates for AOT processing so that AOT processing not only considers its own rules but also e.g. exclusions from other participants on the class path.
1 parent 880a14e commit 76cceef

File tree

4 files changed

+177
-49
lines changed

4 files changed

+177
-49
lines changed

src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContribution {
7676

7777
private final AotContext aotContext;
78-
private final ManagedTypes managedTypes;
7978
private final Lazy<List<Class<?>>> sourceTypes;
8079
private final Consumer<TypeCollector> typeCollectorCustomizer;
8180
private final TypeRegistration contributionAction;
@@ -86,7 +85,6 @@ public ManagedTypesRegistrationAotContribution(AotContext aotContext, ManagedTyp
8685
TypeRegistration contributionAction) {
8786

8887
this.aotContext = aotContext;
89-
this.managedTypes = managedTypes;
9088
this.sourceTypes = Lazy.of(managedTypes::toList);
9189
this.typeCollectorCustomizer = typeCollectorCustomizer;
9290
this.contributionAction = contributionAction;

src/main/java/org/springframework/data/util/Predicates.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ public interface Predicates {
5151

5252
Predicate<Method> IS_BRIDGE_METHOD = Method::isBridge;
5353

54+
/**
55+
* A {@link Predicate} that introspects the declaring class of the member.
56+
*
57+
* @return a {@link Predicate} that introspects the declaring class of the member.
58+
* @since 4.0
59+
*/
60+
static <T extends Member> Predicate<T> declaringClass(Predicate<Class<?>> predicate) {
61+
return t -> predicate.test(t.getDeclaringClass());
62+
}
63+
5464
/**
5565
* A {@link Predicate} that yields always {@code true}.
5666
*

src/main/java/org/springframework/data/util/TypeCollector.java

Lines changed: 165 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.commons.logging.Log;
3737
import org.apache.commons.logging.LogFactory;
3838

39+
import org.springframework.beans.factory.aot.AotServices;
3940
import org.springframework.core.ResolvableType;
4041
import org.springframework.lang.Contract;
4142
import org.springframework.util.ObjectUtils;
@@ -46,44 +47,83 @@
4647
* <p>
4748
* Type inspection walks through all class members (fields, methods, constructors) and introspects those for additional
4849
* types that are part of the domain model.
50+
* <p>
51+
* Type collection can be customized by providing filters that stop introspection when encountering a {@link Predicate}
52+
* that returns {@code false}. Filters are {@link Predicate#and(Predicate) combined} so that multiple filters can be
53+
* taken into account. A type/field/method must pass all filters to be considered for further inspection.
54+
* <p>
55+
* The collector uses {@link AotServices} to discover implementations of {@link TypeCollectorPredicateProvider} so that
56+
* components using {@link TypeCollector} can contribute their own filtering logic to exclude types, fields, and methods
57+
* from being inspected.
4958
*
5059
* @author Christoph Strobl
5160
* @author Sebastien Deleuze
5261
* @author John Blum
62+
* @author Mark Paluch
5363
* @since 3.0
5464
*/
5565
public class TypeCollector {
5666

5767
private static final Log logger = LogFactory.getLog(TypeCollector.class);
5868

59-
static final Set<String> EXCLUDED_DOMAINS = Set.of("java", "sun.", "jdk.", "reactor.", "kotlinx.", "kotlin.", "org.springframework.core.",
60-
"org.springframework.data.mapping.", "org.springframework.data.repository.", "org.springframework.boot.",
61-
"org.springframework.context.", "org.springframework.beans.");
69+
private static final AotServices<TypeCollectorPredicateProvider> providers = AotServices.factories()
70+
.load(TypeCollectorPredicateProvider.class);
6271

63-
private final Predicate<Class<?>> excludedDomainsFilter = type -> {
64-
String packageName = type.getPackageName() + ".";
65-
return EXCLUDED_DOMAINS.stream().noneMatch(packageName::startsWith);
66-
};
72+
private Predicate<Class<?>> typeFilter = Predicates.isTrue();
6773

68-
private Predicate<Class<?>> typeFilter = excludedDomainsFilter
69-
.and(it -> !it.isLocalClass() && !it.isAnonymousClass());
74+
private Predicate<Method> methodFilter = Predicates.isTrue();
7075

71-
private final Predicate<Method> methodFilter = createMethodFilter();
76+
private Predicate<Field> fieldFilter = Predicates.isTrue();
7277

73-
private Predicate<Field> fieldFilter = createFieldFilter();
78+
/**
79+
* Create a new {@link TypeCollector} applying all {@link TypeCollectorPredicateProvider} discovered through
80+
* {@link AotServices}.
81+
*/
82+
public TypeCollector() {
7483

75-
@Contract("_ -> this")
76-
public TypeCollector filterFields(Predicate<Field> filter) {
77-
this.fieldFilter = filter.and(filter);
78-
return this;
84+
providers.forEach(provider -> {
85+
filterTypes(provider.classPredicate());
86+
filterMethods(provider.methodPredicate());
87+
filterFields(provider.fieldPredicate());
88+
});
7989
}
8090

91+
/**
92+
* Add a filter to exclude types from being introspected.
93+
*
94+
* @param filter filter predicate matching a {@link Class}.
95+
* @return {@code this} TypeCollector instance.
96+
*/
8197
@Contract("_ -> this")
8298
public TypeCollector filterTypes(Predicate<Class<?>> filter) {
8399
this.typeFilter = this.typeFilter.and(filter);
84100
return this;
85101
}
86102

103+
/**
104+
* Add a filter to exclude methods from being introspected.
105+
*
106+
* @param filter filter predicate matching a {@link Class}.
107+
* @return {@code this} TypeCollector instance.
108+
*/
109+
@Contract("_ -> this")
110+
public TypeCollector filterMethods(Predicate<Method> filter) {
111+
this.methodFilter = filter.and(filter);
112+
return this;
113+
}
114+
115+
/**
116+
* Add a filter to exclude fields from being introspected.
117+
*
118+
* @param filter filter predicate matching a {@link Class}.
119+
* @return {@code this} TypeCollector instance.
120+
*/
121+
@Contract("_ -> this")
122+
public TypeCollector filterFields(Predicate<Field> filter) {
123+
this.fieldFilter = filter.and(filter);
124+
return this;
125+
}
126+
87127
/**
88128
* Inspect the given type and resolve those reachable via fields, methods, generics, ...
89129
*
@@ -162,7 +202,7 @@ private void processType(ResolvableType type, InspectionCache cache, Consumer<Re
162202
}
163203
}
164204

165-
Set<Type> visitConstructorsOfType(ResolvableType type) {
205+
private Set<Type> visitConstructorsOfType(ResolvableType type) {
166206

167207
if (!typeFilter.test(type.toClass())) {
168208
return Collections.emptySet();
@@ -185,7 +225,7 @@ Set<Type> visitConstructorsOfType(ResolvableType type) {
185225
return new HashSet<>(discoveredTypes);
186226
}
187227

188-
Set<Type> visitMethodsOfType(ResolvableType type) {
228+
private Set<Type> visitMethodsOfType(ResolvableType type) {
189229

190230
if (!typeFilter.test(type.toClass())) {
191231
return Collections.emptySet();
@@ -210,7 +250,7 @@ Set<Type> visitMethodsOfType(ResolvableType type) {
210250
return new HashSet<>(discoveredTypes);
211251
}
212252

213-
Set<Type> visitFieldsOfType(ResolvableType type) {
253+
private Set<Type> visitFieldsOfType(ResolvableType type) {
214254

215255
Set<Type> discoveredTypes = new LinkedHashSet<>();
216256

@@ -228,35 +268,6 @@ Set<Type> visitFieldsOfType(ResolvableType type) {
228268
return discoveredTypes;
229269
}
230270

231-
private Predicate<Method> createMethodFilter() {
232-
233-
Predicate<Method> excludedDomainsPredicate = methodToTest -> excludedDomainsFilter
234-
.test(methodToTest.getDeclaringClass());
235-
236-
Predicate<Method> excludedMethodsPredicate = Predicates.IS_BRIDGE_METHOD //
237-
.or(Predicates.IS_STATIC) //
238-
.or(Predicates.IS_SYNTHETIC) //
239-
.or(Predicates.IS_NATIVE) //
240-
.or(Predicates.IS_PRIVATE) //
241-
.or(Predicates.IS_PROTECTED) //
242-
.or(Predicates.IS_OBJECT_MEMBER) //
243-
.or(Predicates.IS_HIBERNATE_MEMBER) //
244-
.or(Predicates.IS_ENUM_MEMBER) //
245-
.or(excludedDomainsPredicate.negate()); //
246-
247-
return excludedMethodsPredicate.negate();
248-
}
249-
250-
@SuppressWarnings("rawtypes")
251-
private Predicate<Field> createFieldFilter() {
252-
253-
Predicate<Member> excludedFieldPredicate = Predicates.IS_HIBERNATE_MEMBER //
254-
.or(Predicates.IS_SYNTHETIC) //
255-
.or(Predicates.IS_JAVA);
256-
257-
return (Predicate) excludedFieldPredicate.negate();
258-
}
259-
260271
/**
261272
* Container for reachable types starting from a set of root types.
262273
*/
@@ -297,6 +308,7 @@ private List<Class<?>> collect() {
297308
forEach(it -> target.add(it.toClass()));
298309
return List.copyOf(target);
299310
}
311+
300312
}
301313

302314
static class InspectionCache {
@@ -322,5 +334,111 @@ public boolean isEmpty() {
322334
public int size() {
323335
return mutableCache.size();
324336
}
337+
338+
}
339+
340+
/**
341+
* Strategy interface providing predicates to filter types, fields, and methods from being introspected and
342+
* contributed to AOT processing.
343+
* <p>
344+
* {@code BeanRegistrationAotProcessor} implementations must be registered in a
345+
* {@value AotServices#FACTORIES_RESOURCE_LOCATION} resource. This interface serves as SPI and can be provided through
346+
* {@link org.springframework.beans.factory.aot.AotServices}.
347+
* <p>
348+
* {@link TypeCollector} discovers all implementations and applies the combined predicates returned by this interface
349+
* to filter unwanted reachable types from AOT contribution.
350+
*
351+
* @author Mark Paluch
352+
* @since 4.0
353+
*/
354+
public interface TypeCollectorPredicateProvider {
355+
356+
/**
357+
* Return a predicate to filter types.
358+
*
359+
* @return a predicate to filter types.
360+
*/
361+
default Predicate<Class<?>> classPredicate() {
362+
return Predicates.isTrue();
363+
}
364+
365+
/**
366+
* Return a predicate to filter fields.
367+
*
368+
* @return a predicate to filter fields.
369+
*/
370+
default Predicate<Field> fieldPredicate() {
371+
return Predicates.isTrue();
372+
}
373+
374+
/**
375+
* Return a predicate to filter methods for method signature introspection. not provided.
376+
*
377+
* @return a predicate to filter methods.
378+
*/
379+
default Predicate<Method> methodPredicate() {
380+
return Predicates.isTrue();
381+
}
382+
383+
}
384+
385+
/**
386+
* Default implementation of {@link TypeCollectorPredicateProvider} that excludes types from certain packages and
387+
* filters out unwanted fields and methods.
388+
*
389+
* @since 4.0
390+
*/
391+
private static class DefaultTypeCollectorPredicateProvider implements TypeCollectorPredicateProvider {
392+
393+
private static final Set<String> EXCLUDED_DOMAINS = Set.of("java", "sun.", "jdk.", "reactor.", "kotlinx.",
394+
"kotlin.", "org.springframework.core.", "org.springframework.data.mapping.",
395+
"org.springframework.data.repository.", "org.springframework.boot.", "org.springframework.context.",
396+
"org.springframework.beans.");
397+
398+
private static final Predicate<Class<?>> PACKAGE_PREDICATE = type -> {
399+
400+
String packageName = type.getPackageName() + ".";
401+
402+
for (String excludedDomain : EXCLUDED_DOMAINS) {
403+
if (packageName.startsWith(excludedDomain)) {
404+
return true;
405+
}
406+
}
407+
408+
return false;
409+
};
410+
411+
private static final Predicate<Class<?>> UNREACHABLE_CLASS = type -> type.isLocalClass() || type.isAnonymousClass();
412+
413+
private static final Predicate<Member> UNWANTED_FIELDS = Predicates.IS_SYNTHETIC //
414+
.or(Predicates.IS_JAVA) //
415+
.or(Predicates.declaringClass(PACKAGE_PREDICATE));
416+
417+
private static final Predicate<Method> UNWANTED_METHODS = Predicates.IS_BRIDGE_METHOD //
418+
.or(Predicates.IS_STATIC) //
419+
.or(Predicates.IS_SYNTHETIC) //
420+
.or(Predicates.IS_NATIVE) //
421+
.or(Predicates.IS_PRIVATE) //
422+
.or(Predicates.IS_PROTECTED) //
423+
.or(Predicates.IS_OBJECT_MEMBER) //
424+
.or(Predicates.IS_ENUM_MEMBER) //
425+
.or(Predicates.declaringClass(PACKAGE_PREDICATE));
426+
427+
@Override
428+
public Predicate<Class<?>> classPredicate() {
429+
return UNREACHABLE_CLASS.or(PACKAGE_PREDICATE).negate();
430+
}
431+
432+
@Override
433+
public Predicate<Field> fieldPredicate() {
434+
return (Predicate) UNWANTED_FIELDS.negate();
435+
}
436+
437+
@Override
438+
public Predicate<Method> methodPredicate() {
439+
return UNWANTED_METHODS.negate();
440+
}
441+
325442
}
443+
326444
}

src/main/resources/META-INF/spring/aot.factories

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\
88

99
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
1010
org.springframework.data.aot.AuditingBeanRegistrationAotProcessor
11+
org.springframework.data.util.TypeCollector$TypeCollectorPredicateProvider=\
12+
org.springframework.data.util.TypeCollector$DefaultTypeCollectorPredicateProvider

0 commit comments

Comments
 (0)