Resolve package cycle between repository.config and repository.aot.

Closes #2708
This commit is contained in:
Christoph Strobl
2022-10-25 15:53:56 +02:00
committed by Mark Paluch
parent 0e00d5fd3f
commit 8d424855da
20 changed files with 209 additions and 109 deletions

View File

@@ -31,6 +31,8 @@ import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.ManagedTypes;
import org.springframework.data.util.TypeContributor;
import org.springframework.data.util.TypeUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

View File

@@ -35,6 +35,7 @@ import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.ManagedTypes;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeCollector;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec.Builder;

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.aot;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Predicate;
/**
* Abstract utility class containing common, reusable {@link Predicate Predicates}.
*
* @author John Blum
* @see java.util.function.Predicate
* @since 3.0
*/
// TODO: Consider moving to the org.springframework.data.util package.
@SuppressWarnings("unused")
public abstract class Predicates {
public static final Predicate<Member> IS_ENUM_MEMBER = member -> member.getDeclaringClass().isEnum();
public static final Predicate<Member> IS_HIBERNATE_MEMBER = member -> member.getName().startsWith("$$_hibernate"); // this
// should
// go
// into
// JPA
public static final Predicate<Member> IS_OBJECT_MEMBER = member -> Object.class.equals(member.getDeclaringClass());
public static final Predicate<Member> IS_JAVA = member -> member.getDeclaringClass().getPackageName().startsWith("java.");
public static final Predicate<Member> IS_NATIVE = member -> Modifier.isNative(member.getModifiers());
public static final Predicate<Member> IS_PRIVATE = member -> Modifier.isPrivate(member.getModifiers());
public static final Predicate<Member> IS_PROTECTED = member -> Modifier.isProtected(member.getModifiers());
public static final Predicate<Member> IS_PUBLIC = member -> Modifier.isPublic(member.getModifiers());
public static final Predicate<Member> IS_SYNTHETIC = Member::isSynthetic;
public static final Predicate<Member> IS_STATIC = member -> Modifier.isStatic(member.getModifiers());
public static final Predicate<Method> IS_BRIDGE_METHOD = Method::isBridge;
}

View File

@@ -1,257 +0,0 @@
/*
* Copyright 2019-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.aot;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.ResolvableType;
import org.springframework.data.util.Lazy;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Christoph Strobl
* @author Sebastien Deleuze
* @author John Blum
*/
public class TypeCollector {
private static final Log logger = LogFactory.getLog(TypeCollector.class);
static final Set<String> EXCLUDED_DOMAINS = new HashSet<>(Arrays.asList("java", "sun.", "jdk.", "reactor.",
"kotlinx.", "kotlin.", "org.springframework.core.", "org.springframework.data.mapping.",
"org.springframework.data.repository.", "org.springframework.boot.", "org.springframework.core."));
private final Predicate<Class<?>> excludedDomainsFilter = type -> {
String packageName = type.getPackageName();
return EXCLUDED_DOMAINS.stream().noneMatch(packageName::startsWith);
};
private Predicate<Class<?>> typeFilter = excludedDomainsFilter;
private final Predicate<Method> methodFilter = createMethodFilter();
private Predicate<Field> fieldFilter = createFieldFilter();
public TypeCollector filterFields(Predicate<Field> filter) {
this.fieldFilter = filter.and(filter);
return this;
}
public TypeCollector filterTypes(Predicate<Class<?>> filter) {
this.typeFilter = this.typeFilter.and(filter);
return this;
}
/**
* Inspect the given type and resolve those reachable via fields, methods, generics, ...
*
* @param types the types to inspect
* @return a type model collector for the type
*/
public static ReachableTypes inspect(Class<?>... types) {
return inspect(Arrays.asList(types));
}
public static ReachableTypes inspect(Collection<Class<?>> types) {
return new ReachableTypes(new TypeCollector(), types);
}
private void process(Class<?> root, Consumer<ResolvableType> consumer) {
processType(ResolvableType.forType(root), new InspectionCache(), consumer);
}
private void processType(ResolvableType type, InspectionCache cache, Consumer<ResolvableType> callback) {
if (ResolvableType.NONE.equals(type) || cache.contains(type) || type.toClass().isSynthetic()) {
return;
}
cache.add(type);
// continue inspection but only add those matching the filter criteria to the result
if (typeFilter.test(type.toClass())) {
callback.accept(type);
}
Set<Type> additionalTypes = new LinkedHashSet<>();
additionalTypes.addAll(TypeUtils.resolveTypesInSignature(type));
additionalTypes.addAll(visitConstructorsOfType(type));
additionalTypes.addAll(visitMethodsOfType(type));
additionalTypes.addAll(visitFieldsOfType(type));
if (!ObjectUtils.isEmpty(type.toClass().getDeclaredClasses())) {
additionalTypes.addAll(Arrays.asList(type.toClass().getDeclaredClasses()));
}
for (Type discoveredType : additionalTypes) {
processType(ResolvableType.forType(discoveredType, type), cache, callback);
}
}
Set<Type> visitConstructorsOfType(ResolvableType type) {
if (!typeFilter.test(type.toClass())) {
return Collections.emptySet();
}
Set<Type> discoveredTypes = new LinkedHashSet<>();
for (Constructor<?> constructor : type.toClass().getDeclaredConstructors()) {
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(type.toClass(), constructor)) {
if (typeFilter.test(signatureType)) {
discoveredTypes.add(signatureType);
}
}
}
return new HashSet<>(discoveredTypes);
}
Set<Type> visitMethodsOfType(ResolvableType type) {
if (!typeFilter.test(type.toClass())) {
return Collections.emptySet();
}
Set<Type> discoveredTypes = new LinkedHashSet<>();
try {
ReflectionUtils.doWithLocalMethods(type.toClass(), method -> {
if (!methodFilter.test(method)) {
return;
}
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(type.toClass(), method)) {
if (typeFilter.test(signatureType)) {
discoveredTypes.add(signatureType);
}
}
});
} catch (Exception cause) {
logger.warn(cause);
}
return new HashSet<>(discoveredTypes);
}
Set<Type> visitFieldsOfType(ResolvableType type) {
Set<Type> discoveredTypes = new LinkedHashSet<>();
ReflectionUtils.doWithLocalFields(type.toClass(), field -> {
if (!fieldFilter.test(field)) {
return;
}
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(ResolvableType.forField(field, type))) {
if (typeFilter.test(signatureType)) {
discoveredTypes.add(signatureType);
}
}
});
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();
}
public static class ReachableTypes {
private final Iterable<Class<?>> roots;
private final Lazy<List<Class<?>>> reachableTypes = Lazy.of(this::collect);
private final TypeCollector typeCollector;
public ReachableTypes(TypeCollector typeCollector, Iterable<Class<?>> roots) {
this.typeCollector = typeCollector;
this.roots = roots;
}
public void forEach(Consumer<ResolvableType> consumer) {
roots.forEach(it -> typeCollector.process(it, consumer));
}
public List<Class<?>> list() {
return reachableTypes.get();
}
private List<Class<?>> collect() {
List<Class<?>> target = new ArrayList<>();
forEach(it -> target.add(it.toClass()));
return target;
}
}
static class InspectionCache {
private final Map<String, ResolvableType> mutableCache = new LinkedHashMap<>();
public void add(ResolvableType resolvableType) {
mutableCache.put(resolvableType.toString(), resolvableType);
}
public void clear() {
mutableCache.clear();
}
public boolean contains(ResolvableType key) {
return mutableCache.containsKey(key.toString());
}
public boolean isEmpty() {
return mutableCache.isEmpty();
}
public int size() {
return mutableCache.size();
}
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.aot;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.Set;
import java.util.function.Predicate;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.core.annotation.MergedAnnotation;
/**
* @author Christoph Strobl
* @since 3.0
*/
public class TypeContributor {
public static final String DATA_NAMESPACE = "org.springframework.data";
public static final BindingReflectionHintsRegistrar REGISTRAR = new BindingReflectionHintsRegistrar();
/**
* Contribute the type with default reflection configuration, skip annotations.
*
* @param type
* @param contribution
*/
public static void contribute(Class<?> type, GenerationContext contribution) {
contribute(type, Collections.emptySet(), contribution);
}
/**
* Contribute the type with default reflection configuration and only include matching annotations.
*
* @param type
* @param filter
* @param contribution
*/
@SuppressWarnings("unchecked")
public static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> filter,
GenerationContext contribution) {
if (type.isPrimitive()) {
return;
}
if (type.isAnnotation() && filter.test((Class<? extends Annotation>) type)) {
contribution.getRuntimeHints().reflection().registerType(type,
hint -> hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
return;
}
REGISTRAR.registerReflectionHints(contribution.getRuntimeHints().reflection(), type);
}
/**
* Contribute the type with default reflection configuration and only include annotations from a certain namespace and
* those meta annotated with one of them.
*
* @param type
* @param annotationNamespaces
* @param contribution
*/
public static void contribute(Class<?> type, Set<String> annotationNamespaces, GenerationContext contribution) {
contribute(type, it -> isPartOfOrMetaAnnotatedWith(it, annotationNamespaces), contribution);
}
public static boolean isPartOf(Class<?> type, Set<String> namespaces) {
return namespaces.stream().anyMatch(namespace -> type.getPackageName().startsWith(namespace));
}
public static boolean isPartOfOrMetaAnnotatedWith(Class<? extends Annotation> annotation, Set<String> namespaces) {
if (isPartOf(annotation, namespaces)) {
return true;
}
return MergedAnnotation.of(annotation).getMetaTypes().stream().anyMatch(it -> isPartOf(annotation, namespaces));
}
}

View File

@@ -1,258 +0,0 @@
/*
* Copyright 2019-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.aot;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Stream;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationFilter;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
// TODO: Consider moving to the org.springframework.data.util package or make this package-private if not used somewhere
// else.
public class TypeUtils {
public static String TRANSACTION_MANAGER_CLASS_NAME = "org.springframework.transaction.TransactionManager";
/**
* Resolve ALL annotations present for a given type. Will inspect type, constructors, parameters, methods, fields,...
*
* @param type
* @return never {@literal null}.
*/
public static Set<MergedAnnotation<Annotation>> resolveUsedAnnotations(Class<?> type) {
Set<MergedAnnotation<Annotation>> annotations = new LinkedHashSet<>();
annotations.addAll(TypeUtils.resolveAnnotationsFor(type).toList());
for (Constructor<?> ctor : type.getDeclaredConstructors()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(ctor).toList());
for (Parameter parameter : ctor.getParameters()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(parameter).toList());
}
}
for (Field field : type.getDeclaredFields()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(field).toList());
}
try {
for (Method method : type.getDeclaredMethods()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(method).toList());
for (Parameter parameter : method.getParameters()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(parameter).toList());
}
}
} catch (NoClassDefFoundError e) {
// ignore and move on
}
return annotations;
}
public static Stream<MergedAnnotation<Annotation>> resolveAnnotationsFor(AnnotatedElement element) {
return resolveAnnotationsFor(element, AnnotationFilter.PLAIN);
}
public static Stream<MergedAnnotation<Annotation>> resolveAnnotationsFor(AnnotatedElement element,
AnnotationFilter filter) {
return MergedAnnotations
.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables(), filter).stream();
}
public static Collection<Class<Annotation>> resolveAnnotationTypesFor(AnnotatedElement element,
AnnotationFilter filter) {
return MergedAnnotations
.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables(), filter).stream()
.map(MergedAnnotation::getType).toList();
}
public static Collection<Class<Annotation>> resolveAnnotationTypesFor(AnnotatedElement element) {
return resolveAnnotationTypesFor(element, AnnotationFilter.PLAIN);
}
public static boolean isAnnotationFromOrMetaAnnotated(Class<? extends Annotation> annotation, String prefix) {
if (annotation.getPackage().getName().startsWith(prefix)) {
return true;
}
return TypeUtils.resolveAnnotationsFor(annotation)
.anyMatch(it -> it.getType().getPackage().getName().startsWith(prefix));
}
public static boolean hasAnnotatedField(Class<?> type, String annotationName) {
for (Field field : type.getDeclaredFields()) {
MergedAnnotations fieldAnnotations = MergedAnnotations.from(field);
boolean hasAnnotation = fieldAnnotations.get(annotationName).isPresent();
if (hasAnnotation) {
return true;
}
}
return false;
}
public static Set<Field> getAnnotatedField(Class<?> type, String annotationName) {
Set<Field> fields = new LinkedHashSet<>();
for (Field field : type.getDeclaredFields()) {
if (MergedAnnotations.from(field).get(annotationName).isPresent()) {
fields.add(field);
}
}
return fields;
}
public static Set<Class<?>> resolveTypesInSignature(Class<?> owner, Method method) {
Set<Class<?>> signature = new LinkedHashSet<>();
signature.addAll(resolveTypesInSignature(ResolvableType.forMethodReturnType(method, owner)));
for (Parameter parameter : method.getParameters()) {
signature
.addAll(resolveTypesInSignature(ResolvableType.forMethodParameter(MethodParameter.forParameter(parameter))));
}
return signature;
}
public static Set<Class<?>> resolveTypesInSignature(Class<?> owner, Constructor<?> constructor) {
Set<Class<?>> signature = new LinkedHashSet<>();
for (int i = 0; i < constructor.getParameterCount(); i++) {
signature.addAll(resolveTypesInSignature(ResolvableType.forConstructorParameter(constructor, i, owner)));
}
return signature;
}
public static Set<Class<?>> resolveTypesInSignature(Class<?> root) {
Set<Class<?>> signature = new LinkedHashSet<>();
resolveTypesInSignature(ResolvableType.forClass(root), signature);
return signature;
}
public static Set<Class<?>> resolveTypesInSignature(ResolvableType root) {
Set<Class<?>> signature = new LinkedHashSet<>();
resolveTypesInSignature(root, signature);
return signature;
}
private static void resolveTypesInSignature(ResolvableType current, Set<Class<?>> signatures) {
if (ResolvableType.NONE.equals(current) || ObjectUtils.nullSafeEquals(Void.TYPE, current.getType())
|| ObjectUtils.nullSafeEquals(Object.class, current.getType())) {
return;
}
if (signatures.contains(current.toClass())) {
return;
}
signatures.add(current.toClass());
resolveTypesInSignature(current.getSuperType(), signatures);
for (ResolvableType type : current.getGenerics()) {
resolveTypesInSignature(type, signatures);
}
for (ResolvableType type : current.getInterfaces()) {
resolveTypesInSignature(type, signatures);
}
}
public static TypeOps type(Class<?> type) {
return new TypeOpsImpl(type);
}
public interface TypeOps {
Class<?> getType();
default boolean isPartOf(String... packageNames) {
return isPartOf(TypeUtils.PackageFilter.of(packageNames));
}
default boolean isPartOf(PackageFilter... packageFilters) {
for (PackageFilter filter : packageFilters) {
if (filter.matches(getType().getName())) {
return true;
}
}
return false;
}
default Set<Class<?>> signatureTypes() {
return TypeUtils.resolveTypesInSignature(getType());
}
interface PackageFilter {
default boolean matches(Class<?> type) {
return matches(type.getName());
}
boolean matches(String typeName);
static PackageFilter of(String... packages) {
return TypeUtils.PackageFilter.of(packages);
}
}
}
private static class TypeOpsImpl implements TypeOps {
private final Class<?> type;
TypeOpsImpl(Class<?> type) {
this.type = type;
}
public Class<?> getType() {
return type;
}
}
private static class PackageFilter implements TypeOps.PackageFilter {
Set<String> packageNames;
PackageFilter(Set<String> packageNames) {
this.packageNames = packageNames;
}
static PackageFilter of(String... packageNames) {
Set<String> target = new LinkedHashSet<>();
for (String pkgName : packageNames) {
target.add(pkgName.endsWith(".") ? pkgName : (pkgName + '.'));
}
return new PackageFilter(target);
}
@Override
public boolean matches(String typeName) {
for (String pgkName : packageNames) {
if (typeName.startsWith(pgkName)) {
return true;
}
}
return false;
}
}
}