diff --git a/pom.xml b/pom.xml index d55079dbe8..f2908180d8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 3.4.0-SNAPSHOT + 3.4.0-GH-3185-SNAPSHOT Spring Data Core Core Spring concepts underpinning every Spring Data module. diff --git a/src/main/java/org/springframework/data/convert/CustomConversions.java b/src/main/java/org/springframework/data/convert/CustomConversions.java index 45d70fe812..f867f647ad 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 @@ private Class getCustomTarget(Class sourceType, @Nullable Class targetT */ static class ConversionTargetsCache { - private final Map, TargetTypes> customReadTargetTypes = new ConcurrentHashMap<>(); + private volatile 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,24 @@ public Class computeIfAbsent(Class sourceType, Function computeIfAbsent(Class sourceType, Class targetType, Function> mappingFunction) { - TargetTypes targetTypes = customReadTargetTypes.computeIfAbsent(sourceType, TargetTypes::new); + TargetTypes targetTypes = customReadTargetTypes.get(sourceType); + + if (targetTypes == null) { + + synchronized (this) { + + TargetTypes customReadTarget = customReadTargetTypes.get(sourceType); + if (customReadTarget != null) { + targetTypes = customReadTarget; + } else { + + 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 +563,7 @@ interface AbsentTargetTypeMarker {} static class TargetTypes { private final Class sourceType; - private final Map, Class> conversionTargets = new ConcurrentHashMap<>(); + private volatile Map, Class> conversionTargets = new HashMap<>(); TargetTypes(Class sourceType) { this.sourceType = sourceType; @@ -576,8 +584,20 @@ public Class computeIfAbsent(Class targetType, Function optionalTarget = conversionTargets.get(targetType); if (optionalTarget == null) { - optionalTarget = mappingFunction.apply(new ConvertiblePair(sourceType, targetType)); - conversionTargets.put(targetType, optionalTarget == null ? Void.class : optionalTarget); + + synchronized (this) { + + Class conversionTarget = conversionTargets.get(targetType); + if (conversionTarget != null) { + optionalTarget = conversionTarget; + } else { + + 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 +605,8 @@ public Class computeIfAbsent(Class targetType, Function valueConverterRegistry; + private volatile 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,53 @@ public void setConverterCacheEnabled(boolean converterCacheEnabled) { */ @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) { + + synchronized (this) { + + PropertyValueConverter fromCache = converterCache.get(property); + if (fromCache != null) { + converter = fromCache; + } else { + + converter = requireConverterFactory().getConverter(property); + + Map, PropertyValueConverter> converterCache = new HashMap<>( + this.converterCache); + converterCache.put(property, converter != null ? converter : NoOpConverter.INSTANCE); + 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 59b1823ef7..6f0868f7b0 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 @@ 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 volatile Map, Boolean> isPropertyParameterCache = new HashMap<>(); /** * Creates a new {@link InstanceCreatorMetadataSupport} from the given {@link Executable} and {@link Parameter}s. @@ -90,15 +90,25 @@ public boolean isCreatorParameter(PersistentProperty property) { Boolean cached = isPropertyParameterCache.get(property); - if (cached != null) { - return cached; - } + if (cached == null) { + + synchronized (this) { + + Boolean fromCache = isPropertyParameterCache.get(property); + if (fromCache != null) { + cached = fromCache; + } else { - boolean result = doGetIsCreatorParameter(property); + cached = doGetIsCreatorParameter(property); - isPropertyParameterCache.put(property, result); + Map, Boolean> isPropertyParameterCache = new HashMap<>(this.isPropertyParameterCache); + isPropertyParameterCache.put(property, cached); + this.isPropertyParameterCache = isPropertyParameterCache; + } + } + } - return result; + return cached; } @Override diff --git a/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/src/main/java/org/springframework/data/util/ClassTypeInformation.java index 554c41f436..35df8f48b8 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 @@ * * @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 f8f80a405e..56c0d0f665 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 @@ public boolean isCollectionLike() { 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 @@ protected TypeInformation doGetComponentType() { @Override public boolean isMap() { - return CustomCollections.isMap(getType()); + return isMap.get(); } @Nullable