Browse Source

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.

See: #3362
Original Pull Request: #3363
pull/3368/head
Mark Paluch 3 months ago committed by Christoph Strobl
parent
commit
0dc961c1a1
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 2
      src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java
  2. 10
      src/main/java/org/springframework/data/util/Predicates.java
  3. 212
      src/main/java/org/springframework/data/util/TypeCollector.java
  4. 2
      src/main/resources/META-INF/spring/aot.factories

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

@ -75,7 +75,6 @@ import org.springframework.util.ReflectionUtils; @@ -75,7 +75,6 @@ import org.springframework.util.ReflectionUtils;
class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContribution {
private final AotContext aotContext;
private final ManagedTypes managedTypes;
private final Lazy<List<Class<?>>> sourceTypes;
private final Consumer<TypeCollector> typeCollectorCustomizer;
private final TypeRegistration contributionAction;
@ -86,7 +85,6 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri @@ -86,7 +85,6 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri
TypeRegistration contributionAction) {
this.aotContext = aotContext;
this.managedTypes = managedTypes;
this.sourceTypes = Lazy.of(managedTypes::toList);
this.typeCollectorCustomizer = typeCollectorCustomizer;
this.contributionAction = contributionAction;

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

@ -51,6 +51,16 @@ public interface Predicates { @@ -51,6 +51,16 @@ public interface Predicates {
Predicate<Method> IS_BRIDGE_METHOD = Method::isBridge;
/**
* A {@link Predicate} that introspects the declaring class of the member.
*
* @return a {@link Predicate} that introspects the declaring class of the member.
* @since 4.0
*/
static <T extends Member> Predicate<T> declaringClass(Predicate<Class<?>> predicate) {
return t -> predicate.test(t.getDeclaringClass());
}
/**
* A {@link Predicate} that yields always {@code true}.
*

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

@ -36,6 +36,7 @@ import java.util.function.Predicate; @@ -36,6 +36,7 @@ import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.aot.AotServices;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Contract;
import org.springframework.util.ObjectUtils;
@ -46,44 +47,83 @@ import org.springframework.util.ReflectionUtils; @@ -46,44 +47,83 @@ import org.springframework.util.ReflectionUtils;
* <p>
* Type inspection walks through all class members (fields, methods, constructors) and introspects those for additional
* types that are part of the domain model.
* <p>
* Type collection can be customized by providing filters that stop introspection when encountering a {@link Predicate}
* that returns {@code false}. Filters are {@link Predicate#and(Predicate) combined} so that multiple filters can be
* taken into account. A type/field/method must pass all filters to be considered for further inspection.
* <p>
* The collector uses {@link AotServices} to discover implementations of {@link TypeCollectorPredicateProvider} so that
* components using {@link TypeCollector} can contribute their own filtering logic to exclude types, fields, and methods
* from being inspected.
*
* @author Christoph Strobl
* @author Sebastien Deleuze
* @author John Blum
* @author Mark Paluch
* @since 3.0
*/
public class TypeCollector {
private static final Log logger = LogFactory.getLog(TypeCollector.class);
static final Set<String> EXCLUDED_DOMAINS = Set.of("java", "sun.", "jdk.", "reactor.", "kotlinx.", "kotlin.", "org.springframework.core.",
"org.springframework.data.mapping.", "org.springframework.data.repository.", "org.springframework.boot.",
"org.springframework.context.", "org.springframework.beans.");
private static final AotServices<TypeCollectorPredicateProvider> providers = AotServices.factories()
.load(TypeCollectorPredicateProvider.class);
private final Predicate<Class<?>> excludedDomainsFilter = type -> {
String packageName = type.getPackageName() + ".";
return EXCLUDED_DOMAINS.stream().noneMatch(packageName::startsWith);
};
private Predicate<Class<?>> typeFilter = Predicates.isTrue();
private Predicate<Class<?>> typeFilter = excludedDomainsFilter
.and(it -> !it.isLocalClass() && !it.isAnonymousClass());
private Predicate<Method> methodFilter = Predicates.isTrue();
private final Predicate<Method> methodFilter = createMethodFilter();
private Predicate<Field> fieldFilter = Predicates.isTrue();
private Predicate<Field> fieldFilter = createFieldFilter();
/**
* Create a new {@link TypeCollector} applying all {@link TypeCollectorPredicateProvider} discovered through
* {@link AotServices}.
*/
public TypeCollector() {
@Contract("_ -> this")
public TypeCollector filterFields(Predicate<Field> filter) {
this.fieldFilter = filter.and(filter);
return this;
providers.forEach(provider -> {
filterTypes(provider.classPredicate());
filterMethods(provider.methodPredicate());
filterFields(provider.fieldPredicate());
});
}
/**
* Add a filter to exclude types from being introspected.
*
* @param filter filter predicate matching a {@link Class}.
* @return {@code this} TypeCollector instance.
*/
@Contract("_ -> this")
public TypeCollector filterTypes(Predicate<Class<?>> filter) {
this.typeFilter = this.typeFilter.and(filter);
return this;
}
/**
* Add a filter to exclude methods from being introspected.
*
* @param filter filter predicate matching a {@link Class}.
* @return {@code this} TypeCollector instance.
*/
@Contract("_ -> this")
public TypeCollector filterMethods(Predicate<Method> filter) {
this.methodFilter = methodFilter.and(filter);
return this;
}
/**
* Add a filter to exclude fields from being introspected.
*
* @param filter filter predicate matching a {@link Class}.
* @return {@code this} TypeCollector instance.
*/
@Contract("_ -> this")
public TypeCollector filterFields(Predicate<Field> filter) {
this.fieldFilter = fieldFilter.and(filter);
return this;
}
/**
* Inspect the given type and resolve those reachable via fields, methods, generics, ...
*
@ -162,7 +202,7 @@ public class TypeCollector { @@ -162,7 +202,7 @@ public class TypeCollector {
}
}
Set<Type> visitConstructorsOfType(ResolvableType type) {
private Set<Type> visitConstructorsOfType(ResolvableType type) {
if (!typeFilter.test(type.toClass())) {
return Collections.emptySet();
@ -185,7 +225,7 @@ public class TypeCollector { @@ -185,7 +225,7 @@ public class TypeCollector {
return new HashSet<>(discoveredTypes);
}
Set<Type> visitMethodsOfType(ResolvableType type) {
private Set<Type> visitMethodsOfType(ResolvableType type) {
if (!typeFilter.test(type.toClass())) {
return Collections.emptySet();
@ -210,7 +250,7 @@ public class TypeCollector { @@ -210,7 +250,7 @@ public class TypeCollector {
return new HashSet<>(discoveredTypes);
}
Set<Type> visitFieldsOfType(ResolvableType type) {
private Set<Type> visitFieldsOfType(ResolvableType type) {
Set<Type> discoveredTypes = new LinkedHashSet<>();
@ -228,35 +268,6 @@ public class TypeCollector { @@ -228,35 +268,6 @@ public class TypeCollector {
return discoveredTypes;
}
private Predicate<Method> createMethodFilter() {
Predicate<Method> excludedDomainsPredicate = methodToTest -> excludedDomainsFilter
.test(methodToTest.getDeclaringClass());
Predicate<Method> excludedMethodsPredicate = Predicates.IS_BRIDGE_METHOD //
.or(Predicates.IS_STATIC) //
.or(Predicates.IS_SYNTHETIC) //
.or(Predicates.IS_NATIVE) //
.or(Predicates.IS_PRIVATE) //
.or(Predicates.IS_PROTECTED) //
.or(Predicates.IS_OBJECT_MEMBER) //
.or(Predicates.IS_HIBERNATE_MEMBER) //
.or(Predicates.IS_ENUM_MEMBER) //
.or(excludedDomainsPredicate.negate()); //
return excludedMethodsPredicate.negate();
}
@SuppressWarnings("rawtypes")
private Predicate<Field> createFieldFilter() {
Predicate<Member> excludedFieldPredicate = Predicates.IS_HIBERNATE_MEMBER //
.or(Predicates.IS_SYNTHETIC) //
.or(Predicates.IS_JAVA);
return (Predicate) excludedFieldPredicate.negate();
}
/**
* Container for reachable types starting from a set of root types.
*/
@ -297,6 +308,7 @@ public class TypeCollector { @@ -297,6 +308,7 @@ public class TypeCollector {
forEach(it -> target.add(it.toClass()));
return List.copyOf(target);
}
}
static class InspectionCache {
@ -322,5 +334,111 @@ public class TypeCollector { @@ -322,5 +334,111 @@ public class TypeCollector {
public int size() {
return mutableCache.size();
}
}
/**
* Strategy interface providing predicates to filter types, fields, and methods from being introspected and
* contributed to AOT processing.
* <p>
* {@code BeanRegistrationAotProcessor} implementations must be registered in a
* {@value AotServices#FACTORIES_RESOURCE_LOCATION} resource. This interface serves as SPI and can be provided through
* {@link org.springframework.beans.factory.aot.AotServices}.
* <p>
* {@link TypeCollector} discovers all implementations and applies the combined predicates returned by this interface
* to filter unwanted reachable types from AOT contribution.
*
* @author Mark Paluch
* @since 4.0
*/
public interface TypeCollectorPredicateProvider {
/**
* Return a predicate to filter types.
*
* @return a predicate to filter types.
*/
default Predicate<Class<?>> classPredicate() {
return Predicates.isTrue();
}
/**
* Return a predicate to filter fields.
*
* @return a predicate to filter fields.
*/
default Predicate<Field> fieldPredicate() {
return Predicates.isTrue();
}
/**
* Return a predicate to filter methods for method signature introspection. not provided.
*
* @return a predicate to filter methods.
*/
default Predicate<Method> methodPredicate() {
return Predicates.isTrue();
}
}
/**
* Default implementation of {@link TypeCollectorPredicateProvider} that excludes types from certain packages and
* filters out unwanted fields and methods.
*
* @since 4.0
*/
private static class DefaultTypeCollectorPredicateProvider implements TypeCollectorPredicateProvider {
private static final Set<String> EXCLUDED_DOMAINS = Set.of("java", "sun.", "jdk.", "reactor.", "kotlinx.",
"kotlin.", "org.springframework.core.", "org.springframework.data.mapping.",
"org.springframework.data.repository.", "org.springframework.boot.", "org.springframework.context.",
"org.springframework.beans.");
private static final Predicate<Class<?>> PACKAGE_PREDICATE = type -> {
String packageName = type.getPackageName() + ".";
for (String excludedDomain : EXCLUDED_DOMAINS) {
if (packageName.startsWith(excludedDomain)) {
return true;
}
}
return false;
};
private static final Predicate<Class<?>> UNREACHABLE_CLASS = type -> type.isLocalClass() || type.isAnonymousClass();
private static final Predicate<Member> UNWANTED_FIELDS = Predicates.IS_SYNTHETIC //
.or(Predicates.IS_JAVA) //
.or(Predicates.declaringClass(PACKAGE_PREDICATE));
private static final Predicate<Method> UNWANTED_METHODS = Predicates.IS_BRIDGE_METHOD //
.or(Predicates.IS_STATIC) //
.or(Predicates.IS_SYNTHETIC) //
.or(Predicates.IS_NATIVE) //
.or(Predicates.IS_PRIVATE) //
.or(Predicates.IS_PROTECTED) //
.or(Predicates.IS_OBJECT_MEMBER) //
.or(Predicates.IS_ENUM_MEMBER) //
.or(Predicates.declaringClass(PACKAGE_PREDICATE));
@Override
public Predicate<Class<?>> classPredicate() {
return UNREACHABLE_CLASS.or(PACKAGE_PREDICATE).negate();
}
@Override
public Predicate<Field> fieldPredicate() {
return (Predicate) UNWANTED_FIELDS.negate();
}
@Override
public Predicate<Method> methodPredicate() {
return UNWANTED_METHODS.negate();
}
}
}

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

@ -8,3 +8,5 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ @@ -8,3 +8,5 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.data.aot.AuditingBeanRegistrationAotProcessor
org.springframework.data.util.TypeCollector$TypeCollectorPredicateProvider=\
org.springframework.data.util.TypeCollector$DefaultTypeCollectorPredicateProvider

Loading…
Cancel
Save