3636import org .apache .commons .logging .Log ;
3737import org .apache .commons .logging .LogFactory ;
3838
39+ import org .springframework .beans .factory .aot .AotServices ;
3940import org .springframework .core .ResolvableType ;
4041import org .springframework .lang .Contract ;
4142import org .springframework .util .ObjectUtils ;
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 */
5565public 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 = methodFilter .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 = fieldFilter .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+
325383 }
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+
442+ }
443+
326444}
0 commit comments