diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index a6b4ad0e7f..3ee7da24d4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -2297,6 +2297,7 @@ public ObjectMapper setCacheProvider(CacheProvider cacheProvider) { _serializationConfig = _serializationConfig.with(cacheProvider); _deserializationContext = _deserializationContext.withCaches(cacheProvider); _serializerProvider = _serializerProvider.withCaches(cacheProvider); + _typeFactory = _typeFactory.withCaches(cacheProvider); return this; } diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java b/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java index b261053cfb..ab9b6c31dc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java @@ -22,11 +22,19 @@ public interface CacheProvider * @return {@link LookupCache} instance for constructing {@link DeserializerCache}. */ LookupCache> forDeserializerCache(DeserializationConfig config); - + /** * Method to provide a {@link LookupCache} instance for constructing {@link com.fasterxml.jackson.databind.ser.SerializerCache}. * * @return {@link LookupCache} instance for constructing {@link com.fasterxml.jackson.databind.ser.SerializerCache}. */ LookupCache> forSerializerCache(SerializationConfig config); + + /** + * Method to provide a {@link LookupCache} instance for constructing {@link com.fasterxml.jackson.databind.type.TypeFactory}. + * + * @return {@link LookupCache} instance for constructing {@link com.fasterxml.jackson.databind.type.TypeFactory}. + */ + LookupCache forTypeFactory(); + } diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/DefaultCacheProvider.java b/src/main/java/com/fasterxml/jackson/databind/cfg/DefaultCacheProvider.java index 02fdf7824d..f75b708e1e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/DefaultCacheProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/DefaultCacheProvider.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.DeserializerCache; import com.fasterxml.jackson.databind.ser.SerializerCache; +import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.LRUMap; import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.TypeKey; @@ -21,7 +22,7 @@ public class DefaultCacheProvider private static final long serialVersionUID = 1L; private final static DefaultCacheProvider DEFAULT - = new DefaultCacheProvider(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE); + = new DefaultCacheProvider(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE, TypeFactory.DEFAULT_MAX_CACHE_SIZE); /** * Maximum size of the {@link LookupCache} instance constructed by {@link #forDeserializerCache(DeserializationConfig)}. @@ -36,17 +37,25 @@ public class DefaultCacheProvider * @see Builder#maxSerializerCacheSize(int) */ protected final int _maxSerializerCacheSize; - + + /** + * Maximum size of the {@link LookupCache} instance constructed by {@link #forTypeFactory()}. + * + * @see Builder#maxTypeFactoryCacheSize(int) + */ + protected final int _maxTypeFactoryCacheSize; + /* /********************************************************************** /* Life cycle /********************************************************************** */ - protected DefaultCacheProvider(int maxDeserializerCacheSize, int maxSerializerCacheSize) + protected DefaultCacheProvider(int maxDeserializerCacheSize, int maxSerializerCacheSize, int maxTypeFactoryCacheSize) { _maxDeserializerCacheSize = maxDeserializerCacheSize; _maxSerializerCacheSize = maxSerializerCacheSize; + _maxTypeFactoryCacheSize = maxTypeFactoryCacheSize; } /* @@ -84,6 +93,11 @@ public LookupCache> forSerializerCache(Serializa return _buildCache(_maxSerializerCacheSize); } + @Override + public LookupCache forTypeFactory() { + return _buildCache(_maxTypeFactoryCacheSize); + } + /* /********************************************************** /* Overridable factory methods @@ -127,11 +141,19 @@ public static class Builder { */ private int _maxSerializerCacheSize; + /** + * Maximum Size of the {@link LookupCache} instance created by {@link #forTypeFactory()}. + * Corresponds to {@link DefaultCacheProvider#_maxTypeFactoryCacheSize}. + */ + private int _maxTypeFactoryCacheSize; + Builder() { } /** * Define the maximum size of the {@link LookupCache} instance constructed by {@link #forDeserializerCache(DeserializationConfig)} * and {@link #_buildCache(int)}. + *

+ * Note that specifying a maximum size of zero prevents values from being retained in the cache. * * @param maxDeserializerCacheSize Size for the {@link LookupCache} to use within {@link DeserializerCache} * @return this builder @@ -149,6 +171,8 @@ public Builder maxDeserializerCacheSize(int maxDeserializerCacheSize) { /** * Define the maximum size of the {@link LookupCache} instance constructed by {@link #forSerializerCache(SerializationConfig)} * and {@link #_buildCache(int)} + *

+ * Note that specifying a maximum size of zero prevents values from being retained in the cache. * * @param maxSerializerCacheSize Size for the {@link LookupCache} to use within {@link SerializerCache} * @return this builder @@ -163,13 +187,31 @@ public Builder maxSerializerCacheSize(int maxSerializerCacheSize) { return this; } + /** + * Define the maximum size of the {@link LookupCache} instance constructed by {@link #forTypeFactory()} + * and {@link #_buildCache(int)} + *

+ * Note that specifying a maximum size of zero prevents values from being retained in the cache. + * + * @param maxTypeFactoryCacheSize Size for the {@link LookupCache} to use within {@link com.fasterxml.jackson.databind.type.TypeFactory} + * @return this builder + * @throws IllegalArgumentException if {@code maxTypeFactoryCacheSize} is negative + */ + public Builder maxTypeFactoryCacheSize(int maxTypeFactoryCacheSize) { + if (maxTypeFactoryCacheSize < 0) { + throw new IllegalArgumentException("Cannot set maxTypeFactoryCacheSize to a negative value"); + } + _maxTypeFactoryCacheSize = maxTypeFactoryCacheSize; + return this; + } + /** * Constructs a {@link DefaultCacheProvider} with the provided configuration values, using defaults where not specified. * * @return A {@link DefaultCacheProvider} instance with the specified configuration */ public DefaultCacheProvider build() { - return new DefaultCacheProvider(_maxDeserializerCacheSize, _maxSerializerCacheSize); + return new DefaultCacheProvider(_maxDeserializerCacheSize, _maxSerializerCacheSize, _maxTypeFactoryCacheSize); } } } diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java index 93c3c976fc..aa20eafdc3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.cfg.CacheProvider; import com.fasterxml.jackson.databind.util.ArrayBuilders; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.LRUMap; @@ -70,6 +71,15 @@ public class TypeFactory // note: was final in 2.9, removed from 2.10 { private static final long serialVersionUID = 1L; + /** + * Default size used to construct {@link #_typeCache}. + * + * Used to be passed inline. + * + * @since 2.16 + */ + public static final int DEFAULT_MAX_CACHE_SIZE = 200; + private final static JavaType[] NO_TYPES = new JavaType[0]; /** @@ -178,7 +188,7 @@ public class TypeFactory // note: was final in 2.9, removed from 2.10 */ private TypeFactory() { - this(new LRUMap<>(16, 200)); + this(new LRUMap<>(16, DEFAULT_MAX_CACHE_SIZE)); } /** @@ -198,7 +208,7 @@ protected TypeFactory(LookupCache typeCache, TypeParser p, TypeModifier[] mods, ClassLoader classLoader) { if (typeCache == null) { - typeCache = new LRUMap<>(16, 200); + typeCache = new LRUMap<>(16, DEFAULT_MAX_CACHE_SIZE); } _typeCache = typeCache; // As per [databind#894] must ensure we have back-linkage from TypeFactory: @@ -260,11 +270,23 @@ public TypeFactory withCache(LRUMap cache) { * bigger maximum size. * * @since 2.12 + * @deprecated Since 2.16. Use {@link #withCaches(CacheProvider)} instead. */ public TypeFactory withCache(LookupCache cache) { return new TypeFactory(cache, _parser, _modifiers, _classLoader); } + /** + * Mutant factory method that will construct a new {@link TypeFactory} + * with cache instances provided by {@link CacheProvider}. + * + * @since 2.16 + */ + public TypeFactory withCaches(CacheProvider cacheProvider) { + return new TypeFactory(cacheProvider.forTypeFactory(), + _parser, _modifiers, _classLoader); + } + /** * Method used to access the globally shared instance, which has * no custom configuration. Used by {@code ObjectMapper} to diff --git a/src/test/java/com/fasterxml/jackson/databind/cfg/CacheProviderTest.java b/src/test/java/com/fasterxml/jackson/databind/cfg/CacheProviderTest.java index 9841997695..48222810bd 100644 --- a/src/test/java/com/fasterxml/jackson/databind/cfg/CacheProviderTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/cfg/CacheProviderTest.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.util.LRUMap; import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.TypeKey; +import java.util.List; import org.junit.Test; import java.util.HashMap; @@ -90,6 +91,11 @@ public LookupCache> forDeserializerCache(Dese return _cache; } + @Override + public LookupCache forTypeFactory() { + return new LRUMap<>(16, 64); + } + @Override public LookupCache> forSerializerCache(SerializationConfig config) { return new LRUMap<>(8, 64); @@ -110,6 +116,10 @@ public LookupCache> forDeserializerCache(Dese } @Override + public LookupCache forTypeFactory() { + return new LRUMap<>(16, 64); + } + public LookupCache> forSerializerCache(SerializationConfig config) { return _cache; } @@ -128,6 +138,40 @@ public JsonSerializer put(TypeKey key, JsonSerializer value) { return super.put(key, value); } } + + static class CustomTypeFactoryCacheProvider implements CacheProvider { + + final CustomTestTypeFactoryCache _cache = new CustomTestTypeFactoryCache(); + + @Override + public LookupCache> forDeserializerCache(DeserializationConfig config) { + return new LRUMap<>(16, 64); + } + + @Override + public LookupCache forTypeFactory() { + return _cache; + } + + public LookupCache> forSerializerCache(SerializationConfig config) { + return new LRUMap<>(16, 64); + } + } + + static class CustomTestTypeFactoryCache extends LRUMap { + + public boolean _isInvoked = false; + + public CustomTestTypeFactoryCache() { + super(8, 16); + } + + @Override + public JavaType putIfAbsent(Object key, JavaType value) { + _isInvoked = true; + return super.putIfAbsent(key, value); + } + } /* /********************************************************************** @@ -186,7 +230,7 @@ public void testDefaultCacheProviderSharesCache() throws Exception .cacheProvider(cacheProvider) .build(); - // Act + // Act // 3. Add two different types to each mapper cache mapper1.readValue("{\"point\":24}", RandomBean.class); mapper2.readValue("{\"height\":24}", AnotherBean.class); @@ -205,10 +249,12 @@ public void testBuilderValueValidation() throws Exception DefaultCacheProvider.builder() .maxDeserializerCacheSize(0) .maxSerializerCacheSize(0) + .maxTypeFactoryCacheSize(0) .build(); DefaultCacheProvider.builder() .maxDeserializerCacheSize(Integer.MAX_VALUE) .maxSerializerCacheSize(Integer.MAX_VALUE) + .maxTypeFactoryCacheSize(Integer.MAX_VALUE) .build(); // fail cases @@ -224,6 +270,12 @@ public void testBuilderValueValidation() throws Exception } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains("Cannot set maxSerializerCacheSize to a negative value")); } + try { + DefaultCacheProvider.builder().maxTypeFactoryCacheSize(-1); + fail("Should not reach here"); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("Cannot set maxTypeFactoryCacheSize to a negative value")); + } } /** @@ -258,4 +310,19 @@ private void _verifySerializeSuccess(CacheProvider cacheProvider) throws Excepti assertEquals("{\"slide\":123}", mapper.writeValueAsString(new SerBean())); } + + /** + * Sanity test for serialization with {@link CacheProvider#forTypeFactory()} + */ + @Test + public void sanityCheckTypeFactoryCacheSize() throws Exception + { + // custom + CustomTypeFactoryCacheProvider customProvider = new CustomTypeFactoryCacheProvider(); + ObjectMapper mapper = JsonMapper.builder() + .cacheProvider(customProvider) + .build(); + mapper.getTypeFactory().constructParametricType(List.class, HashMap.class); + assertTrue(customProvider._cache._isInvoked); + } }