Polishing for full type information on RepositoryMetadata.

Avoid leaking internals from TypeInformation by exposing a ….toTypeDescriptor() method in TypeInformation directly. This causes the ResolvableType handling to become an implementation detail within ClassTypeInformation and avoid client code to manually deal with both ResolvableType and TypeDescriptor creation.

Changed MethodLookups back to use the resolved domain types as the advanced generics resolutions seems not to be needed here and we can avoid

Fixes #2518.
This commit is contained in:
Oliver Drotbohm
2022-03-29 18:09:43 +02:00
parent 58ed9ce211
commit 3e9451c939
12 changed files with 117 additions and 109 deletions

View File

@@ -31,27 +31,6 @@ import org.springframework.data.util.TypeInformation;
*/
public interface RepositoryMetadata {
/**
* Returns the id {@link TypeInformation} the given class is declared for.
*
* @return the {@link TypeInformation} class of the entity managed by the repository.
*/
TypeInformation<?> getIdTypeInformation();
/**
* Returns the domain {@link TypeInformation} the repository is declared for.
*
* @return the domain class the repository is handling.
*/
TypeInformation<?> getDomainTypeInformation();
/**
* Returns the repository interface.
*
* @return
*/
Class<?> getRepositoryInterface();
/**
* Returns the raw id class the given class is declared for.
*
@@ -60,7 +39,7 @@ public interface RepositoryMetadata {
default Class<?> getIdType() {
return getIdTypeInformation().getType();
}
/**
* Returns the raw domain class the repository is declared for.
*
@@ -70,6 +49,31 @@ public interface RepositoryMetadata {
return getDomainTypeInformation().getType();
}
/**
* Returns the {@link TypeInformation} of the id type of the repository.
*
* @return the {@link TypeInformation} class of the identifier of the entity managed by the repository. Will never be
* {@literal null}.
* @since 2.7
*/
TypeInformation<?> getIdTypeInformation();
/**
* Returns the {@link TypeInformation}of the domain type the repository is declared to manage. Will never be
* {@literal null}.
*
* @return the domain class the repository is handling.
* @since 2.7
*/
TypeInformation<?> getDomainTypeInformation();
/**
* Returns the repository interface.
*
* @return
*/
Class<?> getRepositoryInterface();
/**
* Returns the type {@link Method} return type as it is declared in the repository. Considers suspended methods and
* does not unwrap component types but leaves those for further inspection.

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.repository.core.support;
import java.util.function.Function;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.repository.RepositoryDefinition;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -52,8 +54,8 @@ public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata {
Assert.isTrue(AnnotationUtils.findAnnotation(repositoryInterface, RepositoryDefinition.class) != null,
() -> String.format(NO_ANNOTATION_FOUND, repositoryInterface.getName()));
this.idType = resolveIdType(repositoryInterface);
this.domainType = resolveDomainType(repositoryInterface);
this.idType = resolveType(repositoryInterface, RepositoryDefinition::idClass);
this.domainType = resolveType(repositoryInterface, RepositoryDefinition::domainClass);
}
@Override
@@ -66,25 +68,15 @@ public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata {
return this.domainType;
}
private TypeInformation<?> resolveIdType(Class<?> repositoryInterface) {
private static TypeInformation<?> resolveType(Class<?> repositoryInterface,
Function<RepositoryDefinition, Class<?>> extractor) {
RepositoryDefinition annotation = AnnotationUtils.findAnnotation(repositoryInterface, RepositoryDefinition.class);
if (annotation == null || annotation.idClass() == null) {
throw new IllegalArgumentException(String.format("Could not resolve id type of %s!", repositoryInterface));
}
return ClassTypeInformation.from(annotation.idClass());
}
private TypeInformation<?> resolveDomainType(Class<?> repositoryInterface) {
RepositoryDefinition annotation = AnnotationUtils.findAnnotation(repositoryInterface, RepositoryDefinition.class);
if (annotation == null || annotation.domainClass() == null) {
if ((annotation == null) || (extractor.apply(annotation) == null)) {
throw new IllegalArgumentException(String.format("Could not resolve domain type of %s!", repositoryInterface));
}
return ClassTypeInformation.from(annotation.domainClass());
return ClassTypeInformation.from(extractor.apply(annotation));
}
}

View File

@@ -56,26 +56,28 @@ public class DefaultRepositoryMetadata extends AbstractRepositoryMetadata {
this.domainType = resolveTypeParameter(arguments, 0,
() -> String.format("Could not resolve domain type of %s!", repositoryInterface));
this.idType = resolveTypeParameter(arguments, 1,
() -> String.format("Could not resolve id type of %s!", repositoryInterface));
}
@Override
public TypeInformation<?> getIdTypeInformation() {
return this.idType;
}
@Override
public TypeInformation<?> getDomainTypeInformation() {
return this.domainType;
}
private static TypeInformation<?> resolveTypeParameter(List<TypeInformation<?>> arguments, int index,
Supplier<String> exceptionMessage) {
if (arguments.size() <= index || arguments.get(index) == null) {
if ((arguments.size() <= index) || (arguments.get(index) == null)) {
throw new IllegalArgumentException(exceptionMessage.get());
}
return arguments.get(index).getGenericTypeInformation();
}
public TypeInformation<?> getIdTypeInformation() {
return this.idType;
}
public TypeInformation<?> getDomainTypeInformation() {
return this.domainType;
return arguments.get(index);
}
}

View File

@@ -121,8 +121,8 @@ interface MethodLookups {
Assert.notNull(repositoryMetadata, "Repository metadata must not be null!");
this.entityType = ResolvableType.forType(repositoryMetadata.getDomainTypeInformation().getGenericType());
this.idType = ResolvableType.forType(repositoryMetadata.getIdTypeInformation().getGenericType());
this.entityType = repositoryMetadata.getDomainTypeInformation().toTypeDescriptor().getResolvableType();
this.idType = repositoryMetadata.getIdTypeInformation().toTypeDescriptor().getResolvableType();
this.repositoryInterface = repositoryMetadata.getRepositoryInterface();
}

View File

@@ -21,7 +21,6 @@ import java.util.Set;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
@@ -30,7 +29,6 @@ import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -104,12 +102,6 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
return repositories;
});
}
private static TypeDescriptor getIdTypeDescriptor(RepositoryInformation information) {
TypeInformation<?> idType = information.getIdTypeInformation();
return new TypeDescriptor(ResolvableType.forType(idType.getGenericType()), null, idType.getType().getAnnotations());
}
/**
* Converter to create domain types from any source that can be converted into the domain types identifier type.
@@ -157,7 +149,7 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
Class<?> domainType = targetType.getType();
RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType);
RepositoryInformation information = repositories.getRequiredRepositoryInformation(domainType);
TypeDescriptor idTypeDescriptor = getIdTypeDescriptor(information);
TypeDescriptor idTypeDescriptor = information.getIdTypeInformation().toTypeDescriptor();
Object id = conversionService.convert(source, sourceType, idTypeDescriptor);
@@ -181,7 +173,7 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
return repositoryInformation.map(it -> {
TypeDescriptor idTypeDescriptor = getIdTypeDescriptor(it);
TypeDescriptor idTypeDescriptor = it.getIdTypeInformation().toTypeDescriptor();
return sourceType.equals(idTypeDescriptor)
|| conversionService.canConvert(sourceType, idTypeDescriptor);
@@ -249,7 +241,7 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
return information.map(it -> {
TypeDescriptor idTypeDescriptor = getIdTypeDescriptor(it);
TypeDescriptor idTypeDescriptor = it.getIdTypeInformation().toTypeDescriptor();
return targetType.equals(idTypeDescriptor)
|| conversionService.canConvert(idTypeDescriptor, targetType);

View File

@@ -21,7 +21,6 @@ import java.util.List;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
@@ -74,7 +73,7 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
this.repository = repository;
this.methods = metadata.getCrudMethods();
TypeInformation<?> idType = metadata.getIdTypeInformation();
this.idTypeDescriptor = new TypeDescriptor(ResolvableType.forType(idType.getGenericType()), null, idType.getType().getAnnotations());
this.idTypeDescriptor = idType.toTypeDescriptor();
this.conversionService = conversionService;
}
@@ -249,7 +248,7 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
protected Object convertId(Object id) {
Assert.notNull(id, "Id must not be null!");
TypeDescriptor idDescriptor = TypeDescriptor.forObject(id);
TypeDescriptor idDescriptor = TypeDescriptor.forObject(id);
if (idDescriptor.isAssignableTo(idTypeDescriptor)) {
return id;
@@ -259,7 +258,8 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
if (result == null) {
throw new IllegalStateException(
String.format("Identifier conversion of %s to %s unexpectedly returned null!", id, idTypeDescriptor.getType()));
String.format("Identifier conversion of %s to %s unexpectedly returned null!", id,
idTypeDescriptor.getType()));
}
return result;

View File

@@ -28,6 +28,7 @@ import java.util.Map;
import java.util.Set;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
@@ -55,6 +56,7 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
}
private final Class<S> type;
private final Lazy<TypeDescriptor> descriptor;
/**
* Simple factory method to easily create new instances of {@link ClassTypeInformation}.
@@ -89,8 +91,11 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
* @param type
*/
ClassTypeInformation(Class<S> type) {
super(type, getTypeVariableMap(type));
this.type = type;
this.descriptor = Lazy.of(() -> TypeDescriptor.valueOf(type));
}
/**
@@ -152,6 +157,11 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
return (TypeInformation<? extends S>) type;
}
@Override
public TypeDescriptor toTypeDescriptor() {
return descriptor.get();
}
@Override
public String toString() {
return type.getName();

View File

@@ -19,6 +19,8 @@ import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
/**
@@ -29,6 +31,7 @@ import org.springframework.lang.Nullable;
public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S> {
private final TypeDiscoverer<?> parent;
private final Lazy<TypeDescriptor> descriptor;
private int hashCode;
/**
@@ -44,7 +47,14 @@ public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
super(type, map);
this.parent = parent;
this.descriptor = Lazy.of(() -> new TypeDescriptor(toResolvableType(), null, null));
}
@Override
public TypeDescriptor toTypeDescriptor() {
return descriptor.get();
}
@Override
@@ -57,6 +67,11 @@ public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S
return super.createInfo(fieldType);
}
@Override
protected ResolvableType toResolvableType() {
return ResolvableType.forType(getType(), parent.toResolvableType());
}
@Override
public boolean equals(@Nullable Object obj) {
@@ -80,7 +95,7 @@ public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S
public int hashCode() {
if (this.hashCode == 0) {
this.hashCode = super.hashCode() + 31 * parent.hashCode();
this.hashCode = super.hashCode() + (31 * parent.hashCode());
}
return this.hashCode;

View File

@@ -25,21 +25,14 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.springframework.beans.BeanUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -112,7 +105,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
this.componentType = Lazy.of(this::doGetComponentType);
this.valueType = Lazy.of(this::doGetMapValueType);
this.typeVariableMap = typeVariableMap;
this.hashCode = 17 + 31 * type.hashCode() + 31 * typeVariableMap.hashCode();
this.hashCode = 17 + (31 * type.hashCode()) + (31 * typeVariableMap.hashCode());
}
/**
@@ -291,18 +284,14 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
return parameterTypes.length == 0 ? null : parameterTypes[0];
}
@Override
public Class<S> getType() {
return resolvedType.get();
}
@Override
public Type getGenericType() {
return type;
}
@Override
public TypeInformation<?> getGenericTypeInformation() {
return createInfo(type);
public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(toResolvableType(), getType(), null);
}
@Override
@@ -499,6 +488,10 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
return getSuperTypeWithin(MAP_TYPES);
}
protected ResolvableType toResolvableType() {
return ResolvableType.forType(type);
}
@Override
public boolean equals(@Nullable Object obj) {
@@ -656,7 +649,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(typeInformation);
result = 31 * result + ObjectUtils.nullSafeHashCode(typeParameters);
result = (31 * result) + ObjectUtils.nullSafeHashCode(typeParameters);
return result;
}
}

View File

@@ -17,9 +17,9 @@ package org.springframework.data.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
/**
@@ -150,15 +150,6 @@ public interface TypeInformation<S> {
* @return
*/
Class<S> getType();
/**
* Returns the type of the property with all resolvable generics applied
*
* @return
*/
default Type getGenericType() {
return getType();
}
/**
* Returns the user type of the property if proxied.
@@ -181,13 +172,6 @@ public interface TypeInformation<S> {
*/
ClassTypeInformation<?> getRawTypeInformation();
/**
* Returns a {@link TypeInformation} to represent the {@link TypeInformation} of the type of the current instance with all the generics parameters resolved.
*
* @return
*/
TypeInformation<?> getGenericTypeInformation();
/**
* Transparently returns the {@link java.util.Map} value type if the type is a {@link java.util.Map}, returns the
* component type if the type {@link #isCollectionLike()} or the simple type if none of this applies.
@@ -305,4 +289,11 @@ public interface TypeInformation<S> {
return !type.equals(getType()) && type.isAssignableFrom(getType());
}
/**
* Returns the {@link TypeDescriptor} equivalent of this {@link TypeInformation}.
*
* @return will never be {@literal null}.
* @since 2.7
*/
TypeDescriptor toTypeDescriptor();
}

View File

@@ -19,6 +19,7 @@ import static org.springframework.util.ObjectUtils.*;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -33,6 +34,7 @@ import org.springframework.util.Assert;
class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
private final TypeVariable<?> variable;
private final Lazy<List<TypeInformation<?>>> parameters;
/**
* Creates a new {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and parent
@@ -49,11 +51,18 @@ class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
Assert.notNull(variable, "TypeVariable must not be null!");
this.variable = variable;
this.parameters = Lazy.of(() -> {
return createInfo(getTypeVariableMap().getOrDefault(variable, Object.class)).getTypeArguments();
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getTypeArguments()
*/
@Override
public TypeInformation<?> getGenericTypeInformation() {
return createInfo(getTypeVariableMap().getOrDefault(variable, Object.class));
public List<TypeInformation<?>> getTypeArguments() {
return parameters.get();
}
@Override