diff --git a/src/main/java/org/springframework/data/convert/CustomConversions.java b/src/main/java/org/springframework/data/convert/CustomConversions.java index 45d70fe81..a6eba7ea8 100644 --- a/src/main/java/org/springframework/data/convert/CustomConversions.java +++ b/src/main/java/org/springframework/data/convert/CustomConversions.java @@ -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> 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, TargetTypes> customReadTargetTypes = new ConcurrentHashMap<>(); + private Map, 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> mappingFunction) { - TargetTypes targetTypes = customReadTargetTypes.computeIfAbsent(sourceType, TargetTypes::new); + TargetTypes targetTypes = customReadTargetTypes.get(sourceType); + + if (targetTypes == null) { + + Map, 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> conversionTargets = new ConcurrentHashMap<>(); + private Map, 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> 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 diff --git a/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java b/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java index c4942cf2d..f3b99c663 100644 --- a/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java +++ b/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java @@ -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, 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 , D extends ValueConversionContext

> PropertyValueConverter getValueConverter( P property) { - PropertyValueConverter converter = requireConverterFactory().getConverter(property); + PropertyValueConverter converter = doGetConverter(property); Assert.notNull(converter, String.format("No PropertyValueConverter registered for %s", property)); return converter; } + @SuppressWarnings("unchecked") + @Nullable + private , D extends ValueConversionContext

> PropertyValueConverter doGetConverter( + PersistentProperty property) { + + PropertyValueConverter converter = converterCache.get(property); + + if (converter == null) { + + converter = requireConverterFactory().getConverter(property); + + Map, 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) converter; + } + /** * May be called just once to initialize the underlying factory with its values. */ diff --git a/src/main/java/org/springframework/data/mapping/InstanceCreatorMetadataSupport.java b/src/main/java/org/springframework/data/mapping/InstanceCreatorMetadataSupport.java index 59b1823ef..fcdf28056 100644 --- a/src/main/java/org/springframework/data/mapping/InstanceCreatorMetadataSupport.java +++ b/src/main/java/org/springframework/data/mapping/InstanceCreatorMetadataSupport.java @@ -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> impleme private final Executable executable; private final List> parameters; - private final Map, Boolean> isPropertyParameterCache = new ConcurrentHashMap<>(); + private Map, Boolean> isPropertyParameterCache = new HashMap<>(); /** * Creates a new {@link InstanceCreatorMetadataSupport} from the given {@link Executable} and {@link Parameter}s. @@ -96,7 +96,9 @@ class InstanceCreatorMetadataSupport> impleme boolean result = doGetIsCreatorParameter(property); + Map, Boolean> isPropertyParameterCache = new HashMap<>(this.isPropertyParameterCache); isPropertyParameterCache.put(property, result); + this.isPropertyParameterCache = isPropertyParameterCache; return result; } diff --git a/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/src/main/java/org/springframework/data/util/ClassTypeInformation.java index 554c41f43..35df8f48b 100644 --- a/src/main/java/org/springframework/data/util/ClassTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ClassTypeInformation.java @@ -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 extends TypeDiscoverer { - private static final ConcurrentLruCache> cache = new ConcurrentLruCache<>(64, + private static final ConcurrentLruCache> cache = new ConcurrentLruCache<>(128, ClassTypeInformation::new); + private static final ConcurrentLruCache, ResolvableType> resolvableTypeCache = new ConcurrentLruCache<>(128, + ResolvableType::forClass); + public static final ClassTypeInformation COLLECTION; public static final ClassTypeInformation LIST; public static final ClassTypeInformation SET; @@ -48,11 +52,11 @@ public class ClassTypeInformation extends TypeDiscoverer { static { - OBJECT = (ClassTypeInformation) cache.get(ResolvableType.forClass(Object.class)); - COLLECTION = (ClassTypeInformation) cache.get(ResolvableType.forClass(Collection.class)); - LIST = (ClassTypeInformation) cache.get(ResolvableType.forClass(List.class)); - SET = (ClassTypeInformation) cache.get(ResolvableType.forClass(Set.class)); - MAP = (ClassTypeInformation) 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 type; @@ -70,7 +74,7 @@ public class ClassTypeInformation extends TypeDiscoverer { */ @Deprecated public static ClassTypeInformation from(Class type) { - return from(ResolvableType.forClass(type)); + return from(resolvableTypeCache.get(type)); } static ClassTypeInformation from(ResolvableType type) { diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index f8f80a405..56c0d0f66 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -55,6 +55,16 @@ class TypeDiscoverer implements TypeInformation { private final ResolvableType resolvableType; private final Map>> fields = new ConcurrentHashMap<>(); + private final Lazy isMap = Lazy.of(() -> CustomCollections.isMap(getType())); + private final Lazy isCollectionLike = Lazy.of(() -> { + + Class type = getType(); + + return type.isArray() // + || Iterable.class.equals(type) // + || Collection.class.isAssignableFrom(type) // + || Streamable.class.isAssignableFrom(type) || CustomCollections.isCollection(type); + }); private final Lazy> componentType; private final Lazy> valueType; private final Map, List>> constructorParameters = new ConcurrentHashMap<>(); @@ -125,10 +135,7 @@ class TypeDiscoverer implements TypeInformation { Class 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 implements TypeInformation { @Override public boolean isMap() { - return CustomCollections.isMap(getType()); + return isMap.get(); } @Nullable