Revisit internal caching arrangements.
Closes: #3185 Original Pull Request: #3186
This commit is contained in:
committed by
Christoph Strobl
parent
e0e881f6b8
commit
adc8e3ffae
@@ -16,23 +16,14 @@
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -119,7 +110,7 @@ public class CustomConversions {
|
||||
private final Function<ConvertiblePair, Class<?>> getRawWriteTarget = convertiblePair -> getCustomTarget(
|
||||
convertiblePair.getSourceType(), null, writingPairs);
|
||||
|
||||
@Nullable private final PropertyValueConversions propertyValueConversions;
|
||||
private final @Nullable PropertyValueConversions propertyValueConversions;
|
||||
|
||||
/**
|
||||
* @param converterConfiguration the {@link ConverterConfiguration} to apply.
|
||||
@@ -506,7 +497,7 @@ public class CustomConversions {
|
||||
*/
|
||||
static class ConversionTargetsCache {
|
||||
|
||||
private final Map<Class<?>, TargetTypes> customReadTargetTypes = new ConcurrentHashMap<>();
|
||||
private Map<Class<?>, TargetTypes> customReadTargetTypes = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Get or compute a target type given its {@code sourceType}. Returns a cached {@link Optional} if the value
|
||||
@@ -536,7 +527,15 @@ public class CustomConversions {
|
||||
public Class<?> computeIfAbsent(Class<?> sourceType, Class<?> targetType,
|
||||
Function<ConvertiblePair, Class<?>> mappingFunction) {
|
||||
|
||||
TargetTypes targetTypes = customReadTargetTypes.computeIfAbsent(sourceType, TargetTypes::new);
|
||||
TargetTypes targetTypes = customReadTargetTypes.get(sourceType);
|
||||
|
||||
if (targetTypes == null) {
|
||||
|
||||
Map<Class<?>, TargetTypes> customReadTargetTypes = new HashMap<>(this.customReadTargetTypes);
|
||||
targetTypes = new TargetTypes(sourceType);
|
||||
customReadTargetTypes.put(sourceType, targetTypes);
|
||||
this.customReadTargetTypes = customReadTargetTypes;
|
||||
}
|
||||
|
||||
return targetTypes.computeIfAbsent(targetType, mappingFunction);
|
||||
}
|
||||
@@ -555,7 +554,7 @@ public class CustomConversions {
|
||||
static class TargetTypes {
|
||||
|
||||
private final Class<?> sourceType;
|
||||
private final Map<Class<?>, Class<?>> conversionTargets = new ConcurrentHashMap<>();
|
||||
private Map<Class<?>, Class<?>> conversionTargets = new HashMap<>();
|
||||
|
||||
TargetTypes(Class<?> sourceType) {
|
||||
this.sourceType = sourceType;
|
||||
@@ -576,8 +575,12 @@ public class CustomConversions {
|
||||
Class<?> optionalTarget = conversionTargets.get(targetType);
|
||||
|
||||
if (optionalTarget == null) {
|
||||
|
||||
optionalTarget = mappingFunction.apply(new ConvertiblePair(sourceType, targetType));
|
||||
|
||||
Map<Class<?>, Class<?>> conversionTargets = new HashMap<>(this.conversionTargets);
|
||||
conversionTargets.put(targetType, optionalTarget == null ? Void.class : optionalTarget);
|
||||
this.conversionTargets = conversionTargets;
|
||||
}
|
||||
|
||||
return Void.class.equals(optionalTarget) ? null : optionalTarget;
|
||||
@@ -585,8 +588,8 @@ public class CustomConversions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Value class tying together a {@link ConverterRegistration} and its {@link ConverterOrigin origin} to allow fine
|
||||
* grained registration based on store supported types.
|
||||
* Value class tying together a {@link ConverterRegistration} and its {@link ConverterOrigin origin} to allow
|
||||
* fine-grained registration based on store supported types.
|
||||
*
|
||||
* @since 2.3
|
||||
* @author Christoph Strobl
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -51,6 +53,23 @@ public class SimplePropertyValueConversions implements PropertyValueConversions,
|
||||
|
||||
private @Nullable ValueConverterRegistry<?> valueConverterRegistry;
|
||||
|
||||
private Map<PersistentProperty<?>, PropertyValueConverter<?, ?, ?>> converterCache = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
enum NoOpConverter implements PropertyValueConverter {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Object read(Object value, ValueConversionContext context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object write(Object value, ValueConversionContext context) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link PropertyValueConverterFactory} responsible for creating the actual {@link PropertyValueConverter}.
|
||||
*
|
||||
@@ -129,20 +148,49 @@ public class SimplePropertyValueConversions implements PropertyValueConversions,
|
||||
*/
|
||||
@Override
|
||||
public boolean hasValueConverter(PersistentProperty<?> property) {
|
||||
return requireConverterFactory().getConverter(property) != null;
|
||||
return doGetConverter(property) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <DV, SV, P extends PersistentProperty<P>, D extends ValueConversionContext<P>> PropertyValueConverter<DV, SV, D> getValueConverter(
|
||||
P property) {
|
||||
|
||||
PropertyValueConverter<DV, SV, D> converter = requireConverterFactory().getConverter(property);
|
||||
PropertyValueConverter<DV, SV, D> converter = doGetConverter(property);
|
||||
|
||||
Assert.notNull(converter, String.format("No PropertyValueConverter registered for %s", property));
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <DV, SV, P extends PersistentProperty<P>, D extends ValueConversionContext<P>> PropertyValueConverter<DV, SV, D> doGetConverter(
|
||||
PersistentProperty<?> property) {
|
||||
|
||||
PropertyValueConverter<?, ?, ?> converter = converterCache.get(property);
|
||||
|
||||
if (converter == null) {
|
||||
|
||||
converter = requireConverterFactory().getConverter(property);
|
||||
|
||||
Map<PersistentProperty<?>, PropertyValueConverter<?, ?, ?>> converterCache = new HashMap<>(this.converterCache);
|
||||
|
||||
if (converter == null) {
|
||||
converterCache.put(property, NoOpConverter.INSTANCE);
|
||||
} else {
|
||||
converterCache.put(property, converter);
|
||||
}
|
||||
|
||||
this.converterCache = converterCache;
|
||||
}
|
||||
|
||||
if (converter == NoOpConverter.INSTANCE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (PropertyValueConverter<DV, SV, D>) converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* May be called just once to initialize the underlying factory with its values.
|
||||
*/
|
||||
|
||||
@@ -17,9 +17,9 @@ package org.springframework.data.mapping;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -35,7 +35,7 @@ class InstanceCreatorMetadataSupport<T, P extends PersistentProperty<P>> impleme
|
||||
|
||||
private final Executable executable;
|
||||
private final List<Parameter<Object, P>> parameters;
|
||||
private final Map<PersistentProperty<?>, Boolean> isPropertyParameterCache = new ConcurrentHashMap<>();
|
||||
private Map<PersistentProperty<?>, Boolean> isPropertyParameterCache = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link InstanceCreatorMetadataSupport} from the given {@link Executable} and {@link Parameter}s.
|
||||
@@ -96,7 +96,9 @@ class InstanceCreatorMetadataSupport<T, P extends PersistentProperty<P>> impleme
|
||||
|
||||
boolean result = doGetIsCreatorParameter(property);
|
||||
|
||||
Map<PersistentProperty<?>, Boolean> isPropertyParameterCache = new HashMap<>(this.isPropertyParameterCache);
|
||||
isPropertyParameterCache.put(property, result);
|
||||
this.isPropertyParameterCache = isPropertyParameterCache;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -31,15 +31,19 @@ import org.springframework.util.ConcurrentLruCache;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @deprecated since 3.0 to go package protected at some point. Refer to {@link TypeInformation} only.
|
||||
*/
|
||||
@Deprecated(since = "3.0", forRemoval = true)
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
|
||||
|
||||
private static final ConcurrentLruCache<ResolvableType, ClassTypeInformation<?>> cache = new ConcurrentLruCache<>(64,
|
||||
private static final ConcurrentLruCache<ResolvableType, ClassTypeInformation<?>> cache = new ConcurrentLruCache<>(128,
|
||||
ClassTypeInformation::new);
|
||||
|
||||
private static final ConcurrentLruCache<Class<?>, ResolvableType> resolvableTypeCache = new ConcurrentLruCache<>(128,
|
||||
ResolvableType::forClass);
|
||||
|
||||
public static final ClassTypeInformation<Collection> COLLECTION;
|
||||
public static final ClassTypeInformation<List> LIST;
|
||||
public static final ClassTypeInformation<Set> SET;
|
||||
@@ -48,11 +52,11 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
|
||||
|
||||
static {
|
||||
|
||||
OBJECT = (ClassTypeInformation<Object>) cache.get(ResolvableType.forClass(Object.class));
|
||||
COLLECTION = (ClassTypeInformation<Collection>) cache.get(ResolvableType.forClass(Collection.class));
|
||||
LIST = (ClassTypeInformation<List>) cache.get(ResolvableType.forClass(List.class));
|
||||
SET = (ClassTypeInformation<Set>) cache.get(ResolvableType.forClass(Set.class));
|
||||
MAP = (ClassTypeInformation<Map>) cache.get(ResolvableType.forClass(Map.class));
|
||||
OBJECT = from(Object.class);
|
||||
COLLECTION = from(Collection.class);
|
||||
LIST = from(List.class);
|
||||
SET = from(Set.class);
|
||||
MAP = from(Map.class);
|
||||
}
|
||||
|
||||
private final Class<S> type;
|
||||
@@ -70,7 +74,7 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
|
||||
*/
|
||||
@Deprecated
|
||||
public static <S> ClassTypeInformation<S> from(Class<S> type) {
|
||||
return from(ResolvableType.forClass(type));
|
||||
return from(resolvableTypeCache.get(type));
|
||||
}
|
||||
|
||||
static <S> ClassTypeInformation<S> from(ResolvableType type) {
|
||||
|
||||
@@ -55,6 +55,16 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
|
||||
private final ResolvableType resolvableType;
|
||||
private final Map<String, Optional<TypeInformation<?>>> fields = new ConcurrentHashMap<>();
|
||||
private final Lazy<Boolean> isMap = Lazy.of(() -> CustomCollections.isMap(getType()));
|
||||
private final Lazy<Boolean> isCollectionLike = Lazy.of(() -> {
|
||||
|
||||
Class<S> type = getType();
|
||||
|
||||
return type.isArray() //
|
||||
|| Iterable.class.equals(type) //
|
||||
|| Collection.class.isAssignableFrom(type) //
|
||||
|| Streamable.class.isAssignableFrom(type) || CustomCollections.isCollection(type);
|
||||
});
|
||||
private final Lazy<TypeInformation<?>> componentType;
|
||||
private final Lazy<TypeInformation<?>> valueType;
|
||||
private final Map<Constructor<?>, List<TypeInformation<?>>> constructorParameters = new ConcurrentHashMap<>();
|
||||
@@ -125,10 +135,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
|
||||
Class<S> type = getType();
|
||||
|
||||
return type.isArray() //
|
||||
|| Iterable.class.equals(type) //
|
||||
|| Collection.class.isAssignableFrom(type) //
|
||||
|| Streamable.class.isAssignableFrom(type) || CustomCollections.isCollection(type);
|
||||
return isCollectionLike.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -169,7 +176,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
|
||||
@Override
|
||||
public boolean isMap() {
|
||||
return CustomCollections.isMap(getType());
|
||||
return isMap.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
Reference in New Issue
Block a user