DATACMNS-1101 - Refactor CustomConversion to use two-level caching.
We now use a two-level cache instead of using a composite cache key to reduce object allocations.
This commit is contained in:
committed by
Oliver Gierke
parent
6495a50df7
commit
e6e3290e59
@@ -22,7 +22,16 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.*;
|
||||
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.function.Function;
|
||||
|
||||
@@ -70,25 +79,23 @@ public class CustomConversions {
|
||||
DEFAULT_CONVERTERS = Collections.unmodifiableList(defaults);
|
||||
}
|
||||
|
||||
private final Set<ConvertiblePair> readingPairs = new LinkedHashSet<>();
|
||||
private final Set<ConvertiblePair> writingPairs = new LinkedHashSet<>();
|
||||
private final Set<Class<?>> customSimpleTypes;
|
||||
private final SimpleTypeHolder simpleTypeHolder;
|
||||
|
||||
private final List<Object> converters;
|
||||
|
||||
private final Map<ConvertiblePair, Optional<Class<?>>> customReadTargetTypes;
|
||||
private final Map<ConvertiblePair, Optional<Class<?>>> customWriteTargetTypes;
|
||||
private final Map<Class<?>, Optional<Class<?>>> rawWriteTargetTypes;
|
||||
private final Set<ConvertiblePair> readingPairs = new LinkedHashSet<>();
|
||||
private final Set<ConvertiblePair> writingPairs = new LinkedHashSet<>();
|
||||
private final Set<Class<?>> customSimpleTypes = new HashSet<>();
|
||||
private final ConversionTargetsCache customReadTargetTypes = new ConversionTargetsCache();
|
||||
private final ConversionTargetsCache customWriteTargetTypes = new ConversionTargetsCache();
|
||||
|
||||
private final Function<ConvertiblePair, Optional<Class<?>>> getCustomReadTarget = it -> getCustomTarget(
|
||||
it.getSourceType(), Optional.of(it.getTargetType()), readingPairs);
|
||||
private final Function<ConvertiblePair, Optional<Class<?>>> getReadTarget = convertiblePair -> getCustomTarget(
|
||||
convertiblePair.getSourceType(), Optional.of(convertiblePair.getTargetType()), readingPairs);
|
||||
|
||||
private final Function<Class<?>, Optional<Class<?>>> getCustomWriteTargetForSource = it -> getCustomTarget(it,
|
||||
Optional.empty(), writingPairs);
|
||||
private Function<ConvertiblePair, Optional<Class<?>>> getWriteTarget = convertiblePair -> getCustomTarget(
|
||||
convertiblePair.getSourceType(), Optional.of(convertiblePair.getTargetType()), writingPairs);
|
||||
|
||||
private final Function<ConvertiblePair, Optional<Class<?>>> getCustomWriteTarget = it -> getCustomTarget(
|
||||
it.getSourceType(), Optional.of(it.getTargetType()), writingPairs);
|
||||
private Function<ConvertiblePair, Optional<Class<?>>> getRawWriteTarget = convertiblePair -> getCustomTarget(
|
||||
convertiblePair.getSourceType(), Optional.empty(), writingPairs);
|
||||
|
||||
/**
|
||||
* Creates a new {@link CustomConversions} instance registering the given converters.
|
||||
@@ -101,12 +108,7 @@ public class CustomConversions {
|
||||
Assert.notNull(storeConversions, "StoreConversions must not be null!");
|
||||
Assert.notNull(converters, "List of converters must not be null!");
|
||||
|
||||
this.customSimpleTypes = new HashSet<>();
|
||||
this.customReadTargetTypes = new ConcurrentHashMap<>();
|
||||
this.customWriteTargetTypes = new ConcurrentHashMap<>();
|
||||
this.rawWriteTargetTypes = new ConcurrentHashMap<>();
|
||||
|
||||
List<Object> toRegister = new ArrayList<Object>();
|
||||
List<Object> toRegister = new ArrayList<>();
|
||||
|
||||
// Add user provided converters to make sure they can override the defaults
|
||||
toRegister.addAll(converters);
|
||||
@@ -237,7 +239,7 @@ public class CustomConversions {
|
||||
|
||||
Assert.notNull(sourceType, "Source type must not be null!");
|
||||
|
||||
return rawWriteTargetTypes.computeIfAbsent(sourceType, getCustomWriteTargetForSource);
|
||||
return customWriteTargetTypes.computeIfAbsent(sourceType, getRawWriteTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,8 +256,7 @@ public class CustomConversions {
|
||||
Assert.notNull(sourceType, "Source type must not be null!");
|
||||
Assert.notNull(requestedTargetType, "Target type must not be null!");
|
||||
|
||||
return customWriteTargetTypes.computeIfAbsent(new ConvertiblePair(sourceType, requestedTargetType),
|
||||
getCustomWriteTarget);
|
||||
return customWriteTargetTypes.computeIfAbsent(sourceType, requestedTargetType, getWriteTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,8 +314,7 @@ public class CustomConversions {
|
||||
* @return
|
||||
*/
|
||||
private Optional<Class<?>> getCustomReadTarget(Class<?> sourceType, Class<?> targetType) {
|
||||
|
||||
return customReadTargetTypes.computeIfAbsent(new ConvertiblePair(sourceType, targetType), getCustomReadTarget);
|
||||
return customReadTargetTypes.computeIfAbsent(sourceType, targetType, getReadTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,7 +349,82 @@ public class CustomConversions {
|
||||
|
||||
return !requestedTargetType.isPresent() //
|
||||
? true //
|
||||
: requestedTargetType.map(it -> targetType.isAssignableFrom(it)).orElse(false);
|
||||
: requestedTargetType.map(targetType::isAssignableFrom).orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to cache custom conversion targets.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class ConversionTargetsCache {
|
||||
|
||||
private final Map<Class<?>, TargetTypes> customReadTargetTypes = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Get or compute a target type given its {@code sourceType}. Returns a cached {@link Optional} if the value
|
||||
* (present/absent target) was computed once. Otherwise, uses a {@link Function mappingFunction} to determine a
|
||||
* possibly existing target type.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}.
|
||||
* @param mappingFunction must not be {@literal null}.
|
||||
* @return the optional target type.
|
||||
*/
|
||||
public Optional<Class<?>> computeIfAbsent(Class<?> sourceType,
|
||||
Function<ConvertiblePair, Optional<Class<?>>> mappingFunction) {
|
||||
return computeIfAbsent(sourceType, Object.class, mappingFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or compute a target type given its {@code sourceType} and {@code targetType}. Returns a cached
|
||||
* {@link Optional} if the value (present/absent target) was computed once. Otherwise, uses a {@link Function
|
||||
* mappingFunction} to determine a possibly existing target type.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}.
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @param mappingFunction must not be {@literal null}.
|
||||
* @return the optional target type.
|
||||
*/
|
||||
public Optional<Class<?>> computeIfAbsent(Class<?> sourceType, Class<?> targetType,
|
||||
Function<ConvertiblePair, Optional<Class<?>>> mappingFunction) {
|
||||
|
||||
TargetTypes targetTypes = customReadTargetTypes.computeIfAbsent(sourceType, TargetTypes::new);
|
||||
return targetTypes.computeIfAbsent(targetType, mappingFunction);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object for a specific {@code Class source type} to determine possible target conversion types.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class TargetTypes {
|
||||
|
||||
private final @NonNull Class<?> sourceType;
|
||||
private final Map<Class<?>, Optional<Class<?>>> conversionTargets = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Get or compute a target type given its {@code targetType}. Returns a cached {@link Optional} if the value
|
||||
* (present/absent target) was computed once. Otherwise, uses a {@link Function mappingFunction} to determine a
|
||||
* possibly existing target type.
|
||||
*
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @param mappingFunction must not be {@literal null}.
|
||||
* @return the optional target type.
|
||||
*/
|
||||
public Optional<Class<?>> computeIfAbsent(Class<?> targetType,
|
||||
Function<ConvertiblePair, Optional<Class<?>>> mappingFunction) {
|
||||
|
||||
Optional<Class<?>> optionalTarget = conversionTargets.get(targetType);
|
||||
|
||||
if (optionalTarget == null) {
|
||||
optionalTarget = mappingFunction.apply(new ConvertiblePair(sourceType, targetType));
|
||||
conversionTargets.put(targetType, optionalTarget);
|
||||
}
|
||||
|
||||
return optionalTarget;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,7 +434,7 @@ public class CustomConversions {
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
private static class ConverterRegistration {
|
||||
static class ConverterRegistration {
|
||||
|
||||
private final @NonNull ConvertiblePair convertiblePair;
|
||||
private final @NonNull StoreConversions storeConversions;
|
||||
|
||||
Reference in New Issue
Block a user